USDT Approve and Transfer Failure

Hi all, I am trying to develop a DAPP that accepts the staking of USDT; however, I seem to be running into an error.

I have accounted for the fact that USDT is a 6 decimal token so I am not sure what the error is being caused by.

My smart contract code:

// SPDX-License-Identifier: MIT

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

pragma solidity ^0.8.0;

contract TokenFarm is Ownable, ReentrancyGuard {
    mapping(address => mapping(address => uint256)) public stakingBalance;
    mapping(address => uint256) public uniqueTokensStaked;
    mapping(address => address) public tokenPriceFeedMapping;
    address[] public stakers;
    address[] public allowedTokens;
    IERC20 public timeToken;
    uint256 public totalRaised;
    uint256 public goal;
    enum FUND_STATE {
        OPEN,
        REFUND,
        CLOSED
    }
    FUND_STATE public fund_state;

    constructor(address _timeTokenAddress) public {
        timeToken = IERC20(_timeTokenAddress);
        fund_state = FUND_STATE.OPEN;
        goal = 175000000000000000000000;
    }

    function stateOpen() public onlyOwner {
        fund_state = FUND_STATE.OPEN;
    }

    function stateRefund() public onlyOwner {
        fund_state = FUND_STATE.REFUND;
    }

    function stateClosed() public onlyOwner {
        fund_state = FUND_STATE.CLOSED;
    }

    function setPriceFeedContract(address _token, address _priceFeed)
        public
        onlyOwner
    {
        tokenPriceFeedMapping[_token] = _priceFeed;
    }

    function goalAmount() public onlyOwner returns (uint256) {
        return goal;
    }

    function totalERCRaised() public onlyOwner returns (uint256) {
        return totalRaised;
    }

    function getTokenValue(address _token)
        public
        view
        returns (uint256, uint256)
    {
        address priceFeedAddress = tokenPriceFeedMapping[_token];
        AggregatorV3Interface priceFeed = AggregatorV3Interface(
            priceFeedAddress
        );
        (, int256 price, , , ) = priceFeed.latestRoundData();
        uint256 decimals = uint256(priceFeed.decimals());
        return (uint256(price), decimals);
    }

    function stakeTokens(uint256 _amount, address _token) public nonReentrant {
        require(fund_state == FUND_STATE.OPEN);
        require(_amount > 0, "Amount must be more than zero!");
        require(tokenIsAllowed(_token), "Token is currently not allowed!");
        (uint256 price, uint256 decimals) = getTokenValue(_token);
        require(
            totalRaised + ((_amount * price) / ((10**decimals))) < goal,
            "Too much money"
        );
        uint256 _amount2 = ((_amount * (10**20)) / (10**decimals));
        IERC20(_token).transferFrom(msg.sender, address(this), _amount);
        updateUniqueTokensStaked(msg.sender, _token);
        if (
            address(_token) ==
            address(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
        ) {
            stakingBalance[_token][msg.sender] =
                stakingBalance[_token][msg.sender] +
                _amount2;
            totalRaised = uint256(
                totalRaised + (((_amount2 * price)) / ((10**decimals)))
            );
        }
        if (
            address(_token) ==
            address(0xdAC17F958D2ee523a2206206994597C13D831ec7)
        ) {
            stakingBalance[_token][msg.sender] =
                stakingBalance[_token][msg.sender] +
                _amount2;
            totalRaised = uint256(
                totalRaised + (((_amount2 * price)) / ((10**decimals)))
            );
        }
        if (
            address(_token) ==
            address(0x6B175474E89094C44Da98b954EedeAC495271d0F)
        ) {
            stakingBalance[_token][msg.sender] =
                stakingBalance[_token][msg.sender] +
                _amount;
            totalRaised = uint256(
                totalRaised + (((_amount * price)) / ((10**decimals)))
            );
        }
        if (
            address(_token) ==
            address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)
        ) {
            stakingBalance[_token][msg.sender] =
                stakingBalance[_token][msg.sender] +
                _amount;
            totalRaised = uint256(
                totalRaised + (((_amount * price)) / ((10**decimals)))
            );
        }
        if (uniqueTokensStaked[msg.sender] == 1) {
            stakers.push(msg.sender);
        }
    }

    function issueTokens() public onlyOwner nonReentrant {
        require(fund_state == FUND_STATE.OPEN);
        for (
            uint256 stakersIndex = 0;
            stakersIndex < stakers.length;
            stakersIndex++
        ) {
            address recipient = stakers[stakersIndex];
            uint256 userTotalValue = getUserTotalValue(recipient);
            timeToken.transfer(recipient, userTotalValue);
        }
    }

    function claimRefund(address _token) public nonReentrant {
        require(fund_state == FUND_STATE.REFUND);
        uint256 balance = stakingBalance[_token][msg.sender];
        require(balance > 0, "Staking balance cannot be zero!");
        IERC20(_token).transfer(msg.sender, balance);
        stakingBalance[_token][msg.sender] = 0;
        uniqueTokensStaked[msg.sender] = uniqueTokensStaked[msg.sender] - 1;
    }

    function getUserTotalValue(address _user) public view returns (uint256) {
        uint256 totalValue = 0;
        require(uniqueTokensStaked[_user] > 0, "No tokens staked!");
        for (
            uint256 allowedTokensIndex = 0;
            allowedTokensIndex < allowedTokens.length;
            allowedTokensIndex++
        ) {
            totalValue =
                totalValue +
                getUserSingleTokenValue(
                    _user,
                    allowedTokens[allowedTokensIndex]
                );
        }
        return totalValue;
    }

    function getUserSingleTokenValue(address _user, address _token)
        public
        view
        returns (uint256)
    {
        if (uniqueTokensStaked[_user] <= 0) {
            return 0;
        }
        (uint256 price, uint256 decimals) = getTokenValue(_token);
        return ((stakingBalance[_token][_user] * price) / (10**decimals));
    }

    function withdrawToken(address _token, uint256 _amount)
        external
        onlyOwner
        nonReentrant
    {
        IERC20 token = IERC20(_token);
        token.transfer(msg.sender, _amount);
    }

    function updateUniqueTokensStaked(address _user, address _token) internal {
        if (stakingBalance[_token][_user] <= 0) {
            uniqueTokensStaked[_user] = uniqueTokensStaked[_user] + 1;
        }
    }

    function addAllowedTokens(address _token) public onlyOwner {
        allowedTokens.push(_token);
    }

    function tokenIsAllowed(address _token) public view returns (bool) {
        for (
            uint256 allowedTokensIndex = 0;
            allowedTokensIndex < allowedTokens.length;
            allowedTokensIndex++
        ) {
            if (allowedTokens[allowedTokensIndex] == _token) {
                return true;
            }
        }
        return false;
    }
}

