Smart Order Router
The Smart Order Router (SOR) is a tool designed to optimize order routing across Balancer pools for the best possible price execution. It can be accessed through the SDK, but it is also available as a standalone package for those who only need the swap routing functionality. This example code will utilize the SDK, but it is easy to understand how to make changes for the standalone SOR package.
The below docs assume the SDK is installed and initialized.
import { BalancerSDK } from '@balancer-labs/sdk'
const balancer = new BalancerSDK({
network: 1, // Mainnet
rpcUrl: 'https://rpc.ankr.com/eth' // rpc endpoint
})
const { swaps } = balancer // Swaps module is abstracting SOR
The general flow for finding a swap route using SOR (Smart Order Router) includes the following steps:
Step 1. Pool data fetching
The SOR requires information about the available pools and their current status, including the prices of tokens and the liquidity of the pools. It is essential to use the SOR based on up-to-date information, as outdated information can lead to incorrect slippage predictions and potentially result in failed swaps.
await swaps.fetchPools()
Step 2. Route proposal
The SOR determines the optimal swap route based on the available pool data and the desired swap, taking into account factors such as swap size, gas costs, and slippage. When searching for swaps, developers have to choose between two types of swaps:
findRouteGivenIn
, where the amount of tokens being sent to the pool is known, orfindRouteGivenOut
, where the amount of tokens received from the pool is known.
const swapInfo = await swaps.findRouteGivenIn({
tokenIn: '0xstring', // address of tokenIn
tokenOut: '0xstring', // address of tokenOut
amount: parseEther('1'), // BigNumber with a swap amount
gasPrice: parseFixed('1', 9), // BigNumber current gas price
maxPools, // number of pool included in path, above 4 is usually a high gas price
});
The SOR returns the swap information, including the optimal swap route, the expected slippage and gas cost, and the estimated swap outcome as swapInfo
.
{
tokenAddresses: string[] // tokens used in swaps
swaps: SwapV2[] // swaps calldata
swapAmount: BigNumber
swapAmountForSwaps: BigNumber // used for wrapped assets, eg: stETH / wstETH
returnAmount: BigNumber
returnAmountFromSwaps: BigNumber // used for wrapped assets, eg: stETH/wstETH
returnAmountConsideringFees: BigNumber
tokenIn: string
tokenInForSwaps: string // Used with stETH/wstETH
tokenOut: string
tokenOutFromSwaps: string // Used with stETH/wstETH
marketSp: string
}
Example response for ETH to wBTC swap
{
tokenAddresses: [
'0x0000000000000000000000000000000000000000',
'0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'
],
swaps: [
{
poolId: '0xa6f548df93de924d73be7d25dc02554c6bd66db500020000000000000000000e',
assetInIndex: 0,
assetOutIndex: 1,
amount: '1000000000000000000',
userData: '0x',
returnAmount: '7677860'
}
],
swapAmount: BigNumber { _hex: '0x0de0b6b3a7640000', _isBigNumber: true },
swapAmountForSwaps: BigNumber { _hex: '0x0de0b6b3a7640000', _isBigNumber: true },
returnAmount: BigNumber { _hex: '0x7527a4', _isBigNumber: true },
returnAmountFromSwaps: BigNumber { _hex: '0x7527a4', _isBigNumber: true },
returnAmountConsideringFees: BigNumber { _hex: '0x752543', _isBigNumber: true },
tokenIn: '0x0000000000000000000000000000000000000000',
tokenInForSwaps: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
tokenOut: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599',
tokenOutFromSwaps: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599'
marketSp: '13.022594322651878',
}
Step 3. Transaction encoding
To execute the swap, the returned swapInfo
must be encoded into a transaction, which requires the following information:
const tx = swaps.buildSwap({
userAddress: '0xstring', // user address
swapInfo, // result from the previous step
kind: SwapType.SwapExactIn, // or SwapExactOut
deadline, // BigNumber block timestamp
maxSlippage, // [bps], eg: 1 == 0.01%, 100 == 1%
})
Step 4. Broadcast transaction
const signer = balancer.provider.getSigner()
await signer.sendTransaction({
to: tx.to,
data: tx.data,
value: tx.value
})