Build a Front Running Bot with Ethers.js: Step-by-Step Guide

a trading robot, making money online

Overview

Front running bots are bots that have the ability to anticipate a big transaction that is going to change the token price, sometimes by up to 100%. So how can we create a front running bot with Ethers.js? In this article, we’ll walk you through the process step-by-step.

Disclaimer

The guide on how to create a front running bot from scratch provided in this article is intended for educational purposes only. It is a proof of concept and does not necessarily reflect the performance or functionality of a professional front running bot. If you are looking for a reliable and professional front running bot, please visit Fastlybot.

Front Running in Crypto

In traditional markets like stocks, front running is trading by a broker (or insider) who has privileged knowledge of a future transaction that is about to affect its price substantially. In the crypto world, it’s a bit different but follows the same idea: you need knowledge about a transaction that’s about to happen. Once you know about a big order that is going to hit the market, you have almost everything you need to front run.

For example, if I knew someone was going to buy 100 BNB of some token, I could get in front of that order and buy shares of that token first. Then, I wait for the big order I knew was coming to happen and sell my shares right after. So, I might buy at $1, and after the big order, the price goes up to $1.5. I sell immediately after that big order and take a profit of $0.5—which is 50% of my investment in just 1 second!

Enough with the theory. You came here to build your own front running bot, right? Let’s get started.

Tools You’ll Need

The core of this bot relies on listening for pending transactions on the blockchain—in this case, the Binance Smart Chain (BSC). To make this work, you’ll need two things:

  • A wallet (we’ll use a private key from a wallet like MetaMask—keep it safe!).
  • A node connected to the BSC network.

