Cheaper Arbitrage with Batch Swaps

Orb
Balancer Protocol
Published in
4 min readNov 16, 2022

When attempting to swap tokens intermediate swaps are often necessary. This occurs when there is no pool containing both tokens or a cheaper route is available. These swaps usually require multiple function calls. Balancer provides a gas efficient solution, batch swaps.

How Does it Work?

Batch swaps are a single function call to Balancer’s Vault which then updates the balance of the pools.

The Balancer Vault aggregates tokens across all pools. This allows for the separation of accounting logic from pool contracts. The Vault accounts for token balances, while the Swaps contract implements the logic behind swaps, joins, and exits. A single batch swap can break a trade into stages. Token balances update at the end of each swap so trades can navigate through several pools.

A very useful feature, especially for UIs and aggregators, is batch swap simulation. This functionality is achieved using the queryBatchSwap function from the Vault. QueryBatchSwap mimics the BatchSwap function but reverts at the end returning the encoded balance update data.

Use Cases

1. No Pool Containing all the Tokens Being Traded

When no pool contains all the necessary tokens, multiple swaps must be made to receive the desired token. For example, when there is a trade from WBTC to wstETH on Balancer, there is no pool which contains both tokens. The only pool containing wstETH is the wstETH/WETH pool. Therefore, an intermediate trade will be necessary through Balancer’s WBTC/WETH pool — first trading WBTC for WETH:

then WETH for wstETH:

Instead of calling swaps on the two individual pools, the Vault’s batchSwap function can be called and the Vault will adjust token balances for both pools.

2. Cheaper Route — Large Trade/High Slippage

Even if there is an existing pool with both tokens, a portion of the trade may be routed through additional pools if the trade is large enough to incur significant slippage. For instance, if there were a trade of 1000 ETH for USDC on Balancer, to minimize slippage, the trade will be broken into 3 portions and routed through different pools. The majority of the trade will be routed through the WETH/USDC pool. Another portion will be routed through two pools — first swapping WETH for DAI using the WETH/DAI pool, then DAI for USDC in the DAI/USDC/USDT pool. The third portion will be routed through Balancer’s boosted and linear pools. All 5 of these trades can be made with a single batch swap.

3. Cheaper Route — Low Liquidity

Another use case of batch swaps is when there is not deep liquidity in a given pool. For example, since Balancer’s WETH/USDT pool does not have deep liquidity($43k at time of writing), many trades for WETH to USDT will first be from WETH to USDC and/or WETH to DAI in the USDC/WETH and DAI/WETH pools, then from USDC to USDT and/or DAI to USDT through the DAI/USDC/USDT pool. Again, all 4 of these trades can be made with a single batch swap.

The Code

The batch swap function has the following structure:

batch_swap_function = contract_vault.functions.batchSwap(
swap_kind,
swap_step_stucts,
checksum_tokens,
fund_struct,
token_limits,
deadline
)

token_limits can be determined by calling the Vault’s queryBatchSwap function to receive the estimated balance changes of each token after the batch swap is completed. This can be very useful for applying slippage tolerance.

Swap_step_structs is an array of batch swap steps with following structure:

struct BatchSwapStep = {
bytes32 poolId;
uint256 assetInIndex;
uint256 assetOutIndex;
uint256 amount;
bytes userData;
}

Here, amount is equal to the amount to be swapped. However, for multihop trades, amount should be set to 0 for swaps that are using tokens received from previous steps. assetInIndex and assetOutIndex should be set to the index of the tokens in the checksum_tokens array passed into the batchSwap function. The checksum_tokens array should be listed in alphabetical order of the tokens’ symbols not in the order of the swaps to be performed. Therefore, if we want to swap BAL → WETH → BTC, the tokens array we get is [BAL, BTC, WETH]. UserData represents any additional data that is needed to perform the swap. This can be left empty for all current Balancer pools, but it allows more flexibility for future pools with custom logic.

For additional resources on batch swaps, please check the code walkthrough in either JS or Python, or have a look at the Batch Swap Overview.

--

--

Orb
Balancer Protocol

Orb is dedicated to propelling Balancer’s growth by scaling global utilization of the Protocol and growing the ecosystem.