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 USDCshould be sent as1000000because 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
- Use the standard
Routerto add liquidity with pool tokens - Use the
CompositeLiquidityRouterto add liquidity with a pool's underlying tokens - Use the
UnbalancedAddViaSwapRouterto add liquidity to a pool that has unbalanced adds disabled, by swapping first
- Use the standard
- 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 know | Function | Kind |
|---|---|---|
| The exact amount of each token you want to add | addLiquidityUnbalanced | UNBALANCED |
| The exact amount of BPT you want to receive | addLiquidityProportional | PROPORTIONAL |
| The exact amount of BPT you want, paid in a single token | addLiquiditySingleTokenExactOut | SINGLE_TOKEN_EXACT_OUT |
| You are adding the pool's very first liquidity | initialize | - |
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/maths 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.