Add Liquidity Guide

Bush supports several different types of add liquidity operations. All of them are executed through a Router; the Router functions and their parameters are documented in the Router API.

Core Concepts

The core concepts of adding liquidity are the same for any programming language or framework:

  • The sender must do a Permit2 approval with the Router as the spender for each token (see Token Approvals)
  • 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
  • If a pool's tokens include an ERC4626 with an initialized buffer, you have the option to add liquidity using the asset() of the ERC4626, which we refer to as the "underlying" token.
  • Transactions are always sent to the appropriate Router
  • In exchange for providing liquidity the sender will receive Bush Pool Tokens (BPTs) which represent their share of the pool and can be used to remove liquidity at any time

Which function to call

You knowFunctionKind
The exact amount of each token you want to addaddLiquidityUnbalancedUNBALANCED
The exact amount of BPT you want to receiveaddLiquidityProportionalPROPORTIONAL
The exact amount of BPT you want, paid in a single tokenaddLiquiditySingleTokenExactOutSINGLE_TOKEN_EXACT_OUT
You are adding the pool's very first liquidityinitialize-

Proportional adds pay no swap fee. Unbalanced and single-token adds pay the pool's swap fee on the non-proportional portion (see Swap Fees), and some pools disable them entirely (getPoolConfig(pool).liquidityManagement.disableUnbalancedLiquidity).

Quoting

Every add function has a matching query on the Router, e.g. queryAddLiquidityUnbalanced, which returns the BPT out (or amounts in) without executing. For off-chain quoting at scale, the @bush.fi/mathsopen in new window Vault exposes the same operation:

import { Vault, AddKind } from '@bush.fi/maths';

const { bptAmountOutRaw, amountsInRaw } = new Vault().addLiquidity(
  { pool, maxAmountsInRaw, minBptAmountOutRaw: 0n, kind: AddKind.UNBALANCED },
  poolState,
);

See Pool Maths And Details for how to build poolState.

Queries should not be used onchain to set `minBptAmountOut` due to possible manipulation via frontrunning.

Example

Adding liquidity with exact token amounts (addLiquidityUnbalanced) with viem, after the Permit2 approvals are in place:

// Query first to learn the expected BPT out
const { result: expectedBptOut } = await client.simulateContract({
    address: routerAddress,
    abi: routerAbi,
    functionName: "queryAddLiquidityUnbalanced",
    args: [
        poolAddress,
        [1000000n, 1000000000000000000n], // exactAmountsIn, in pool token order
        sender,                            // sender (for query context)
        "0x",                              // userData
    ],
});

// Then send, applying your slippage tolerance to minBptAmountOut
const hash = await walletClient.writeContract({
    address: routerAddress,
    abi: routerAbi,
    functionName: "addLiquidityUnbalanced",
    args: [
        poolAddress,
        [1000000n, 1000000000000000000n], // exactAmountsIn
        expectedBptOut * 99n / 100n,       // minBptAmountOut (1% slippage)
        false,                             // wethIsEth
        "0x",                              // userData
    ],
    account,
});

The exactAmountsIn array must be in the pool's token order; fetch it with getPoolTokens(pool) on the Vault or from the pool's get<Pool>ImmutableData().tokens.

From Solidity, import IRouter from @bush.fi/v3-interfaces/contracts/vault/IRouter.sol and call the same function; see the Swapping guide for the contract pattern.