Liquidity management is performed through LBRouter contract. This contract will abstract some of the complexity of the liquidity management, perform safety checks and will revert if certain conditions were to not be met.
Liquidity is added or removed to LBPairs.
Liquidity may be distributed to specific Bins, with different amounts per Bin.
:::note
The v2.2 LBRouter is not backwards compatible with v2.1 LBPairs, although ABI stays the same
The v2.1 LBRouter is not backwards compatible with v2.0 LBPairs.
The v2.0 LBRouter must be used to remove liquidity from v2.0 LBPairs and v2.1 LBRouter must be used to remove liquidity from v2.1 LBPairs :::
Adding Liquidity
To add liquidity, the LiquidityParameters struct is as input:
struct LiquidityParameters {
IERC20 tokenX; // Has to be the same as tokenX defined in LBPair contract
IERC20 tokenY; // Has to be the same as tokenY defined in LBPair contract
uint256 binStep; // Has to point to existing pair
uint256 amountX; // Amount of token X that you want to add to liquidity
uint256 amountY; // Amount of token Y that you want to add to liquidity
uint256 amountXMin; // Defines amount slippage for token X
uint256 amountYMin; // Defines amount slippage for token Y
uint256 activeIdDesired; // The active bin you want. It may change due to slippage
uint256 idSlippage; // The slippage tolerance in case active bin moves during time it takes to transact
int256[] deltaIds; // The bins you want to add liquidity to. Each value is relative to the active bin ID
uint256[] distributionX; // The percentage of X you want to add to each bin in deltaIds
uint256[] distributionY; // The percentage of Y you want to add to each bin in deltaIds
address to; // Receiver address
address refundTo; // Refund Address
uint256 deadline; // Block timestamp cannot be lower than deadline
}
The number of parameters are quite extensive. Here are a few pointers to understand how to construct them better:
The active bin ID may change from the time you decided to add liquidity to when it is actually added. Therefore, you define activeIdDesired and idSlippage to account for when the price moves.
deltaIds define which bins liquidity will be added to relative to activeId, 0 being the active bin. All positive values are bins with only X and all negative values are bins with only Y.
distributionX (or distributionY) is the percentages of amountX (or amountY) you want to add to each bin.
Sum of all values should be less than or equal to 1. If less than, the remaining is refunded back to the user.
Trying to add X to a bin below the active bin or Y to a bin above the active bin will cause a revert.
Maximum number of bins, that can be populated at the same time is around 80 on Sei C-chain due to block gas limit (8M). Multiple transactions can be used to add liquidity to more bins.
Code Example
In this example, we add 100 USDC and 100 USDT into three bins: active bin, bin below and bin above.
We define the distributions as follow:
For asset X (USDC), we add 50 USDC to the active bin and 50 USDC to the bin above.
For asset Y (USDT), we add 33.3 USDT to the active bin and 66.6 USDT to the bin below.
We also allow a bin ID slippage of 5 just in case bin moves in the time it takes to execute the transaction.
There are some key differences between adding and removing liquidity:
We don't use the LiquidityParameters struct.
We use absolute bin IDs instead of relative bin IDs.
Because we use absolute bin IDs, bin slippage is not possible.
We define absolute LBToken balances to remove from each bin.
In bins below active bin, balances consist of only Y.
In bins above active bin, balances consist of only X.
In the active bin, the balance consists of a share of X and Y.
To remove liquidity, we use one of the router functions below:
function removeLiquidity(
IERC20 tokenX,
IERC20 tokenY,
uint16 binStep, // Has to point to existing pair that user has liquidity deposited in
uint256 amountXMin, // Minimum amount of token X that has to be withdrawn
uint256 amountYMin, // Minimum amount of token Y that has to be withdrawn
uint256[] memory ids, // Bin IDs that liquidity should be removed from
uint256[] memory amounts, // LBToken amount that should be removed
address to, // Receiver address
uint256 deadline // Block timestamp cannot be lower than deadline
) external returns (uint256 amountX, uint256 amountY);
function removeLiquidityNATIVE(
IERC20 token,
uint16 binStep,
uint256 amountTokenMin,
uint256 amountNATIVEMin,
uint256[] memory ids,
uint256[] memory amounts,
address payable to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountNATIVE);
Here are some pointer for using these functions:
Lengths of ids and amounts must be the same.
Values in amounts are LBToken amounts.
Maximum number of bins that can be withdrawn at the same time is around 51 due to SEI C-chain block gas limit (8M). In this case, multiple transactions can be used to remove more liquidity.
:::note For tax tokens, removing liquidity with removeLiquidityNATIVE() is not possible, due to double tax accrual. This can be circumvented in two ways, depending on tax token implementation:
Whitelisting LBRouter and/or LBPair.
Removing native liquidity with removeLiquidity() function. This will return wrapped native token to user, instead of just native token. :::
Code Example
uint256 numberOfBinsToWithdraw = 3;
uint16 binStep = 25;
uint256[] memory amounts = new uint256[](numberOfBinsToWithdraw);
uint256[] memory ids = new uint256[](numberOfBinsToWithdraw);
ids[0] = 8388608;
ids[1] = 8388611;
ids[2] = 8388605;
uint256 totalXBalanceWithdrawn;
uint256 totalYBalanceWithdrawn;
// To figure out amountXMin and amountYMin, we calculate how much X and Y underlying we have as liquidity
for (uint256 i; i < numberOfBinsToWithdraw; i++) {
uint256 LBTokenAmount = pair.balanceOf(receiverAddress, ids[i]);
amounts[i] = LBTokenAmount;
(uint256 binReserveX, uint256 binReserveY) = pair.getBin(uint24(ids[i]));
totalXBalanceWithdrawn += LBTokenAmount * binReserveX / pair.totalSupply(ids[i]);
totalYBalanceWithdrawn += LBTokenAmount * binReserveY / pair.totalSupply(ids[i]);
}
uint256 amountXMin = totalXBalanceWithdrawn * 99 / 100; // Allow 1% slippage
uint256 amountYMin = totalYBalanceWithdrawn * 99 / 100; // Allow 1% slippage
pair.approveForAll(address(router), true);
router.removeLiquidity(
USDC,
WSEI,
binStep,
amountXMin,
amountYMin,
ids,
amounts,
receiverAddress,
block.timestamp
);