For the node, you’ll need to choose a reliable blockchain node provider that offers WebSocket endpoints for BSC. There are many options out there; pick one that suits your needs and get the WebSocket endpoint (it’ll look something like wss://your-node-provider-endpoint).

Project Setup

First, let’s set up a directory for the bot’s code to live in. Open a terminal and run these commands (if you don’t have npm and Node.js installed, download and install them from here):

mkdir FrontRunningBot

cd FrontRunningBot

npm init -y

Installing Dependencies

Before we start coding, we need to add some libraries: express for the server and ethers to interact with the blockchain using JavaScript. Run this command in the root of your project directory:

npm i express ethers

Coding the Front Running Bot

Create a file called server.js and open it with your text editor of choice. Then add the following code:

const express = require("express");

const http = require('http');

const ethers = require("ethers");

const app = express();

const PORT = process.env.PORT || 3888;


const WSS_ENDPOINT = "wss://your-node-provider-endpoint"; // Replace with your node provider's WebSocket endpoint

const SECRET_KEY = "your-private-key"; // Replace with your private key, keep it secure!


const PAN_ROUTER_ADDRESS = "0x10ED43C718714eb63d5aA57B78B54704E256024E";

const BNB_CONTRACT = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";


const provider = new ethers.providers.WebSocketProvider(WSS_ENDPOINT);

const wallet = new ethers.Wallet(SECRET_KEY, provider);


const iface = new ethers.utils.Interface([

'function swapExactETHForTokens(uint256 amountOutMin, address[] path, address to, uint256 deadline)',

'function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)',

'function swapExactETHForTokensSupportingFeeOnTransferTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)'

]);


provider.on("pending", (tx) => {

provider.getTransaction(tx).then(async (transaction) => {

if (transaction && transaction.to === PAN_ROUTER_ADDRESS) {

const value = ethers.utils.formatEther(transaction.value);

const gasPrice = ethers.utils.formatUnits(transaction.gasPrice, 'gwei');

const gasLimit = transaction.gasLimit.toString();


if (parseFloat(value) > 10) {

console.log("value:", value);

console.log("gasPrice:", gasPrice);

console.log("gasLimit:", gasLimit);

console.log("from:", transaction.from);


let result = [];

try {

result = iface.decodeFunctionData('swapExactETHForTokens', transaction.data);

} catch (error) {

try {

result = iface.decodeFunctionData('swapExactETHForTokensSupportingFeeOnTransferTokens', transaction.data);

} catch (error) {

try {

result = iface.decodeFunctionData('swapETHForExactTokens', transaction.data);

} catch (error) {

console.log("final err:", transaction);

}

}

}


if (result.length > 0) {

const tokenAddress = result[1][1];

console.log("tokenAddress:", tokenAddress);

console.log("result:", result);


// Front running logic

const targetGasPrice = transaction.gasPrice;

const buyGasPrice = targetGasPrice.add(ethers.utils.parseUnits('1', 'gwei'));

const sellGasPrice = targetGasPrice.sub(ethers.utils.parseUnits('1', 'gwei'));


const gasLimit = transaction.gasLimit;


// Buy the token

await buyToken(wallet, tokenAddress, gasLimit, buyGasPrice);


// Wait for the target transaction to be mined, then sell

const targetTxHash = transaction.hash;

const checkInterval = setInterval(async () => {

const receipt = await provider.getTransactionReceipt(targetTxHash);

if (receipt && receipt.blockNumber) {

clearInterval(checkInterval);

await sellToken(wallet, tokenAddress, gasLimit, sellGasPrice);

}

}, 1000);

}

}

}

});

});


// Error handling for WebSocket

provider._websocket.on("error", async (ep) => {

console.log(`Unable to connect to ${ep.subdomain} retrying in 3s...`);

setTimeout(() => {

provider = new ethers.providers.WebSocketProvider(WSS_ENDPOINT);

}, 3000);

});


provider._websocket.on("close", async (code) => {

console.log(`Connection lost with code ${code}! Attempting reconnect in 3s...`);

provider._websocket.terminate();

setTimeout(() => {

provider = new ethers.providers.WebSocketProvider(WSS_ENDPOINT);

}, 3000);

});


// Express server

const server = http.createServer(app);

server.listen(PORT, () => {

console.log(`Listening on port ${PORT}`);

});


// Helper functions

function calculateGasPrice(action, amount) {

if (action === "buy") {

return amount.add(ethers.utils.parseUnits('1', 'gwei'));

} else {

return amount.sub(ethers.utils.parseUnits('1', 'gwei'));

}

}


function router(account) {

return new ethers.Contract(

PAN_ROUTER_ADDRESS,

[

'function getAmountsOut(uint amountIn, address[] memory path) public view returns (uint[] memory amounts)',

'function swapExactTokensForTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)',

'function swapExactTokensForTokensSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external returns (uint[] memory amounts)',

'function swapExactETHForTokensSupportingFeeOnTransferTokens(uint amountOutMin, address[] calldata path, address to, uint deadline) external payable',

'function swapExactTokensForETH (uint amountOutMin, address[] calldata path, address to, uint deadline) external payable'

],

account

);

}


function erc20(account, tokenAddress) {

return new ethers.Contract(

tokenAddress,

[

{

"constant": true,

"inputs": [{ "name": "_owner", "type": "address" }],

"name": "balanceOf",

"outputs": [{ "name": "balance", "type": "uint256" }],

"payable": false,

"type": "function"

},

{ "inputs": [], "name": "decimals", "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], "stateMutability": "view", "type": "function" },

{ "inputs": [], "name": "symbol", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" },

{

"constant": false,

"inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }],

"name": "approve",

"outputs": [{ "name": "", "type": "bool" }],

"payable": false,

"stateMutability": "nonpayable",

"type": "function"

},

],

account

);

}


const buyToken = async (account, tokenContract, gasLimit, gasPrice) => {

const buyAmount = 0.1; // Amount to buy in BNB

const slippage = 0; // Slippage tolerance

let amountOutMin = 0;

const amountIn = ethers.utils.parseUnits(buyAmount.toString(), 'ether');


if (parseInt(slippage) !== 0) {

const amounts = await router(account).getAmountsOut(amountIn, [BNB_CONTRACT, tokenContract]);

amountOutMin = amounts[1].sub(amounts[1].div(100).mul(slippage));

}


const tx = await router(account).swapExactETHForTokensSupportingFeeOnTransferTokens(

amountOutMin,

[BNB_CONTRACT, tokenContract],

account.address,

Date.now() + 1000 * 60 * 10,

{

value: amountIn,

gasLimit: gasLimit,

gasPrice: gasPrice,

}

);


const receipt = await tx.wait();

if (receipt && receipt.blockNumber && receipt.status === 1) {

console.log(`Buy transaction mined: https://bscscan.com/tx/${receipt.transactionHash}`);

} else {

console.log(`Buy transaction failed or not mined: https://bscscan.com/tx/${receipt.transactionHash}`);

}

};


const sellToken = async (account, tokenContract, gasLimit, gasPrice, value = 99) => {

const tokenBalance = await erc20(account, tokenContract).balanceOf(account.address);

const amountIn = tokenBalance.mul(value).div(100);

let amountOutMin = 0;


const amounts = await router(account).getAmountsOut(amountIn, [tokenContract, BNB_CONTRACT]);

amountOutMin = amounts[1].sub(amounts[1].mul(slippage).div(100));


const approveTx = await erc20(account, tokenContract).approve(PAN_ROUTER_ADDRESS, amountIn);

await approveTx.wait();


const tx = await router(account).swapExactTokensForETHSupportingFeeOnTransferTokens(

amountIn,

amountOutMin,

[tokenContract, BNB_CONTRACT],

account.address,

Date.now() + 1000 * 60 * 10,

{

gasLimit: gasLimit,

gasPrice: gasPrice,

}

);


const receipt = await tx.wait();

if (receipt && receipt.blockNumber && receipt.status === 1) {

console.log(`Sell transaction mined: https://bscscan.com/tx/${receipt.transactionHash}`);

} else {

console.log(`Sell transaction failed or not mined: https://bscscan.com/tx/${receipt.transactionHash}`);

}

};

What’s Happening in the Code?

  • Node Provider Setup: Replace wss://your-node-provider-endpoint with the WebSocket endpoint from your chosen node provider.
  • Private Key: Replace your-private-key with your wallet’s private key (and keep it secure—don’t share it!).
  • Listening for Transactions: The bot listens for pending transactions on PancakeSwap (using the router address 0x10ED43C718714eb63d5aA57B78B54704E256024E). When it spots a transaction with a value greater than 10 BNB, it jumps into action.
  • Decoding Transactions: We decode the transaction data to find the token being traded.
  • Front Running Logic:
  • For buying, we set a gas price 1 gwei higher than the target transaction to get in front.
  • We buy the token immediately.
  • Then, we wait for the target transaction to be mined (using its hash) and sell afterward with a gas price 1 gwei lower.

Helper Functions

  • calculateGasPrice: Adjusts the gas price for buying (adds 1 gwei) and selling (subtracts 1 gwei).
  • router: Interacts with PancakeSwap’s router contract.
  • erc20: Interacts with the ERC20 token contract (e.g., to check balance or approve spending).
  • buyToken: Buys the token using BNB via PancakeSwap.
  • sellToken: Sells a percentage (default 99%) of the token back to BNB.

Running the Bot

  1. Save the code in server.js.
  2. Open package.json, find the "scripts" section, and add this line:
  3. "start": "node server.js"
  4. So it looks like:
  5. "scripts": {
  6. "test": "echo \"Error: no test specified\" && exit 1",
  7. "start": "node server.js"
  8. },
  9. In your terminal, run:
  10. npm start
  11. Wait for a big transaction to happen, and you’ll see the bot in action!

Final Thoughts

This bot is a basic proof of concept. In the real world, front running is tricky—you need to nail the timing, manage gas prices perfectly, and handle risks like failed transactions or network congestion. Test it thoroughly before putting any real money on the line. I hope you enjoyed this tutorial—see you next time!

You can follow me on Twitter @madoutech.

See all posts