Triangular arbitrage on Uniswap v3

IntermediateMay 07, 2024
Triangular arbitrage serves as a strategy in crypto exchange trading, leveraging variations in exchange rates within a single market or across multiple markets.
Triangular arbitrage on Uniswap v3

Implemented with multihop swaps

Triangular arbitrage serves as a strategy in crypto exchange trading, leveraging variations in exchange rates within a single market or across multiple markets. This method comprises three sequential trades: swapping an initial cryptocurrency for a second, the second for a third, and ultimately, the third crypto back to the initial one, all with the aim of generating profit. Hence, the term “triangular” encapsulates its three-step process.

Image generated by AI

How it works?

On DEX, opportunities for triangular arbitrage are usually caused by liquidity differences across multiple pools. They are usually short-lived, lasting just a few seconds or even less, as the exchange swiftly adjusts any pricing discrepancies. Consequently, automated trading algorithms equipped to swiftly execute trades are employed to capitalize on these fleeting differences. To help with understanding the concept, here comes an example:

Above triangular transcation starts from 01 — buying 1 wBTC with $60,000 USDC, followed by 02 — buying 16 WETH with 1 wBTC and ended with 03 — selling 16 WETH for $66,000 USDC. At the end of the journey, we would have gotten $6,000 USDC as the profit.

Implementing with Mutihop Swaps on Uniswap v3

There are two styles of multi-hop swapping available on Uniswap v3: Exact Input and Exact Output. As their names suggest, first one expects token with exact amout as the input of the swapping, and at the end of it, a token with amout will output at exchange rates; The latter one expects specified exact amout as the output, only sufficient amount of token as input can fulfill the swapping at the exchange rates.

With business nature of trangular arbitrage, we would like to take a token with exact amout as the input, swap it into another cypto and then swap again back to the original token to take profit, as we wish.

address constant SWAP_ROUTER_02 = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;

address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;

address constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;

contract MultiHopSwap {

 using SafeERC20 for IERC20;

ISwapRouter02 private constant ROUTER = ISwapRouter02(SWAP_ROUTER_02);

uint256 private constant MAX_INT =

   115792089237316195423570985008687907853269984665640564039457584007913129639935;

function swapExactInputMultiHop(uint256 amountIn) external {

   IERC20(USDC).safeApprove(address(ROUTER), MAX_INT);

   IERC20(WETH).safeApprove(address(ROUTER), MAX_INT);

   IERC20(DAI).safeApprove(address(ROUTER), MAX_INT);

   bytes memory path =

       abi.encodePacked(USDC, uint24(3000), WETH, uint24(3000), DAI, uint24(3000), USDC) ;

   ISwapRouter02.ExactInputParams memory params = ISwapRouter02

       .ExactInputParams({

       path: path,

       recipient: address(this),

       amountIn: amountIn,

       amountOutMinimum: 1

   });

   ROUTER.exactInput(params);

 }

}

Routers play a crucial role in facilitating liquidity provision. Since they are stateless and do not hold token balances, routers can be replaced safely. For this reason, routers have release numbers, starting at 01. In our implementation, we use Router02 at 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 on mainnet.

SafeERC20 is a protective layer built around ERC20 transactions, ensuring secure interaction with ERC20 tokens within our contract. Unlike regular ERC20 functions, SafeERC20 enhances safety by verifying the boolean return values of ERC20 operations. If any operation fails, the transaction is reverted, minimizing risks. Additionally, SafeERC20 accommodates non-standard ERC20 tokens lacking boolean return values, providing flexibility and robustness in token management. By approving maxium amount, we allow Router02 to transfer tokens on our behalf. Without doing so, you would expect to see a STF error message in which STF means execution is reverted by require assertion in TransferHelper.safeTransferFrom function.

Next, we will look at how a triangular path is defined:

bytes memory path = abi.encodePacked(USDC, uint24(3000),

                                WETH, uint24(3000),

                                DAI,  uint24(3000),

                                USDC) ;

