In this tutorial I’ll be explaining how I built Uniswap v3 trading bot in preparation for arbitrage opportunities. We will be looking at why Uniswap v3 is important and how concentrated liquidity pools provide new features such as range orders. I’ll then show how I built and tested the trading bot prior to Uniswap v3 official launch in May 2021.
- Uniswap v3 Updates
- How Range Orders Work [Video]
- Uniswap v2 Trading Bot Code
- Experimental V3 Trading Bot
- Uniswap v3 Opportunities
- Conclusion
Uniswap v3 Updates
When Uniswap v3 was initially announced I read through the technical details and was underwhelmed by the projects development. Over the next few weeks the more I read and considered the implications of these changes the more I think this will change the game for the crypto industry and DeFi in particular.
Uniswap’s v3 update will introduce an entirely new set of contracts and it will be up to users to migrate their funds and trading activity across. There are a number of improvements, the most important of which are:
- Concentrated liquidity improves capital efficiency and enables limit type orders
- Variable fees – Stablecoin @ 0.05%, Standard 0.3%, Microcap 1%
- Oracle improvements to provide TWAP and Liquidity accumulator data
- Liquidity providers get a position specific NFT rather than a ERC20 LP token
- Reduced fees via layer two scaling on optimism (expected Julyish)
Synthentix recently tested optimism staking and got block times down below 0.3 seconds and transaction costs 143x lower.
How Range Orders Work
In Uniswap v2 a user would deposit 100 DAI and 100 USDT to a liquidity pool and only a tiny fraction of that capital would be actively traded. If the USDT price of DAI moves to 1.01 then the user will end up with a tiny amount more USDT and less DAI along with trading fees. An algorithm would spread the capital efficiency across a price curve from zero to infinity.
In Uniswap v3 a liquidity provider can provide a range at which they want to concentrate their liquidity. A user migh concentrate liquidity between 0.99 and 1.01 which would enable much more capital exposure and earn significantly more fees. However if the USDT price of DAI moves to 1.02 then the user will end up with $200~ USDT and no DAI until the price returns to the specified range and becomes active again.
This increases capital efficiency up to 4000% and reduces slippage for traders to a point where it could be more efficient than order books on centralised exchanges. I believe it will also make 3rd party liquidty management tools an almost essential requirement for liquidity providers. This could be a big opportunity for yield farming platforms like Yearn Finance if they develop liquidity range management and risk aggregators.
The key here is if price moves outside of the range then they essentially end up with 100% of the token that dropped in value. This will lead to complete impermanent loss until the price moves back into the range.
Note: impermanent loss is arguably the worse descriptor in blockchain terminology. It was originally used to describe fluctuations in stablecoins which will for the most part return to the pegged 1 USD. However when the price of an asset pair moves and does not come back which is often the case when trading altcoins the users liquidity pool gets more weighted towards the token that goes down in price. This “loss” may be greater than the fees gained in trending markets and is very much permanent.
The concept of concentrated liquidity opens up the opportunity for Uniswap v3 users to execute range orders. A user can provide concentrated liquidity within a tight range to swap tokens at a set price. This creates a decentralised orderbook of sorts which opens up a world of new opportunities for DeFi trading. I think there’s a chance we will see a whole new wave of speculation on “Uniswap Gems” this summer if the optimism layer two solution gets rolled out while the bull market is still in full swing.
Uniswap v2 Trading Bot Code
This is the code to execute a swap on the existing Uniswap v2 liquidity pools
const Web3 = require('web3');
const routerABI = require('./uniswapABI.json');
const credentials = require('./credentials.json');
const web3 = new Web3(`https://ropsten.infura.io/v3/${credentials.infuraKey}`);
const privateKey = credentials.privateKey;
const activeAccount = web3.eth.accounts.privateKeyToAccount(privateKey);
const routerAddress = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'; // Uniswap
const fromToken = `0xc778417e063141139fce010982780140aa0cd5ab`; // Ropsten WETH
const toToken = `0xad6d458402f60fd3bd25163575031acdce07538d`; // Ropsten DAI
const routerContract = new web3.eth.Contract(routerABI, routerAddress);
const expiryDate = Math.floor(Date.now() / 1000) + 900;
(async () => {
const qty = web3.utils.toBN(web3.utils.toWei('0.01'));
const quote = await routerContract.methods.quote(qty,fromToken,toToken).call();
console.log(`quote`,quote);
// approve weth spending manually at https://app.uniswap.org
let tx_builder = routerContract.methods.swapExactTokensForTokens(qty,0,[fromToken, toToken],activeAccount.address,expiryDate);
let encoded_tx = tx_builder.encodeABI();
let transactionObject = {
gas: 238989,
data: encoded_tx,
from: activeAccount.address,
to: routerAddress
};
web3.eth.accounts.signTransaction(transactionObject, activeAccount.privateKey, (error, signedTx) => {
if (error) {
console.log(error);
} else {
web3.eth.sendSignedTransaction(signedTx.rawTransaction).on('receipt', (receipt) => {
console.log(receipt);
});
}
});
})();
Experimenting Pre-Release With A Uniswap V3 Trading Bot
The contracts were deployed to testnet on the 21st April and are still subject to change. This code is experimental and for demonstration purposes only.
I’ve been experimenting with it since then and there’s some code here if anyone is interested: https://github.com/jamesbachini/Uniswap-V3-Experiments
The first thing I had to do was try to create a liquidity pool. I think this was the first ever liquidity pool created for Uniswap v3 on the Ethereum testnet, a claim to fame! 🎉
const fromToken = `0xc778417e063141139fce010982780140aa0cd5ab`; // Ropsten WETH
const toToken = `0xad6d458402f60fd3bd25163575031acdce07538d`; // Ropsten DAI
let tx_builder = factoryContract.methods.createPool(fromToken,toToken,500);
I then had to approve spending of tokens by the position manager contract.
const approveQty = 1;
const toContract = new web3.eth.Contract(erc20ABI, toTokenAddress);
let tx_builder = toContract.methods.approve(contractAddress,approveQty);
Range orders I believe will be executed via the mint function using tickLower and tickUpper to specify the range in which tokens will be swapped. Not trading will take place until price enters the range at which point it will swap 100% of tokens upon exiting the range in a method similar to impermanent loss. One caveat is if that if price re-enters the range before the trader can remove the liquidity it will start trading again.
The code for execution is going to look something like this (untested):
let tx_builder = poolContract.methods.mint(fromAddress,0.999,1.001,500);
Swaps can be executed using the router functions which work in a similar way to V2. https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/SwapRouter.sol
The testnet contracts were updated around the 23rd and they were unable to deploy the updated contract to Ropsten so I moved my experiments over to Klaven testnet.
At this point there are very few people working on the testnet deployments. I went through the factory addresses for each of the available testnets and it didn’t look like anyone had successfully set up a liquidity pool. There’s a discord channel for v3-integrations where someone mentioned they had it up and running locally but it seems a bit strange that a project with six billion in TVL has such little 3rd party development 10 days before the expected release.
So the v3 trading bot was built using the official documentation and source code as this was all I had to go on.
I used the tick lens contract to get tick data which should provide insights in to trading positions.
const quote = await tickLensContract.methods.getPopulatedTicksInWord(poolAddress,tickBitmapIndex).call();
I then used the swap router to execute the trade.
const params = {
tokenIn: fromTokenAddress,
tokenOut: toTokenAddress,
fee: 3000,
recipient: activeAccount.address ,
deadline: expiryDate,
amountIn: 10000000000, // wei
amountOutMinimum: 0,
sqrtPriceLimitX96: 0,
};
let tx_builder = routerContract.methods.exactInputSingle(params);
The full code for this Uniswap v3 Trading Bot can be found here:
https://github.com/jamesbachini/Uniswap-V3-Experiments/blob/main/uniswap-v3-trader.js
It is untested and will need modification to run on the mainnet Uniswap v3 release which is scheduled for the 5th May 2021.
Update 12th May 2021 – Have made a few changes to the code after the v3 release and can confirm the script at uniswap-v3-trader.js works for basic swaps using the exactInputSingle function.
Uniswap v3 Opportunities
When Uniswap v3 launches there are going to be a lot of new opportunities. As the liquidity pools get moved over from v2 and users start to trade in to them we will see distortions in price and liquidity spreads which can be arbitraged using something like the Uniswap v3 trading bot above.
Some ideas that I think are interesting and worth exploring are:-
- Mean reversion trading against movement away from spot or perpetual future price on centralised exchanges, perhaps hedging with futures
- Triangular arbitrage as liquidity pools fill up, there will likely be short term price discrepancies between assets
- Black holes of low liquidity areas along the price curve allowing for price bumps and jumps
- Trend following strategies for low cap “uniswap gems” once the floods gates are opened by fee reductions on layer 2
- Governance tokens for liquidity management defi protocols, Yearn Finance perhaps
- Short term cash and carry trades selling volatility
Conclusion
As I mentioned I originally found Uniswap v3 to be underwhelming at first glance but am happy to come full circle and admit I got it wrong. The more I look at the code and consider the implications the more bullish I am on Uniswap, Ethereum and a DeFi summer in 2021.
Lower fees are the biggest pain point preventing funds flowing back to Uniswap from competitors and that wont be fixed until the roll out of optimism’s’ layer two in July. Prior to that Uniswap v3 will launch in May and it will kick start a new era of decentralised cryptocurrency trading.
The fact that Coinbase currently has more trading volume than Uniswap shows how early we are. In the future I think we will see more consumer focused apps like Trust Wallet using DeFi services like Uniswap on the back end.
I hope you’ve enjoyed this content. Please share it with anyone that you thing might find it useful or interesting.