3

I'm rying to swap some BNB -> Token, then swap it back, all in one solidity function. Here's my code:

interface IERC20 {
    function approve(address spender, uint256 value) external returns (bool);
}

interface IPancakeSwapRouter {
    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapExactTokensForETH(
        uint256 amountIn, 
        uint256 amountOutMin, 
        address[] calldata path, 
        address to, 
        uint256 deadline
    ) external returns (uint[] memory amounts);

    function WETH() external pure returns (address);
    function factory() external pure returns (address);
}

interface IPancakeFactory {
    function getPair(address tokenA, address tokenB) external view returns (address pair);
}

contract Test {

    receive() external payable { }
    fallback() external payable { }

    function BuySell(address token_out, uint value) public payable {

        address swap_router_addr = address(0x10ED43C718714eb63d5aA57B78B54704E256024E); //Pancake router
        IPancakeSwapRouter swap = IPancakeSwapRouter(swap_router_addr);

        uint deadline = block.timestamp + 1200;

        address[] memory path_buy = new address[](2);
        path_buy[0] = swap.WETH();
        path_buy[1] = token_out;

        // 1. try buy 0.001
        uint256[] memory amounts;

        amounts = swap.swapExactETHForTokens { value: msg.value } (
            0, path_buy, address(this), deadline
        );

        // 2. try to sell 
        // approve first
        IERC20 token_dest = IERC20(token_out);
        address pair = IPancakeFactory(swap.factory()).getPair(swap.WETH(), token_out);
        token_dest.approve(pair, amounts[1]); // approve WBNB/Token pair
        token_dest.approve(swap_router_addr, amounts[1]); // approve PancakeSwap Router

        // sell
        address[] memory path_sell = new address[](2);
        path_sell[0] = path_buy[1];
        path_sell[1] = path_buy[0];

        swap.swapExactTokensForETH(
            amounts[1], 0, path_sell, address(this), deadline);
    }
}

I can confirm the error occurs at the last line swapExactTokensForETH, it says "TRANSFER_FROM_FAILED", I guess it's about approve(), but I already approved the token for PancakeRoute before the swap.

Some failed tx sample: https://bscscan.com/tx/0x3feaa9c746ee4458d073a0311b90fb8658d45ed7bac4877bc2934f7e05c4c538

Why the error?

3 Answers 3

8

The issue is that you need to approve the pair and not the router. Looking at the transaction you linked you can see that it revert on the transfer from 0xa89C6f52Ce3Ce1BFF6CAac1E1c98b4f9b3AA5f63 (your contract) to 0x4a1a7f263b49dA21fBeD0907317233AA06AdF0dF (PancakePair). Therefore this is also the account that you need to approve.

The address of the PancakePair contract can be loaded from the PancakeFactory by calling getPair(tokenA, tokenB). The factory address can be loaded from the router via factory().

Edit:

The token that you are using enforces a transfer fee. For this you cannot use swapExactTokensForETH, you need to use swapExactTokensForTokensSupportingFeeOnTransferTokens.

9
  • I tried approve both Router and the Pair, but same error, the code updated. BTW how do you know it reverted when transferring token to 0x4a1a7f263b49dA21fBeD0907317233AA06AdF0dF(PancakePair) ? I don't see this in the page, from the "Internal Txns" there are only two lines, and it's transferring BNB from my contract to PancakeRouter. The failed sample tx: bscscan.com/tx/…
    – aj3423
    Jan 7, 2022 at 9:25
  • 1
    You can check the trace on bscscan. (the three dots in the top right and select "Geth Debug Trace_2")
    – Richard
    Jan 7, 2022 at 12:43
  • 1
    MSTART is a token that enforces a fee on transfer, these need to be handled differently. You need to call swapExactTokensForTokensSupportingFeeOnTransferTokens.
    – Richard
    Jan 7, 2022 at 12:55
  • 1
    Yeah, that's what I mean with "it might not be that simple". You cannot transfer the full amount, but I would have to look into the details of the MSTART contract to figure out how to best calculate how much you can send. In their contract they have a _getRate that they use for this, but this is all very specific to their token, so I don't know the details.
    – Richard
    Jan 7, 2022 at 17:02
  • 1
    Finally I worked it out. If a token uses fee and you buy it with amounts = swapExactETHForTokens(), the amounts[1] is LESS than what you expected. For example, the MSTART token takes fee at 4%, if the amounts[1] returns 100, the actual balanceOf() is only 96. So when you try to sell 100 MSTART, it shows error "TRANSFER_FROM_FAILED". I guess this is why swapExactETHForTokensSupportingFeeOnTransferTokens not returning amounts. My solution is use balanceOf() to get the token actually bought.
    – aj3423
    Jan 7, 2022 at 18:58
0

Your code probably has design errors and needs some changes.

Exchanging native BNB for tokens directly is not possible. You need to call the WBNB contract using withdrawal and deposit for that.

After that exchanges are in WBNB and tokens.

If the token already has a liquidity pool, just call the exchange function directly in the pancake router V2 contract.

On the other hand, if your token has fees you should call the fee-supported exchange function on the transfer: https://docs.uniswap.org/protocol/V2/reference/smart-contracts/router-02

Remember to give the proper approvals on the token and WBNB contracts.

0

Some tokens don't allow you to approve more then your balance. I have an example of getTokenBalance in web3. But you can use an equivalent in Solidity.

async function approveToken(tokenAddress) {
    const contractApprove = new web3Node.eth.Contract([
        {
            constant: false,
            inputs: [
                { name: '_spender', type: 'address' },
                { name: '_value', type: 'uint256' },
            ],
            name: 'approve',
            outputs: [{ name: 'success', type: 'bool' }],
            payable: false,
            stateMutability: 'nonpayable',
            type: 'function',
        },
    ], tokenAddress);

    const nonce = await web3Node.eth.getTransactionCount(wallet, 'pending');
    const balance = await getTokenBalance(tokenAddress);
    return await contractApprove.methods.approve(pancakev2routeraddress, balance)
        .send({ from: wallet, gasLimit: maxGasLimit, gasPrice: gasPrice, nonce: nonce });
};

async function getTokenBalance(tokenAddress) {
    const contract = new web3Node.eth.Contract([
        {
            constant: true,
            inputs: [{ name: 'owner', type: 'address' }],
            name: 'balanceOf',
            outputs: [{ name: 'balance', type: 'uint256' }],
            type: 'function'
        }
    ], tokenAddress);
    return await contract.methods.balanceOf(wallet).call();
};
3
  • This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review
    – DevCrypto
    Mar 8 at 9:52
  • His question is TRANSFER FROM FAILED. I got exactly the same error and the solution is to approve exactly the balance you own as you cannot approve more then your balance in certain situations. Tell me why it's not the solution ? He even said it was the problem in his solution. : "returns 100, the actual balanceOf() is only 96. So when you try to sell 100 MSTART, it shows error "TRANSFER_FROM_FAILED". I guess this is why". So I provided a solution in web3 instead of solidity. A solution that approved exactly the amount of balance you own. Please tell me why this is wrong ?
    – LiT Zelos
    Mar 8 at 10:19
  • "TRANSFER FROM FAILED" typically indicates that there was an error or failure in the approval process for a transfer.
    – LiT Zelos
    Mar 8 at 10:25

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.