I can not approve for the contract

This is my test contract:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";


contract CallContract is Ownable {

    IERC20 public usdt;

    function setUSDTContract(address usdtAddress) public onlyOwner {
        require(usdtAddress != address(0));
        usdt = IERC20(usdtAddress);
    }

    function approveUsdt(uint amount) public returns (uint){
        require(usdt.approve(address(this),amount), "appropve usdt failed");
        return usdt.allowance(msg.sender, address(this));
    }

    function getApprovedBalance() public view returns (uint) {
        return usdt.allowance(msg.sender, address(this));
    }
}

I set usdt address, then

  1. call approveUsdt to approve some usdt for the contract, and it returns allowance normally.
  2. call getApprovedBalance it returns “0”
    question: Why approve failed for the contract? Why approveUsdt returns allowance normally, but getApprovedBalance returns 0?

This approves the contract to transfer an amount of usdt from itself:

usdt.approve(address(this),amount)

And this returns the amount of usdt that the contract is allowed to transfer from the sender:

usdt.allowance(msg.sender, address(this))

Now, obviously, the sender in this case is not the contract, but the account that you’re using in order to sign and execute each one of these transactions.


The above is just to strictly answer your question.

In order to give you some further help, note that the usdt.approve(address(this),amount) part is completely redundant.

An account doesn’t need approval to transfer funds from itself; it only needs approval to transfer funds from other accounts.

More generally, you seem to be missing the whole point in the approve/transferFrom scheme, which consists of the following two transactions:

  1. entity executes tokenContract.approve(someContract), in order to allow someContract to transfer funds from entity

  2. entity executes someContract.someFunc, which internally executes tokenContract.transferFrom(entity) in order to transfer funds from entity to somewhere (either to someContract itself, or to some other account)

Note that entity can be either an externally-owned account (aka wallet), or a smart-contract account.


In short, your off-chain script (assuming that this is how you are trying to execute the process), should look more or less like this:

  1. Deploy an instance of CallContract

  2. Sign and send with yourAccount: usdt.approve(callContract.address, amount)

  3. Verify that usdt.allowance(yourAccount, callContract.address) returns amount

Again - all of the above should be executed from your off-chain script, NOT as part of your contract.

At this point, callContract should be able to transfer up to amount usdt from yourAccount to any other account, so you can add a function in this contract, which will internally call usdt.transferFrom in order to execute the desired transfer.

On top of that, you may have realized by now that both of the functions currently implemented in your contract are redundant:

  • approveUsdt, because this approval should be executed outside the contract
  • getApprovedBalance , because this query can be executed outside the contract
function approveUsdt(uint amount) public returns (uint){
    require(usdt.approve(address(this), amount), "approve usdt failed");
    return usdt.allowance(msg.sender, address(this));
}

In this function, you are attempting to approve the contract (address(this)) to spend the user’s USDT tokens. However, the typical use case is to approve a different address (e.g., a decentralized exchange or another smart contract) to spend your tokens.

Here’s the corrected function:

function approveUsdt(uint amount) public returns (uint){
    require(usdt.approve(msg.sender, amount), "approve usdt failed");
    return usdt.allowance(msg.sender, address(this));
}

Now, you are approving the caller (msg.sender) to spend their USDT tokens, and the function should return the correct allowance.

In the original function, you were approving the contract itself (address(this)), which is likely the reason why getApprovedBalance returned “0” because the contract wasn’t approved to spend the tokens of the caller.