Pancake swap v3 exactInputSingle fails without reason

Im trying to make swap on PancakeSwapRouterV3 (0x13f4EA83D0bd40E75C8222255bc855a974568Dd4) using method exactInputSingle.
With v2 version i could do this with swapExactTokensForETH method but is no longer in new contract.

I want to swap native token in SmartChain and swap it to other token(busd for example) but my transaction keeps getting reverted.

Want to buy/sell some token by web3js.

Heres part of my code i run transaction:

:1234: Code to reproduce

let pancakeSwapAbi = JSON.parse(fs.readFileSync(pancakeSwapContractFile));
let pancakeSwapContract = new web3.eth.Contract(pancakeSwapAbi,pancakeSwapContractAddress);
var count = await web3.eth.getTransactionCount(fromAddress);
const deadline = Math.floor(Date.now() / 1000) + (60 * 10);

    const value = web3.utils.toWei("0.1", 'ether')
    const params = {
        tokenIn: WBNBAddress,
        tokenOut: TokenAddress,
        fee: 30000,
        recipient: fromAddress,
        deadline: deadline,
        amountIn: value,
        amountOutMinimum: 0,
        sqrtPriceLimitX96: 0,
    }`

const txArgs = {
nonce: web3.utils.toHex(count),
gasPrice: web3.utils.toHex(gasPrice),
from: fromAddress,
to: pancakeSwapContractAddress,
value: web3.utils.toHex(0),
gas: gas,
data: pancakeSwapContract.methods.exactInputSingle(params).encodeABI()
};

let private_key = process.env.USER_PK;
let txHash = null;
try {
let signedTransaction = await web3.eth.accounts.signTransaction(txArgs, private_key, function () {
log.info(Transaction signed from ${fromAddress} to ${pancakeSwapContractAddress} on ${valueToSend} WEI);
}
);
await web3.eth.sendSignedTransaction(signedTransaction.rawTransaction, async function (error, hash) {
txHash = hash;
});
} catch (e) {
console.log(e);
log.error('Error while transfer: ', e);
return;
}


:computer: Environment

Web3js

The returned value of Date.now() might be subjected to your local time-zone, which in turn might be a relic of the past relatively to the chain's current time.

You can verify this by comparing your deadline value with:

const timestamp = (await web3.eth.getBlock("latest")).timestamp;

Or you can actually just use that value instead, just be sure to add a few minutes to it.

Thank You for your reply. Those values look like this:

deadline
1704445684
timestamp
1704445082

Are they good? Deadline is higher. I even tried to have deadline = (await web3.eth.getBlock("latest")).timestamp + 10000 but same result. Just waits longer to fail

So perhaps you need to use a higher gas-price.

Then i think i would get some error about low gas.
Now i have const gas = 2100000;
Do you think is sufficient?

That's gas, not gas-price.

Gas indicates the amount of gas units that you are providing for the transaction to execute.

Gas-price indicates the amount of ether that you are willing to pay per gas unit.

The product of these two values is how much ether will be taken out of your wallet. If the transaction ends up using less gas than what you've stated, then your wallet will be refunded for the remaining. But your wallet nevertheless must hold that initial amount of ether in order to execute the transaction under the specified gas and gas-price.

For example, suppose that you state:

  • Gas = 20000 units
  • Gas-price = 0.001 ether

Then your wallet needs to hold at least 20000 * 0.001 = 20 ether.

Now, supposed that the transaction has consumed only 15000 gas units.

Then you wallet will ultimately be charged only 15000 * 0.001 = 15 ether.

In either case, the higher gas-price you specify, the faster your transaction is likely to execute.
After all, you are stating that you're willing to pay more for getting it executed.

BTW, you might need to specify type: 1 here, since the default by now might be type: 2, which requires a different input structure altogether. For more details, read about eip-1559.

In either case, before executing the transaction, you can request for the estimated amount of gas that it requires, and then use a slightly higher amount when you finally execute that transaction.

This would be beneficial not only for knowing how much gas you need to specify, but also for telling you whether or not the transaction can be executed to completion without reverting:

const tx = pancakeSwapContract.methods.exactInputSingle(params).encodeABI();
const gas = await tx.estimateGas({from: fromAddress});

If this throws an exception, then it indicates of an actual problem in the contract function call which you are trying to execute within the transaction. For example, an incorrect input value, an unauthorized caller, etc.

If it doesn't throw an exception, then you can use the returned gas value (or perhaps a slightly higher value in order to ensure successful completion) in order to execute the transaction.

Thank for your support I tried what you told me like this for tests:

 const valueToSend = web3.utils.toWei("0.1", 'ether');
        const params = {
            tokenIn: BUSDAddress,
            tokenOut: USDTContractAddress,
            fee: 30000,
            recipient: fromAddress,
            deadline: deadline,
            amountIn: web3.utils.toHex(valueToSend),
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0,
        }
        
        const tx = pancakeSwapContract.methods.exactInputSingle(params);
        const gas = await tx.estimateGas({from: fromAddress});

        const txArgs = {
            nonce: web3.utils.toHex(count),
            gasPrice: web3.utils.toHex(gasPrice),
            from: fromAddress,
            to: pancakeSwapContractAddress,
            value: '0x00',
            gas: gas,
            data: tx.encodeABI()
        };

Looks like when code try to run tx.estimateGas instant returns "Error occurred: Error: Returned error: execution reverted"

Well, you haven't tried the type: 1 part which I have mentioned, but anyway, since estimateGas throws an exception, the problem clearly occurs regardless of that type, which is used only for executing the actual transaction.

Please elaborate on these two:

tokenIn: BUSDAddress,
tokenOut: USDTContractAddress,

Also, please specify which chain and which network on that chain you are targeting.

For example, ethereum/mainnet, polygon/mumbai, etc.

Note that this looks a bit suspicious:

The name of this variable implies that you are intending to send ETH.
But in the transaction itself, you specify value: '0x00'.
So you should explain exactly which Pancake-Swap pool you are targeting here.

Oh, and one more thing - you most definitely need to approve the Pancake-Swap contract to transfer tokens from your account, prior to executing this transaction (and prior to even estimating its expected gas consumption).

So in short, you need to sign and send a transaction of the following function-call:

busdTokenContract.approve(pancakeSwapContractAddress, amountIn)

Im on SMART CHAIN MAINNET.
I've tried with type: 1 but same result.
Im approving spending BUSD to pancake contract. Code below.

I typed value: '0x00' cause old method from v2 used same parameter. It looks like this:

let rawTxSwap = {
        nonce: web3.utils.toHex(count),
        gasPrice: web3.utils.toHex(gasPrice),
        from: from,
        to: pancakeSwapContractAddress,
        value: '0x00',
        gas: gas,
        data: pancakeSwapContract.methods.swapExactTokensForETH(
            valueToSendBN,
            web3.utils.toWei("0.00000000000000001", 'ether'),
            [tokenContractAddress, WBNBAddress],
            from,
            Date.now() + 10000
        ).encodeABI(),
    };

Heres BUSD approval transaction: https://bscscan.com/tx/0xc7b91845dd633cc38d54e881acd774826c428ed9f498af01aad623b8a78dbd1d

My
BUSDContractAddress: 0xe9e7cea3dedca5984780bafc599bd69add087d56
USDTContractAddress: 0x55d398326f99059fF775485246999027B3197955
and are correct

Heres my BUSD-PancakeSwap aprroval method:
*dont look at mess in code, cause just for test purposes :slight_smile:

exports.approve_busd = async function (pancakeSwapContractAddress) {
    let BUSDContractAddress = process.env.BUSD_CONTRACT_ADDRESS;
    let BUSDContractFile = process.env.BUSD_CONTRACT_FILE;
    let BUSDAbi = JSON.parse(fs.readFileSync(BUSDContractFile));
    let BUSDContract = new web3.eth.Contract(BUSDAbi, BUSDContractAddress);

    try {
        let fromAddress = process.env.USER_ADDRESS;

        let gasPrice = await web3.eth.getGasPrice();
        let gas = 320000;
        let private_key = process.env.USER_PK;

        let count = await web3.eth.getTransactionCount(fromAddress);
        let txHash = null;
        try {
            let rawTxApproveBUSD = {
                nonce: web3.utils.toHex(count),
                gasPrice: web3.utils.toHex(gasPrice),
                from: fromAddress,
                to: BUSDContractAddress,
                value: '0x00',
                gas: gas,
                data: BUSDContract.methods.approve(
                    pancakeSwapContractAddress,
                    BigInt(10000000000000000000000000),
                ).encodeABI(),
            };

            let signedTransactionApprove = await web3.eth.accounts.signTransaction(rawTxApproveBUSD, private_key, function () {
                    log.info(`Transaction signed from ${fromAddress} to ${BUSDContractAddress} for approve;`);
                }
            );

            await web3.eth.sendSignedTransaction(signedTransactionApprove.rawTransaction, async function (error, hash) {
                txHash = hash;
            });
        } catch (e) {
            log.error('Error while transfer eth: ', e);
            return;
        }
        log.info(`Transaction sent from ${fromAddress} to ${BUSDContractAddress} for approve`);
        console.log("Transaction hash: " + txHash);

        console.log(`\n \n ***** END *****`);
    } catch (e) {
        log.error('Error while transfer eth: ', e);
    }
}

And my actual code for swap BUSD->USDT

exports.buy_tokens_by_busd_v3 = async function (pancakeSwapContractAddress, pancakeSwapContractFile) {
    await this.approve_busd(pancakeSwapContractAddress)

    const BUSDContractAddress = process.env.BUSD_CONTRACT_ADDRESS;
    const USDTContractAddress = process.env.USDT_CONTRACT_ADDRESS;
    const BUSDContractFile = process.env.BUSD_CONTRACT_FILE;
    const fromAddress = process.env.USER_ADDRESS;

    try {
        const BUSDAbi = JSON.parse(fs.readFileSync(BUSDContractFile));
        const BUSDContract = new web3.eth.Contract(BUSDAbi, BUSDContractAddress);

        const pancakeSwapAbi = JSON.parse(fs.readFileSync(pancakeSwapContractFile));
        const pancakeSwapContract = new web3.eth.Contract(pancakeSwapAbi, pancakeSwapContractAddress);
        const BUSDBalance = await BUSDContract.methods.balanceOf(fromAddress).call();

        const count = await web3.eth.getTransactionCount(fromAddress);
        const deadline = (await web3.eth.getBlock("latest")).timestamp + 10000;

        console.log("BUSD BALANCE");
        console.log(BUSDBalance);

        console.log('deadline');
        console.log(deadline);

        console.log('actual timestamp');
        console.log((await web3.eth.getBlock("latest")).timestamp);

        let gasPrice = await web3.eth.getGasPrice();

        let gas = 2100000;
        const valueToSend = web3.utils.toWei("0.1", 'ether');
        const params = {
            tokenIn: BUSDContractAddress,
            tokenOut: USDTContractAddress,
            fee: 3000,
            recipient: fromAddress,
            deadline: deadline,
            amountIn: web3.utils.toHex(valueToSend),
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0,
        }

        const tx = pancakeSwapContract.methods.exactInputSingle(params);
        // const gas = await tx.estimateGas({from: fromAddress});

        console.log('gas');
        console.log(gas);

        console.log('value to send: ');
        console.log(valueToSend);

        const txArgs = {
            nonce: web3.utils.toHex(count),
            gasPrice: web3.utils.toHex(gasPrice),
            from: fromAddress,
            to: pancakeSwapContractAddress,
            value: '0x00',
            gas: gas,
            data: tx.encodeABI()
        };

        let private_key = process.env.USER_PK;
        let txHash = null;
        try {
            let signedTransaction = await web3.eth.accounts.signTransaction(txArgs, private_key, function () {
                    log.info(`Transaction signed from ${fromAddress} to ${pancakeSwapContractAddress} on ${valueToSend} WEI`);
                }
            );

            await web3.eth.sendSignedTransaction(signedTransaction.rawTransaction, async function (error, hash) {
                console.log(error);
                txHash = hash;
            });
        } catch (e) {
            console.log(e);
            log.error('Error while transfer: ', e);
            console.log(` Balance: ${balance}, ValueToSend: ${valueToSend}. Gas price ${gasPrice}, Gas Value ${gasValue}`);
            return;
        }

        log.info(`Transaction sent from ${fromAddress} to ${pancakeSwapContractAddress} on ${valueToSend} WEI`);
        console.log("Transaction hash: " + txHash);
        console.log(`\n \n ***** END *****`);
    } catch (e) {
        log.error('Error occurred: ', e);
    }
}