So the error that I am getting on the front-end looks like this:
image

And my relevant front-end code is here:

import { useEffect, useState } from "react"
import { useEthers, useContractFunction, Kovan } from "@usedapp/core"
import { constants, utils } from "ethers"
import TokenFarm from "../utils/TokenFarm.json"
import ERC20 from "../utils/MockERC20.json"
import { Contract } from "@ethersproject/contracts"
import networkMapping from "../utils/constants.json"

export const useStakeTokens = (tokenAddress: string) => {
    const { chainId } = useEthers()
    console.log({ chainId })
    const { abi } = TokenFarm
    const tokenFarmAddress = chainId ? networkMapping[String(chainId)]["TokenFarm"][0] : constants.AddressZero
    console.log(tokenFarmAddress)
    const tokenFarmInterface = new utils.Interface(abi)
    const tokenFarmContract = new Contract(tokenFarmAddress, tokenFarmInterface)

    const erc20ABI = ERC20.abi
    const erc20Interface = new utils.Interface(erc20ABI)
    const erc20Contract = new Contract(tokenAddress, erc20Interface)

    const { send: approveErc20Send, state: approveAndStakeErc20State } =
        useContractFunction(erc20Contract, "approve", {
            transactionName: "Approve ERC20 transfer",
        })

    const approveAndStake = (amount: string) => {
        setAmountToStake(amount)
        return approveErc20Send(tokenFarmAddress, amount)
    }

    const { send: stakeSend, state: stakeState } =
        useContractFunction(tokenFarmContract, "stakeTokens", {
            transactionName: "Stake Tokens",
        })
    const [amountToStake, setAmountToStake] = useState("0")

    useEffect(() => {
        if (approveAndStakeErc20State.status === "Success") {
            stakeSend(amountToStake, tokenAddress);
        }

    }, [approveAndStakeErc20State])

    const [state, setState] = useState(approveAndStakeErc20State)

    useEffect(() => {
        if (approveAndStakeErc20State.status === "Success") {
            setState(stakeState)
        } else {
            setState(approveAndStakeErc20State)
        }
    }, [approveAndStakeErc20State, stakeState])

    return { approveAndStake, approveErc20Send, approveAndStakeErc20State, state }

}

