I am trying to execute a swap on Pancakeswap using web3, but I get an error when calculating transaction cost

Viewed 23375

I am new at ethereum development and I am working on a simple script to execute swaps using Pancakeswap. Everything seems to be working well up to the point where I am building my transaction and calculate the tx-cost. I am using the UniswapRouterV02 abi create my Pancakeswap contract. The code:

const init = async () => {

    const [WBNB, BUSD] = await Promise.all(
        [addresses.WBNB, addresses.BUSD].map(tokenAddress => (
            new Token(
                ChainId.MAINNET,
                tokenAddress,
                18
            )
        )));


    const pair = await Fetcher.fetchPairData(WBNB, BUSD, provider)
    const route = await new Route([pair], WBNB)
    const trade = await new Trade(route, new TokenAmount(WBNB, tradeAmount), TradeType.EXACT_INPUT)
    const executionPrice = trade.executionPrice.toSignificant(12)

    // Correct prices; everything seems correct up until here

    const slippageTolerance = new Percent('50', '10000')

    const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw
    const path = [WBNB.address, BUSD.address]
    const to = MY_ADDRESS
    const deadline = Math.floor(Date.now() / 1000) + 60 * 20
    const value = trade.inputAmount.raw

    // Correct prices everything seems correct up until here

    const pancakeSwap = new web3.eth.Contract(
        abis.uniswapRouter.abi,
        addresses.PANCAKE_ROUTER //'0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F'
    );

    let tx = pancakeSwap.methods.swapExactTokensForTokens(
        tradeAmount,
        web3.utils.toBN(amountOutMin.toString()),
        path,
        to,
        deadline
    )

    const [gasPrice, gasCost] = await Promise.all([
        web3.eth.getGasPrice(),
        tx.estimateGas({from: admin}),
    ]);


    console.log(`gasPrice: ${gasPrice}`)
    console.log(`gasCost: ${gasCost}`)
}

init()

The price calculation for the swap returns correct prices. However when I try to calculate the transaction costs the following error is thrown: Error: Returned error: gas required exceeds allowance (44038122) or always failing transaction

Any help would be greatly appreciated, please let me know if more of my code should be clarified!

3 Answers

In turns out that with web3 it is not possible to interact with the Pancakeswap contract. I found a solution using ethers.js. Following code worked for me to execute a swap on Pancake on mainnet. Before executing the first transaction Pancakeswap needs to be allowed, this code is commented out. I had to play around with the gasprice and gasLimit a bit to make it work.

require("dotenv").config()
const ethers = require('ethers')
const {ChainId, Token, TokenAmount, Fetcher, Pair, Route, Trade, TradeType, Percent} = 
require('@pancakeswap-libs/sdk');
const Web3 = require('web3');
const web3 = new Web3('wss://apis.ankr.com/wss/c40792ffe3514537be9fb4109b32d257/946dd909d324e5a6caa2b72ba75c5799/binance/full/main');
const {JsonRpcProvider} = require("@ethersproject/providers");
const provider = new JsonRpcProvider('https://bsc-dataseed1.binance.org/');
const { address: admin } = web3.eth.accounts.wallet.add(process.env.PRIVATE_KEY)

const addresses = {
    WBNB: '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c',
    BUSD: '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56',
    PANCAKE_ROUTER: '0x05fF2B0DB69458A0750badebc4f9e13aDd608C7F'
}

const ONE_ETH_IN_WEI = web3.utils.toBN(web3.utils.toWei('1'))
const tradeAmount = ONE_ETH_IN_WEI.div(web3.utils.toBN('1000'))

const init = async () => {

const [WBNB, BUSD] = await Promise.all(
    [addresses.WBNB, addresses.BUSD].map(tokenAddress => (
        new Token(
            ChainId.MAINNET,
            tokenAddress,
            18
        )
    )));

const pair = await Fetcher.fetchPairData(WBNB, BUSD, provider)
const route = await new Route([pair], WBNB)
const trade = await new Trade(route, new TokenAmount(WBNB, tradeAmount), TradeType.EXACT_INPUT)

const slippageTolerance = new Percent('50', '10000')

// create transaction parameters
const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw
const path = [WBNB.address, BUSD.address]
const to = admin
const deadline = Math.floor(Date.now() / 1000) + 60 * 20

// Create signer
const wallet = new ethers.Wallet(
    Buffer.from(
    process.env.PRIVATE_KEY, // paste your private key from metamask here
    "hex"
    )
)
const signer = wallet.connect(provider)

// Create Pancakeswap ethers Contract
const pancakeswap = new ethers.Contract(
    addresses.PANCAKE_ROUTER,
    ['function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)'],
    signer
)

// Allow Pancakeswap
// let abi = ["function approve(address _spender, uint256 _value) public returns (bool success)"]
// let contract = new ethers.Contract(WBNB.address, abi, signer)
// await contract.approve(addresses.PANCAKE_ROUTER, ethers.utils.parseUnits('1000.0', 18), {gasLimit: 100000, gasPrice: 5e9})

// Execute transaction
const tx = await pancakeswap.swapExactTokensForTokens(
    ethers.utils.parseUnits('0.001', 18),
    ethers.utils.parseUnits(web3.utils.fromWei(amountOutMin.toString()), 18),
    path,
    to,
    deadline,
    { gasLimit: ethers.utils.hexlify(200000), gasPrice: ethers.utils.parseUnits("10", "gwei") }
)

console.log(`Tx-hash: ${tx.hash}`)

const receipt = await tx.wait();

console.log(`Tx was mined in block: ${receipt.blockNumber}`)
}