Through abi.encodePacked, Solidity tightly packs multiple values without adding any padding. It concatenates the raw binary data of each parameter. Not difficult to understand that the parameters sequentialized the swaps with fees between the crypto pairs. The path starts from USDC and stops at USDC expecting a profit. Then it is wrapped by ExactInputParams with other mandator parameters and fed into router for multi-hop swapping.

Testing

We use the same technique by forking mainnet with impersonation. Once 10 USDC is verified being credited to the contract, multi-hop swaps can be triggered as below:

it("performs multi hop swap", async () => {

balance = await swap.tokenBalance(USDC);

console.log(`Current balance of USDC = ${balance}`);

console.log(`Swapping ${initialFundingHuman} USDC`);

const tx = await swap.swapExactInputMultiHop(ethers.parseUnits(initialFundingHuman, DECIMALS));

receipt = await tx.wait();

balance = await swap.tokenBalance(USDC);

console.log(`Current balance of USDC = ${balance}`);

expect(balance).not.equal(0);

});

The test result should look like below:

USDC balance of whale: 170961218210457n

Impersonation Started.

Impersonation completed.

Current balance of USDC = 100000000

Swapping 100 USDC

Current balance of USDC = 91677417

After jumping through the hoops, we lost money — Obviously the path with spot rates were not in favor with us, but you get the idea on how triangular arbitrage should be done using multi-hop swaps on Uniswap v3.

Flash loan funded triangular arbitrage

Haven’t I told the most powerful funding source in DeFi ecosystem is Flash loan? It won’t need much of creativity for you to construct a Flash loan funded triangular arbitrage trading strategy using both Flash loan and multihop swaps I taught about. A combined logic can be explained by the updated sequence diagram as below:

Sequnce diagram for Flash loan funded triangular arbitrage on Uniswap v3 (Ignored some operations for simplicity)

Check out my source code for both Flash loan and Multihop swaps implemented on Uniswap v3 — https://medium.com/cryptocurrency-scripts/flash-loan-on-uniswap-v3-84bca2bfe255, digest the sequence diagram and do you own homework to get the combined the smart contracts done.

Considerations of profitability

First thing we want to looked at is the path that comprises of the sequence of the 3 trades: To be profitable, they must be the right 3 pairs of crypto at the right rates. In order to find all this rightness, you need to develop a program that permutates tradable pairs in the pattern of triangle arbitrage and simulate the swaps to check the profitability. Retrieving rates from blockchain can be slow and it will further slow down the process if there are too many paths waiting to be verified for profitability. You may want to narrow down the list of triangular paths by calculating Profit & Loss based on the surface prices provided by a DEX’s GraphQL pricing endpoints if any (here comes Uniswap v3’s), as GraphQL APIs are much faster than blockchain to provide quotation data. Once the paths are shortlisted, run them with quotes retrieved from the chain for more accurate Profit & Loss calculation.

Boosting your investment with Flash loan can further extend the profit — borrowing tokens at low interest and invest them with profitable strategy will always be a good idea. Theoretically, as long as the gross profit is good to cover the Flash loan and swap fees, the triangular arbitrage strategy will be considered profitable. One critical trick to guard your profit and mitigate overall trading risk is to have a logic in your trading contract to fail the flash loan transcation if the gross profitability check fails, for the reason when the trascation fails, all operations will roll back and you don’t need to bear the loss and even the fees for the transcation. This piece of logic will work as catch-all gatekeeper to prevent slippage or exchange rate movement that is not in favor with us.

Having said that, regardless of success or failure of the transcation, gas fees is something you can never get away from and could be the main cause you lose money from triangular arbitrage. Always assess the gas fees for the transcation of your strategy and take that into net profitability calculation. Please do refer to the gas fee estimation test cases in my source code.

Disclaimer:

  1. This article is reprinted from [Cryptocurrency Scripts], All copyrights belong to the original author [Aaron Li]. If there are objections to this reprint, please contact the Gate Learn team, and they will handle it promptly.
  2. Liability Disclaimer: The views and opinions expressed in this article are solely those of the author and do not constitute any investment advice.
  3. Translations of the article into other languages are done by the Gate Learn team. Unless mentioned, copying, distributing, or plagiarizing the translated articles is prohibited.
Start Now
Sign up and get a
$100
Voucher!
Create Account