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

3

12

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!

Mediator answered 30/4, 2021 at 22:20 Comment(1)
Did you ever use this code to interact with the BSC? Or is it only for the Etherum blockchain? I wonder if something similar can be done with the same library in Python for automated trading.Lignite
M
11

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()
Mediator answered 6/5, 2021 at 13:26 Comment(3)
The code works with the pancake router v1, but with the new router v2 (0x10ed43c718714eb63d5aa57b78b54704e256024e) it doesn't work. All transactions fail. What would have to be modified to work with v2?Unlearn
There is a way to retrive automatically the path of the token to swap ? As pancakeswap doesRevest
tried with above code but facing error "Fail with error 'TransferHelper: TRANSFER_FROM_FAILED'" and "Warning! Error encountered during contract execution [execution reverted]", does it mean input (0.001 WBNB) is too small?Judon
C
5

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.

Care answered 10/6, 2021 at 12:19 Comment(0)
N
-2

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.

Nelia answered 23/5, 2021 at 21:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.