Allowance is never approved (ERC20 - USDC)

I am testing an ERC20 contract from USDC on Polygon and a lot of functions appear to work but I am stuck on the actual payments and I endlessly get the message that the ERC20 balance is too low. It is all about the allowance not being set.

I think it is the way the addresses are being used. My code is below. Does anyone have any ideas?

The app use case is a standard time-locked contract ecommerce one - the buyer pays into the contract a USDC amount and then the seller gets the USDC amount when a condition is met (eg time has passed). The buyer deploys the contract and funds it. The seller receives the funds.

function approveTransfer(uint256 amount) 
        public  {
        IERC20(usdcAddress).approve(address(this), amount); // this fails
    }

    function checkAllowance() public view returns (uint256) {
        return IERC20(usdcAddress).allowance(msg.sender, address(this)); // this returns 0
    }

    function approveAndTransferUSDC(uint256 amount) 
        public returns(bool)  {
        return IERC20(usdcAddress).transferFrom(msg.sender, address(this), amount);
    }

output from Approve call -
from": "0xfe4F5145f6e09952a5ba9e956ED0C25e3Fa4c7F1",
"topic": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925",
"event": "Approval",
"args": {
"0": "0x3E18b9DAdC0d5aD3918aB3356E73808714F2c983",
"1": "0x0D1D5933dA6283D635D6ae65c356FBe01Dc1797C",
"2": "1000",
"owner": "0x3E18b9DAdC0d5aD3918aB3356E73808714F2c983", <--- contract addr
"spender": "0x0D1D5933dA6283D635D6ae65c356FBe01Dc1797C", <--- buyer addr
"value": "1000"
}
buyer - 0x0D1D5933dA6283D635D6ae65c356FBe01Dc1797C
contract - 0x3E18b9DAdC0d5aD3918aB3356E73808714F2c983
seller - 0x846799Ed461091F982d52FB2f7812913c8E90B01

IERC20(usdcAddress).approve(address(this), amount);

This code approves your contract (first argument, address(this)) to spend your contract's (caller of the approve() function) tokens.

If you want the user to give you approval, they need to invoke the approve() function on the usdcAddress directly - not through any other contract.

So the process is made from 2 separate transactions:

  1. user invokes approve() function on usdcAddress, passing your contract address as the first argument
  2. user invokes your function approveAndTransferUSDC() that calls the transferFrom()
1 Like

It seems that the approve function has the owner as the escrow contract but that should be the buyer (msg.sender) - results from test below.

You say invoke on the actual usdcAddress directly and not in the escrow contract but how is that done within an escrow framework? Does that mean the ecommerce app needs to perform several sendTransactions, one for approve to the deployed USDC contract (from circle) to approve the escrow contract address which manages the actual settlement logic?

I saw a lot of escrow ERC20 examples online but I cannot really find many people even using approve. I tried just transfer (not transferFrom) and that had similar errors.

Here are the test results from the approve -
"event": "Approval",
"args": {
"0": "0x2456b43ec60Fb3A0888aF7440C3cFeDFdF302Ae6",
"1": "0x0FA8781a83E46826621b3BC094Ea2A0212e71B23",
"2": "100",
"owner": "0x2456b43ec60Fb3A0888aF7440C3cFeDFdF302Ae6", <--- escrow contract
"spender": "0x0FA8781a83E46826621b3BC094Ea2A0212e71B23", <---- USDC ERC20 contract (for the stablecoin)
"value": "100"

That's right.

A real-life example is Uniswap and other DeX apps. Before you're able to sell your USDC for another token, you need to approve the Uniswap address to spend your USDC (that's the first transaction). And only then you can successfully submit the actual trade (second transaction).

Note that the approval can be for a larger amount than the actual trade, and it decreases with each transferFrom(). So in practice, the user doesn't need to send 2 transactions for each trade. They can for example approve Uniswap to spend 100 USDC, and then submit 5 separate trades - each spending 20 of those approved tokens.

I have been using wagmi and wallet connect for the actual web3 part and that means now I have to revert to wagmi to work out how to implement all this.

I don't have any experience with Wagmi (so anyone else feel free to chip in with a code example) but based on their website (stating you can "interact with contracts") it seems that you can still request 2 separate transactions using this framework.

Yes wagmi basically implements connections between wallets and dapps. It is off-topic for this forum, but it looks like I need to implement a part solution using walletconnect and part solution in the solidity.

This works now using the UI at polygon mumbai scan to manually approve and then the solidity can perform the transfer functions. I am just implementing now in reactjs with usecontract. So there are a few stages -

  1. Approve via the UI (or via wagmi) for the approval amount
    2.This sets an allowance in the erc20 contract (not the escrow contract but on the actual stablecoin contract)
  2. Then the transferfrom is done as follows - IERC20(usdcAddress).transferFrom(buyer, address(this), amount) - this pays the amount into the escrow contract
  3. Then the seller claims the amount via a transfer - IERC20(usdcAddress).transfer(seller, amount)
    Hence the UX for the buyer involves -
  4. Escrow conract deployment (time locked) - one txn
  5. Approve (one txn)
  6. Transfer (also one txn)

This is far from a "one click" type of model which Amazon uses and needs the buyer to make a lot of approvals via their wallet. My suggestion here is that the functions can be combined and the approve and transfers be combined into one function. The UX should really be for the buyer to deploy, approve, and transfer in one txn. The process for paying ether into a contract is much simplier and the contract deployment can manage the actual receipt of funds, hence there is just one wallet approval for the buyer.
The Seller UX is -

  1. Claim (one txn)