Keep in mind, if the token you want to swap has no liquidity, a pair with liquidity must be created before you can swap.
Time to get our swap on! We will be swapping ETH for DAI in our code example. Let us start by initiating our project. Open a terminal window and run the following commands to create your project directory:
mkdir swapTokensWithEthers && cd swapTokensWithEthers && mkdir abis && npm init -y
Then, run this command to create the required files:
touch .secret && touch index.js && touch ./abis/router.json
Installed required dependencies:
npm i ethers @uniswap/sdk
Before moving on to the next step, complete these three steps:
- Import your private key into the .secret file (to find out how, take a look at this guide).
- Navigate to the source code for the Router address on Etherscan and copy the ABI into your ./abis/router.json file (the ABI can be found on the Contract tab)
- Retrieve some test ETH on Rinkeby (Make sure you have your wallet configured to the Rinkeby test-net. You can get some free test ETH from this faucet.)
Now we'll cover the code needed to swap tokens programmatically. Open the swapTokensWithEthers directory within an editor of your choice and navigate to the index.js file. Add the following code snippets in order to complete the script.
Importing Dependencies
We will first need to add the necessary dependencies for our project. We'll import the ethers library to interact with the smart contract and the Uniswap SDK to fetch and create our swap structure. The fs and utils libraries will be helpful when reading or modifying data.
using ethers js to swap tokens
const { ethers } = require("ethers")
const UNISWAP = require("@uniswap/sdk")
const fs = require('fs');
const { Token, WETH, Fetcher, Route, Trade, TokenAmount, TradeType, Percent} = require("@uniswap/sdk");
const { getAddress } = require("ethers/lib/utils");
const { ethers } = require("ethers")
const UNISWAP = require("@uniswap/sdk")
const fs = require('fs');
const { Token, WETH, Fetcher, Route, Trade, TokenAmount, TradeType, Percent} = require("@uniswap/sdk");
const { getAddress } = require("ethers/lib/utils");
const { ethers } = require("ethers")
const UNISWAP = require("@uniswap/sdk")
const fs = require('fs');
const { Token, WETH, Fetcher, Route, Trade, TokenAmount, TradeType, Percent} = require("@uniswap/sdk");
const { getAddress } = require("ethers/lib/utils");
Configure Infra Provider
Using our QuickNode HTTP URL and Ethers, we will instantiate a Provider object representing our communication with the blockchain.
using ethers js to swap tokens
const QUICKNODE_HTTP_ENDPOINT = "YOUR_QUICKNODE_HTTP_URL"
let provider = new ethers.providers.getDefaultProvider(QUICKNODE_HTTP_ENDPOINT)
const QUICKNODE_HTTP_ENDPOINT = "YOUR_QUICKNODE_HTTP_URL"
let provider = new ethers.providers.getDefaultProvider(QUICKNODE_HTTP_ENDPOINT)
const QUICKNODE_HTTP_ENDPOINT = "YOUR_QUICKNODE_HTTP_URL"
let provider = new ethers.providers.getDefaultProvider(QUICKNODE_HTTP_ENDPOINT)
Import Wallet
To import the account we will use for swapping tokens, use the fs module to read from the .secret file and then instantiate a Wallet object using Ethers.
using ethers js to swap tokens
const privateKey = fs.readFileSync(".secret").toString().trim()
const wallet = new ethers.Wallet(privateKey, provider)
const privateKey = fs.readFileSync(".secret").toString().trim()
const wallet = new ethers.Wallet(privateKey, provider)
const privateKey = fs.readFileSync(".secret").toString().trim()
const wallet = new ethers.Wallet(privateKey, provider)
Instantiate Router Contract
Ethers has a Contract module we can use to instantiate instances of smart contracts. To create an instance of the Router contract, we will need to input a smart contract address, ABI and a Provider object.
using ethers js to swap tokens
UNISWAP_ROUTER_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
UNISWAP_ROUTER_ABI = fs.readFileSync("./abis/router.json").toString()
UNISWAP_ROUTER_CONTRACT = new ethers.Contract(UNISWAP_ROUTER_ADDRESS, UNISWAP_ROUTER_ABI, provider)
UNISWAP_ROUTER_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
UNISWAP_ROUTER_ABI = fs.readFileSync("./abis/router.json").toString()
UNISWAP_ROUTER_CONTRACT = new ethers.Contract(UNISWAP_ROUTER_ADDRESS, UNISWAP_ROUTER_ABI, provider)
UNISWAP_ROUTER_ADDRESS = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"
UNISWAP_ROUTER_ABI = fs.readFileSync("./abis/router.json").toString()
UNISWAP_ROUTER_CONTRACT = new ethers.Contract(UNISWAP_ROUTER_ADDRESS, UNISWAP_ROUTER_ABI, provider)
Import Token Data
To import token data, we will need to create an instance of the Token class and pass in required inputs such as a chain ID, smart contract address, and decimal places. Note that the contract address and decimal figures can vary depending on the token.
using ethers js to swap tokens
const DAI = new Token(
UNISWAP.ChainId.RINKEBY,
"0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
18
);
const DAI = new Token(
UNISWAP.ChainId.RINKEBY,
"0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
18
);
const DAI = new Token(
UNISWAP.ChainId.RINKEBY,
"0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa",
18
);
We don't need to create a WETH instance since the SDK has a handy module we can use to return the WETH address on the Ethereum main-net, Ropsten, Rinkeby, Görli, or Kovan test-nets.
Swapping ETH for Tokens
Our core logic will lie in a swapTokens() function that will take both tokens as arguments and an amount to be swapped. Note the slippage amount is set to %0.50 by default. Refer to the comments throughout the function to understand what each line achieves.
using ethers js to swap tokens
async function swapTokens(token1, token2, amount, slippage = "50") {
try {
const pair = await Fetcher.fetchPairData(token1, token2, provider); //creating instances of a pair
const route = await new Route([pair], token2); // a fully specified path from input token to output token
let amountIn = ethers.utils.parseEther(amount.toString()); //helper function to convert ETH to Wei
amountIn = amountIn.toString()
const slippageTolerance = new Percent(slippage, "10000"); // 50 bips, or 0.50% - Slippage tolerance
const trade = new Trade( //information necessary to create a swap transaction.
route,
new TokenAmount(token2, amountIn),
TradeType.EXACT_INPUT
);
const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex
const amountOutMinHex = ethers.BigNumber.from(amountOutMin.toString()).toHexString();
const path = [token2.address, token1.address]; //An array of token addresses
const to = wallet.address; // should be a checksummed recipient address
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time
const value = trade.inputAmount.raw; // // needs to be converted to e.g. hex
const valueHex = await ethers.BigNumber.from(value.toString()).toHexString(); //convert to hex string
//Return a copy of transactionRequest, The default implementation calls checkTransaction and resolves to if it is an ENS name, adds gasPrice, nonce, gasLimit and chainId based on the related operations on Signer.
const rawTxn = await UNISWAP_ROUTER_CONTRACT.populateTransaction.swapExactETHForTokens(amountOutMinHex, path, to, deadline, {
value: valueHex
})
//Returns a Promise which resolves to the transaction.
let sendTxn = (await wallet).sendTransaction(rawTxn)
//Resolves to the TransactionReceipt once the transaction has been included in the chain for x confirms blocks.
let reciept = (await sendTxn).wait()
//Logs the information about the transaction it has been mined.
if (reciept) {
console.log(" - Transaction is mined - " + '\n'
+ "Transaction Hash:", (await sendTxn).hash
+ '\n' + "Block Number: "
+ (await reciept).blockNumber + '\n'
+ "Navigate to https://rinkeby.etherscan.io/txn/"
+ (await sendTxn).hash, "to see your transaction")
} else {
console.log("Error submitting transaction")
}
} catch(e) {
console.log(e)
}
}
async function swapTokens(token1, token2, amount, slippage = "50") {
try {
const pair = await Fetcher.fetchPairData(token1, token2, provider); //creating instances of a pair
const route = await new Route([pair], token2); // a fully specified path from input token to output token
let amountIn = ethers.utils.parseEther(amount.toString()); //helper function to convert ETH to Wei
amountIn = amountIn.toString()
const slippageTolerance = new Percent(slippage, "10000"); // 50 bips, or 0.50% - Slippage tolerance
const trade = new Trade( //information necessary to create a swap transaction.
route,
new TokenAmount(token2, amountIn),
TradeType.EXACT_INPUT
);
const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex
const amountOutMinHex = ethers.BigNumber.from(amountOutMin.toString()).toHexString();
const path = [token2.address, token1.address]; //An array of token addresses
const to = wallet.address; // should be a checksummed recipient address
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time
const value = trade.inputAmount.raw; // // needs to be converted to e.g. hex
const valueHex = await ethers.BigNumber.from(value.toString()).toHexString(); //convert to hex string
//Return a copy of transactionRequest, The default implementation calls checkTransaction and resolves to if it is an ENS name, adds gasPrice, nonce, gasLimit and chainId based on the related operations on Signer.
const rawTxn = await UNISWAP_ROUTER_CONTRACT.populateTransaction.swapExactETHForTokens(amountOutMinHex, path, to, deadline, {
value: valueHex
})
//Returns a Promise which resolves to the transaction.
let sendTxn = (await wallet).sendTransaction(rawTxn)
//Resolves to the TransactionReceipt once the transaction has been included in the chain for x confirms blocks.
let reciept = (await sendTxn).wait()
//Logs the information about the transaction it has been mined.
if (reciept) {
console.log(" - Transaction is mined - " + '\n'
+ "Transaction Hash:", (await sendTxn).hash
+ '\n' + "Block Number: "
+ (await reciept).blockNumber + '\n'
+ "Navigate to https://rinkeby.etherscan.io/txn/"
+ (await sendTxn).hash, "to see your transaction")
} else {
console.log("Error submitting transaction")
}
} catch(e) {
console.log(e)
}
}
async function swapTokens(token1, token2, amount, slippage = "50") {
try {
const pair = await Fetcher.fetchPairData(token1, token2, provider); //creating instances of a pair
const route = await new Route([pair], token2); // a fully specified path from input token to output token
let amountIn = ethers.utils.parseEther(amount.toString()); //helper function to convert ETH to Wei
amountIn = amountIn.toString()
const slippageTolerance = new Percent(slippage, "10000"); // 50 bips, or 0.50% - Slippage tolerance
const trade = new Trade( //information necessary to create a swap transaction.
route,
new TokenAmount(token2, amountIn),
TradeType.EXACT_INPUT
);
const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex
const amountOutMinHex = ethers.BigNumber.from(amountOutMin.toString()).toHexString();
const path = [token2.address, token1.address]; //An array of token addresses
const to = wallet.address; // should be a checksummed recipient address
const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time
const value = trade.inputAmount.raw; // // needs to be converted to e.g. hex
const valueHex = await ethers.BigNumber.from(value.toString()).toHexString(); //convert to hex string
//Return a copy of transactionRequest, The default implementation calls checkTransaction and resolves to if it is an ENS name, adds gasPrice, nonce, gasLimit and chainId based on the related operations on Signer.
const rawTxn = await UNISWAP_ROUTER_CONTRACT.populateTransaction.swapExactETHForTokens(amountOutMinHex, path, to, deadline, {
value: valueHex
})
//Returns a Promise which resolves to the transaction.
let sendTxn = (await wallet).sendTransaction(rawTxn)
//Resolves to the TransactionReceipt once the transaction has been included in the chain for x confirms blocks.
let reciept = (await sendTxn).wait()
//Logs the information about the transaction it has been mined.
if (reciept) {
console.log(" - Transaction is mined - " + '\n'
+ "Transaction Hash:", (await sendTxn).hash
+ '\n' + "Block Number: "
+ (await reciept).blockNumber + '\n'
+ "Navigate to https://rinkeby.etherscan.io/txn/"
+ (await sendTxn).hash, "to see your transaction")
} else {
console.log("Error submitting transaction")
}
} catch(e) {
console.log(e)
}
}
Once we compile all the code together in our index.js file , we then need to call the function in order for our swap logic to execute. To do this, add the following line of code to the bottom of your script:
using ethers js to swap tokens
swapTokens(DAI, WETH[DAI.chainId], .02) //first argument = token we want, second = token we have, the amount we want
swapTokens(DAI, WETH[DAI.chainId], .02) //first argument = token we want, second = token we have, the amount we want
swapTokens(DAI, WETH[DAI.chainId], .02) //first argument = token we want, second = token we have, the amount we want
Save the file and run the command node index.js in a terminal window to execute the code. The terminal output should look like this:
We can confirm the transaction was successful by looking up the hash in a block explorer:
If you want to conduct this swap on another chain such as Ethereum or Polygon, you will need to replace the token and router address as they can have different addresses across chains. You will also need access to a Node for each chain you want to swap on. Remember that each blockchain has its own native token used for paying transactions.