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/mathsopen in new window (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 USDC should be sent as 1000000 because 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:

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);
  • exactAmountIn defines the exact amount of tokenIn to send.
  • minAmountOut defines the minimum amount of tokenOut to receive. If the amount is less than this (e.g. because of slippage) the transaction will revert
  • If wethIsEth is set to true, the Router will deposit the exactAmountIn of ETH into the WETH contract. So, the transaction must be sent with the appropriate value amount
  • deadline the UNIX timestamp at which the swap must be completed by - if the transaction is confirmed after this time then the transaction will fail.
  • userData allows additional parameters to be provided for custom pool types. In most cases it is not required and a value of 0x can 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);
  • deadline the UNIX timestamp at which the swap must be completed by - if the transaction is confirmed after this time then the transaction will fail.
  • If wethIsEth is set to true, the Router will deposit the exactAmountIn of ETH into the WETH contract. So, the transaction must be sent with the appropriate value amount
  • userData allows additional parameters to be provided for custom pool types. In most cases it is not required and a value of 0x can be provided.
  • paths an array of swap paths, in this case SwapPathExactAmountIn, 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 path defines a minAmountOut. If the amount of tokenOut is 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

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
        );
    }
}