Getting started with DEX in Node.js
My goal is to create a trading bot that will scout decentralized exchanges (DEX) for buying and selling opportunities using AI and make me a millionaire… no, at least a billionaire! It’s a nice thing to dream about but there’s a lot of work ahead to get there since I have only just started. I plan to share my entire journey on Medium, so I invite you, my reader, to come along for a ride and learn together with me!
If we think about functional requirements for such trading bot, I figured that there are two most essential and basic functions that it must perform and that I can kick off my project with.
- Getting a price quote
- Making a trade (buy, sell, swap - depending on the context)
Next we need to choose the DEX to trade on. I chose UniSwap because it is among the largest ones by the amount of liquidity and it provides well maintained tools and documentation for developers.
So, the starting point of our project will be a simple app prototype that gets price quotes for a chosen token pair.
Interacting with a DEX
Because DEX runs on the blockchain, we interact with smart contracts deployed there. To do that we will use the well maintained and capable ethers Node.js library.
Before we can interact with a contract, we need to connect to the blockchain and provide our credentials to be able to sign transactions.
import { ethers } from 'ethers'
// Connect to the BSC mainnet
const provider = ethers.getDefaultProvider();
// Set your private key, eg. by reading it from an environment variable
const { PRIVATE_KEY } = process.env
// Sign the transaction with the contract owner's private key
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
const walletAddress = await wallet.getAddress()
const walletBalance = await wallet.getBalance()
console.log(walletAddress + ':', walletBalance.toBigInt())
Voilà! We connected to the blockchain and interacted with it by reading our wallet balance. Getting your private key depends on your wallet, but if you are using Metamask, you can get it by opening the Account options menu and selecting Account details.
In the next menu choose Export private key and copy it. Be very careful with keeping it safe. I put mine in an environment variable and read it with
const { PRIVATE_KEY } = process.env
But how do we get a price quote for a token pair? We need to interact with a DEX contract deployed on the blockchain. To interact with a contract using ethers library, we need
- Contract address
- Contract ABI
Contract ABI is like a schema for the contract and it can be described in JSON format. Contract address is the actual blockchain address where the contract is deployed.
UniSwap actively develops its platform and the latest version of their protocol is V3. UniSwap V3 documentation has very good description of their contracts and there’s also a list of deployment addresses where we can find the first contract we need. It is UniswapV3Factory and its address is 0x1F98431c8aD98523631AE4a59f267346ea31F984.
In Etherscan at the bottom of the page there is a Contract tab where we can find the contract’s source code and the ABI in JSON format.
The ABI is in the bottom of the page. We will need it in our project, so let’s copy and paste it into a JSON file.
We’ll save it in a contracts folder as UniswapV3Factory.json and import it.
import uniswapV3FactoryAbi from './contracts/UniswapV3Factory.json'
Now we can create an ethers contract instance using this ABI and the wallet we created earlier:
const uniswapV3FactoryAddress = '0x1F98431c8aD98523631AE4a59f267346ea31F984'
const factoryContract = new ethers.Contract(uniswapV3FactoryAddress, uniswapV3FactoryAbi, wallet)
We can not get the price quote directly from this contract though. In UniSwap V3 protocol we need to use this contract to get us the contract address of the pool that provides liquidity for a pair of tokens. That contract, in turn, has a method that will give us the data needed to calculate the token price.
So let’s get the pool address of two tokens using the contract’s getPool method:
function getPool(
address tokenA,
address tokenB,
uint24 fee
) external view returns (address pool)
We’ll use USDC and WETH tokens and 3000 as the fee, even though I’m not sure what the fee input does. Please, tell me in the comments if you do!
const tokenAAddress = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' // USDC
const tokenBAddress = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' // WETH
const txInputs = [
tokenAAddress,
tokenBAddress,
3000
]
const poolAddress = await factoryContract.getPool(...txInputs)
console.log('Pool address:', poolAddress)
This should return 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8 which is the address of a UniswapV3Pool contract for our pair. We will need its ABI as well, so let’s copy it from Etherscan and paste into the file called UniswapV3Pool.json in the contracts folder. Then we will import it and interact with it to get our price data.
import uniswapV3PoolAbi from './contracts/UniswapV3Pool.json'
...
const poolContract = new ethers.Contract(poolAddress, uniswapV3PoolAbi, wallet)
const slot0 = await poolContract.slot0()
Uniswap documentation describes the slot0 function in the following way:
The 0th storage slot in the pool stores many values, and is exposed as a single method to save gas when accessed externally.
But what’s important to us is that it contains the tick value from which we can calculate the price. Ticks are a way to express a range of prices that the tokens can be between. Ticks are integers so some math needs to be done to calculate the price value. There’s A Technical Analysis of UniSwap V3 article that does a great job of explaining ticks and other features of the UniSwap V3 protocol. Meanwhile I will just give you the algorithm that gets the job done:
const { tick } = slot0
const tokenBPrice = 1 / ((1.0001 ** tick) * (10 ** -12))
console.log('Tick:', tick, 'Price:', tokenBPrice)
And we’re done! Our output at the time of writing:
0x28C6c06298d514Db089934071355E5743bf21d60: 3561404823107000n
Pool address: 0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8
Tick: 203989 Price: 1384.564588886404
I hope you enjoyed the article and by the time you run your app, the price will be higher on its way to the moon :) Join me in the next article where I will attempt to make a swap!
You can find all the code covered in this story in my DEX-BOT GitHub repo.