Swapping with the Router
This guide illustrates the process of executing swaps through the Router once swap paths have been established. The examples provided encompass both single and multi-path swap types, focusing on exactIn swaps. For additional information on exactOut swaps, please refer to the Router API and Batch Router API documentation.
Finding the best path across pools is the integrator's job: index the pools (Fetching Pools And Data) and quote candidate routes off-chain with the exact maths in @bush.fi/maths (Pool Maths And Details).
Core Concepts
The core concepts of executing Swaps are the same for any programming language or framework:
- The sender must do a Permit2 approval with the Router as the spender for each swap input token (see Token Approvals). Contracts that already hold the input tokens can skip approvals entirely by using the prepaid Aggregator Routers, which expose the same functions.
- Token amount inputs/outputs are always in the raw token scale, e.g.
1 USDCshould be sent as1000000because it has 6 decimals - Transactions are always sent to the Router (single swaps) or Batch Router (multi-path swaps); addresses are on the Deployment Addresses page
- There are two different swap kinds:
- ExactIn: Where the user provides an exact input token amount.
- ExactOut: Where the user provides an exact output token amount.
- There are two subsets of a swap:
- Single Swap: A swap, tokenIn > tokenOut, using a single pool. This is the most gas efficient option for a swap of this kind.
- Multi-path Swaps: Swaps involving multiple paths but all executed in the same transaction. Each path can have its own (or the same) tokenIn/tokenOut.
The following sections provide specific implementation details for Javascript and Solidity.
Single Swap
The following code examples demonstrate how to execute a single token swap specifying an exact input token amount. To achieve this, we use two Router functions:
swapSingleTokenExactIn- Execute a swap specifying an exact input token amount.querySwapSingleTokenExactIn- The router query used to simulate a swap. It returns the exact amount of token out that would be received.
The Router interface for swapSingleTokenExactIn is:
/**
* @notice Executes a swap operation specifying an exact input token amount.
* @param pool Address of the liquidity pool
* @param tokenIn Token to be swapped from
* @param tokenOut Token to be swapped to
* @param exactAmountIn Exact amounts of input tokens to send
* @param minAmountOut Minimum amount of tokens to be received
* @param deadline Deadline for the swap
* @param userData Additional (optional) data required for the swap
* @param wethIsEth If true, incoming ETH will be wrapped to WETH; otherwise the Vault will pull WETH tokens
* @return amountOut Calculated amount of output tokens to be received in exchange for the given input tokens
*/
function swapSingleTokenExactIn(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountIn,
uint256 minAmountOut,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256 amountOut);
exactAmountIndefines the exact amount of tokenIn to send.minAmountOutdefines the minimum amount of tokenOut to receive. If the amount is less than this (e.g. because of slippage) the transaction will revert- If
wethIsEthis set totrue, the Router will deposit theexactAmountInofETHinto theWETHcontract. So, the transaction must be sent with the appropriatevalueamount deadlinethe UNIX timestamp at which the swap must be completed by - if the transaction is confirmed after this time then the transaction will fail.userDataallows additional parameters to be provided for custom pool types. In most cases it is not required and a value of0xcan be provided.
Javascript
Addresses in the examples are placeholders; use the pool and router addresses for your network.
Resources:
Solidity
Queries should not be used onchain to set minAmountOut due to possible manipulation via frontrunning.
pragma solidity ^0.8.24;
import { IRouter } from "@bush.fi/v3-interfaces/contracts/vault/IRouter.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract SingleSwap {
IRouter public router;
constructor(IRouter _router) {
router = _router;
}
function singleSwap(
address pool,
IERC20 tokenIn,
IERC20 tokenOut,
uint256 exactAmountIn,
uint256 minAmountOut,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external {
router.swapSingleTokenExactIn(
pool,
tokenIn,
tokenOut,
exactAmountIn,
minAmountOut,
deadline,
wethIsEth,
userData
);
}
}
Multi Path Swap
Note
Multi-path Swaps use the Bush Batch Router, not the Router
The following code examples demonstrate how to execute a multi path swap specifying exact input token amounts. To achieve this, we use two Router functions:
swapExactIn- Execute a swap involving multiple paths, specifying exact input token amounts.querySwapExactIn- The router query used to simulate a swap. It returns the exact amount of token out for each swap path.
The Batch Router interface for swapExactIn is:
/**
* @notice Executes a swap operation involving multiple paths (steps), specifying exact input token amounts.
* @param paths Swap paths from token in to token out, specifying exact amounts in.
* @param deadline Deadline for the swap
* @param wethIsEth If true, incoming ETH will be wrapped to WETH; otherwise the Vault will pull WETH tokens
* @param userData Additional (optional) data required for the swap
* @return pathAmountsOut Calculated amounts of output tokens corresponding to the last step of each given path
* @return tokensOut Calculated output token addresses
* @return amountsOut Calculated amounts of output tokens, ordered by output token address
*/
function swapExactIn(
SwapPathExactAmountIn[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external payable returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut);
deadlinethe UNIX timestamp at which the swap must be completed by - if the transaction is confirmed after this time then the transaction will fail.- If
wethIsEthis set totrue, the Router will deposit theexactAmountInofETHinto theWETHcontract. So, the transaction must be sent with the appropriatevalueamount userDataallows additional parameters to be provided for custom pool types. In most cases it is not required and a value of0xcan be provided.pathsan array of swap paths, in this caseSwapPathExactAmountIn, that have a number of steps,SwapPathStep, to swap a given tokenIn to tokenOut:
struct SwapPathStep {
address pool;
IERC20 tokenOut;
// If true, the "pool" is an ERC4626 Buffer. Used to wrap/unwrap tokens if pool doesn't have enough liquidity.
bool isBuffer;
}
struct SwapPathExactAmountIn {
IERC20 tokenIn;
// for each step:
// if tokenIn == pool use removeLiquidity SINGLE_TOKEN_EXACT_IN
// if tokenOut == pool use addLiquidity UNBALANCED
SwapPathStep[] steps;
uint256 exactAmountIn;
uint256 minAmountOut;
}
- each
pathdefines aminAmountOut. If the amount oftokenOutis less than this (e.g. because of slippage) the transaction will revert - pool add/remove operations can be included in the path by using a pool address as tokenIn/Out
- tokenIn == pool: router will remove liquidity from pool to a single token,
tokenOut - tokenOut == pool: router will add liquidity using
tokenIn - isBuffer: if true, this means the "pool" address is actually an ERC4626 wrapped token, and we want to use the associated buffer
- tokenIn == pool: router will remove liquidity from pool to a single token,
Javascript
Resources:
Solidity
Queries should not be used onchain to set minAmountOut due to possible manipulation via frontrunning.
pragma solidity ^0.8.24;
import { IBatchRouter } from "@bush.fi/v3-interfaces/contracts/vault/IBatchRouter.sol";
import { SwapPathExactAmountIn } from "@bush.fi/v3-interfaces/contracts/vault/BatchRouterTypes.sol";
contract MultiPathSwap {
IBatchRouter public router;
constructor(IBatchRouter _router) {
router = _router;
}
function multiPathSwap(
SwapPathExactAmountIn[] memory paths,
uint256 deadline,
bool wethIsEth,
bytes calldata userData
) external {
router.swapExactIn(
paths,
deadline,
wethIsEth,
userData
);
}
}