Remove Liquidity Guide
Bush supports several different types of remove 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 removing liquidity are the same for any programming language or framework:
- When removing liquidity the user sends Bush Pool Tokens (BPTs), and will receive pool tokens
- BPT is an ERC20 with
permit; use apermitsignature (or a regular approval) to allow the Router to spend it. Permit2 is not required for BPT. 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 receive the
asset()of the ERC4626 when removing liquidity. - Transactions are always sent to a Router
- Use the standard
Routerto receive standard pool tokens - Use the
CompositeLiquidityRouterto receive a pool's underlying tokens
- Use the standard
Which function to call
| You know | Function | Kind |
|---|---|---|
| The exact amount of BPT to burn, receiving every token proportionally | removeLiquidityProportional | PROPORTIONAL |
| The exact amount of BPT to burn, receiving a single token | removeLiquiditySingleTokenExactIn | SINGLE_TOKEN_EXACT_IN |
| The exact amount of a single token to receive | removeLiquiditySingleTokenExactOut | SINGLE_TOKEN_EXACT_OUT |
| The pool is in Recovery Mode | removeLiquidityRecovery | - |
Proportional removes pay no swap fee and always work, including when the pool is paused (via Recovery Mode). Single-token removes pay the pool's swap fee on the non-proportional portion and can be disabled per pool.
Quoting
Every remove function has a matching query on the Router, e.g. queryRemoveLiquidityProportional. For off-chain quoting at scale, the @bush.fi/maths Vault exposes the same operation:
import { Vault, RemoveKind } from '@bush.fi/maths';
const { amountsOutRaw, bptAmountInRaw } = new Vault().removeLiquidity(
{ pool, minAmountsOutRaw, maxBptAmountInRaw, kind: RemoveKind.PROPORTIONAL },
poolState,
);
See Pool Maths And Details for how to build poolState.
Queries should not be used onchain to set `minAmountsOut` due to possible manipulation via frontrunning.
Example
Removing liquidity proportionally with viem, after approving the Router to spend BPT:
// Query first to learn the expected amounts out
const { result: expectedAmountsOut } = await client.simulateContract({
address: routerAddress,
abi: routerAbi,
functionName: "queryRemoveLiquidityProportional",
args: [
poolAddress,
1000000000000000000n, // exactBptAmountIn
sender, // sender (for query context)
"0x", // userData
],
});
// Then send, applying your slippage tolerance to each minAmountOut
const hash = await walletClient.writeContract({
address: routerAddress,
abi: routerAbi,
functionName: "removeLiquidityProportional",
args: [
poolAddress,
1000000000000000000n, // exactBptAmountIn
expectedAmountsOut.map((a) => (a * 99n) / 100n), // minAmountsOut (1% slippage)
false, // wethIsEth
"0x", // userData
],
account,
});
minAmountsOut 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.