init()

You can check this working example that buys a token from pancakeswap.finance: https://github.com/religion-counter/onlyone/blob/main/helper-scripts/buy-onlyone-pancakeswap.js

// Helper script that buys ONLYONE token from a specified address specified on text file SPECIFY_ACCOUNTS_YOU_WANT_TO_BUY_FOR_HERE.json
// The amount is specified with 'originalAmountToBuyWith' variable in the source
// The JSON file should have an array with objects with 'address' field and 'privateKey' field.
// Buys ONLYONE for ${bnbAmount} BNB from pancakeswap for address ${targetAccounts[targetIndex].address}
// targetIndex is passed as an argument: process.argv.splice(2)[0]

var fs = require('fs')
var Tx = require('ethereumjs-tx').Transaction;
var Web3 = require('web3')
var Common = require('ethereumjs-common').default;

var web3 = new Web3(new Web3.providers.HttpProvider('https://bsc-dataseed.binance.org/'))
var BSC_FORK = Common.forCustomChain(
    'mainnet',
    {
        name: 'Binance Smart Chain Mainnet',
        networkId: 56,
        chainId: 56,
        url: 'https://bsc-dataseed.binance.org/'
    },
    'istanbul',
);

// SPECIFY_THE_AMOUNT_OF_BNB_YOU_WANT_TO_BUY_FOR_HERE
var originalAmountToBuyWith = '0.007' + Math.random().toString().slice(2,7);
var bnbAmount = web3.utils.toWei(originalAmountToBuyWith, 'ether');

var targetAccounts = JSON.parse(fs.readFileSync('SPECIFY_ACCOUNTS_YOU_WANT_TO_BUY_FOR_HERE.json', 'utf-8'));

var targetIndex = Number(process.argv.splice(2)[0]);
var targetAccount = targetAccounts[targetIndex];

console.log(`Buying ONLYONE for ${originalAmountToBuyWith} BNB from pancakeswap for address ${targetAccount.address}`);

var res = buyOnlyone(targetAccounts[targetIndex], bnbAmount);
console.log(res);

async function buyOnlyone(targetAccount, amount) {

    var amountToBuyWith = web3.utils.toHex(amount);
    var privateKey = Buffer.from(targetAccount.privateKey.slice(2), 'hex')  ;
    var abiArray = JSON.parse(JSON.parse(fs.readFileSync('onlyone-abi.json','utf-8')));
    var tokenAddress = '0xb899db682e6d6164d885ff67c1e676141deaaa40'; // ONLYONE contract address
    var WBNBAddress = '0xbb4cdb9cbd36b01bd1cbaebf2de08d9173bc095c'; // WBNB token address

    // var onlyOneWbnbCakePairAddress = '0xd22fa770dad9520924217b51bf7433c4a26067c2';
    // var pairAbi = JSON.parse(fs.readFileSync('cake-pair-onlyone-bnb-abi.json', 'utf-8'));
    // var pairContract = new web3.eth.Contract(pairAbi, onlyOneWbnbCakePairAddress/*, {from: targetAccount.address}*/);
    var amountOutMin = '100' + Math.random().toString().slice(2,6);
    var pancakeSwapRouterAddress = '0x10ed43c718714eb63d5aa57b78b54704e256024e';

    var routerAbi = JSON.parse(fs.readFileSync('pancake-router-abi.json', 'utf-8'));
    var contract = new web3.eth.Contract(routerAbi, pancakeSwapRouterAddress, {from: targetAccount.address});
    var data = contract.methods.swapExactETHForTokens(
        web3.utils.toHex(amountOutMin),
        [WBNBAddress,
         tokenAddress],
        targetAccount.address,
        web3.utils.toHex(Math.round(Date.now()/1000)+60*20),
    );

    var count = await web3.eth.getTransactionCount(targetAccount.address);
    var rawTransaction = {
        "from":targetAccount.address,
        "gasPrice":web3.utils.toHex(5000000000),
        "gasLimit":web3.utils.toHex(290000),
        "to":pancakeSwapRouterAddress,
        "value":web3.utils.toHex(amountToBuyWith),
        "data":data.encodeABI(),
        "nonce":web3.utils.toHex(count)
    };

    var transaction = new Tx(rawTransaction, { 'common': BSC_FORK });
    transaction.sign(privateKey);

    var result = await web3.eth.sendSignedTransaction('0x' + transaction.serialize().toString('hex'));
    console.log(result)
    return result;
}

You can also contribute to the repository if you are interested.

I am a total beginner but have the same goal as you. I'm trying to get your code to work.

  1. I used the following Code to create the signer. I used my 'mnemonic' instead of the private key:

    const wallet = ethers.Wallet.fromMnemonic(mnemonic);

  2. I noticed that pancake uses the swapExactETHForTokens() function instead of swapExactTokensForTokens() for the transactions. Does that make any difference?

  3. I am not quite sure whether the Output and Input Amount is calculated correctly... I don't quite understand it yet, but will try to go through it as soon as possible.

  4. jklepatch has written a code which contains trading via pancakeswap V2. However, the trade does not work if i execute the code because the AmountOut generats an error (codeline 78)...

In the meantime, have you been able to modify your code or make it work? If it is helpful I can also post my version of your code here.

Related