I would very much appreciate any and all help - really trying to get past this last step in my smart contract development for my project!

Cheers

Hello Gimmy,

Let me try to help you with your USDT Approve and Transfer failure.

In the console after you deploy the contract to the testnet:

  1. Set a variable and point the value to the deployed contract like this:

let farm = await TokenFarm.deployed();

  1. Next, check to see if your contract address outputs from the variable you declared above like this:

farm.address;

The contract address should output.

  1. Since you do not have the interface for USDC, you can call the USDC's basic ABI like this:

let usdc_abi = [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}];

You should get "undefined" as the output.

  1. Next, call the ABI you declared above and link it to the original USDC contract address like this:

let usdc = new web3.eth.Contract(usdc_abi, '0x07865c6E87B9F70255377e024ace6630C1Eaa37F');

Notice I am using the USDC Goerli contract address above (0x07...aa37F). Replace this with the USDC contract you are using.

  1. Now, run this command to get a list of the functions available to use for USDC:

usdc.methods;

You should get a list of the functions now that you can use to interact with the USDC contract.

  1. Call the APPROVE function and give YOUR TOKEN FARM CONTRACT permission to spend USDC funds on your behalf. Remember that reads are designated by .call() and writes are designated by .send({from: 'USER WALLET ADDRESS'}) like this:

usdc.methods.approve('TOKEN FARM CONTRACT ADDRESS', 'AMOUNT').send({from: 'USER WALLET ADDRESS'});

Your approval should be confirmed.

  1. Next, transfer the funds to your TOKEN FARM contract like this.

usdc.methods.transfer('TOKEN FARM CONTRACT', 'AMOUNT').send({from: 'USER WALLET ADDRESS'});

The USDC should have been successfully transferred to your TOKEN FARM contract.

  1. Finally, check to see that your TOKEN FARM CONTRACT has the USDC by running.

usdc.methods.balanceOf('TOKEN FARM CONTRACT').call();

The balance should reflect the USDC you transferred.

Hope this helps.

Pavon

Hey Pavon, I really appreciate the help! Just wondering how I could apply this so if someone were to visit my front-end how they would be able to transfer USDT.

Also I noticed you used USDC for this example - fortunately I am actually not having any troubles with USDC but instead USDT is giving me the error. How would I go about this for USDT?

Please let me know if I am misinterpreting anything as I am still relatively new to all this!

Thanks a lot, appreciate all the help!

You would use the same steps above for USDT since it is an ERC20 token. Just replace the CONTRACT ADDRESS in Step 4 above with the one for USDT...along with the variable names.

As far as front end goes, unfortunately I am not well versed in frontend development. I, too, am still learning as well.

The steps I laid out above is for interaction with your contract after deployment on the backend.

Pavon

Ah ok, thanks Pavon, I'll give this all a shot!

If anyone else sees this and knows how to integrate it with the front-end I would really appreciate it!

USDT is not a standard ERC20 token, that is it does not have a returning value for transfer transferFrom, so you can not use the interface IERC20, maybe you can try to use SafeERC20.sol

1 Like

Hey Skyge - appreciate the tip! I'll give that a shot

1 Like

For context to those who stumble upon this post - simply changed all the transfer or transferFrom in the smart contract to safeTransfer or safeTransferFrom.

Further, of course had to call in the SafeERC20.sol and stated that:

using SafeERC20 for IERC20;

This post was quite useful as well - thank you Skyge for your continued help and guidance on these forums!

Cheers

1 Like