I have a small Paraswap contract. All it does is make two low level calls to Paraswap to swap tokens and return back to sender.

pragma solidity ^0.8.0;

import "./IERC20.sol"; // from OpenZeppelin 

contract Swap {

    address private constant Paraswap = // Main Paraswap address
    address private constant ParaswapProxy = // Paraswap Proxy address

    function swapIt(
        address _token1,
        uint _amount1,
        bytes calldata _swapdata1,
        address _token2,
        uint _amount2,
        bytes calldata _swapdata2
        ) external {

            IERC20(_token1).transferFrom(msg.sender, address(this), _amount1);

            IERC20(_token1).approve(ParaswapProxy, _amount1);
            (bool success1, ) = Paraswap.call(_swapdata1);
            require(success1, 'Failed at one');

            IERC20(_token2).approve(ParaswapProxy, _amount2);
            (bool success2, ) = Paraswap.call(_swapdata2);
            require(success2, 'Failed at two');

            IERC20(_token2).transferFrom(address(this), msg.sender, _amount2);

One thing I have to do is approve the tokens with the Paraswap proxy so it can move the tokens from the contract to the main Paraswap contract to do the swaps.

The way I see it going is like this with UDST > USDC > USDT

  1. I approve my wallet separately with my contract to spend max amount of USDT.
  2. I start the contract and the function swapIt starts.
  3. Because I approved my wallet beforehand, the contract transfers USDT from my wallet to itself.
  4. Contract then approves the amount with the ParaswapProxy and makes the low level call to swap USDT to USDC.
  5. It returns USDC to the contract.
  6. Contract approves USDC with ParaswapProxy and calls low level call to swap from USDC back to USDT.
  7. It returns USDT to the contract.
  8. Contract returns USDT back to my wallet.

What's happening is at step 6 on the low level call, it fails. It can't see a USDC balance to do another swap.

I figured this out by adding:

uint bal = IERC20(_token2).balanceOf(address(this));
require(bal >= _amount2, 'Balance too low');

And I got that error on USDC.

So I dialed back and tried the first swap by itself:

IERC20(_token1).transferFrom(msg.sender, address(this), _amount1);

IERC20(_token1).approve(ParaswapProxy, _amount1);
(bool success1, ) = Paraswap.call(_swapdata1);
require(success1, 'Failed at one');

And USDT is taken from my wallet, swapped to USDC and left on the contract, so that part is working. It is just at the second swap it fails to see the USDC. I feel I am missing something small.

Would the starting balance of USDC be 0 and then I have to try to detect when it is transferred back? Or is this something completely different?


Looking into this more I came across this site for debugging transactions.


Looking at my transaction, it now says:

ERC20: transfer amount exceeds balance

Somewhere along the lines I believe the value of USDC is changing before the second swap and is higher than the amount specified but I'm not sure how, why, or how to get around it. I am okay with it taking more so I changed a line to:

IERC20(_token2).approve(ParaswapProxy, 1 ether);

But still the same error.

1 Answer 1


After going through tenderly debug explorer and looking at the transactions details line by line, I found out that I was approving USDT twice.

So on first swap it went fine, but on the second swap it approved USDT again and failed when it needed to approve USDC.

But also even after approving USDC during the second swap, the amount of USDC would somehow change and become less than what was expected.

This was an issue in my Python code and partly an issue with the exchange.

  • Hi, what is the address of Paraswap // Main Paraswap address ? thanks
    – adam mr
    Commented Jun 13 at 5:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.