Adjusting Position Liquidity
- Rust
- TypeScript Kit
- TypeScript Legacy
Once you've opened a position in an Orca Whirlpool, you may need to adjust the amount of liquidity you've provided to align with market conditions or your strategy. Whether you want to add more liquidity to capture additional fees or withdraw liquidity to reduce exposure or realize profits, the Whirlpools SDK provides functions for both.
This guide explains how to use the SDK functions to increase and decrease the liquidity in your position.
1. Overview of Adjusting Liquidity
The SDK functions for increasing or decreasing liquidity work similarly, enabling you to modify the liquidity of an existing position. You can specify the liquidity directly or provide amounts of token A or token B to increase or decrease liquidity.
With these functions, you can:
- Increase liquidity to potentially earn more fees as trading volume grows.
- Decrease liquidity to reduce exposure or withdraw profits.
2. Getting Started Guide
Adjusting Liquidity in a Position
Adjusting liquidity in an existing position can be done as follows:
- RPC Client: Use a Solana RPC client to interact with the blockchain.
- Position Mint: Provide the mint address of the NFT representing your position. This NFT serves as proof of ownership of the position you want to adjust.
- Liquidity Parameters: Choose how you want to adjust liquidity. You only need to provide one of these parameters, and the function will compute the others in the returned quote based on the current price of the pool and the price range of the position:
liquidity
: Specify the liquidity value to add or remove.tokenA
: Specify the amount of token A to add or withdraw.tokenB
: Specify the amount of token B to add or withdraw.
- Slippage tolerance: Set the maximum slippage tolerance (optional, defaults to 1%). Slippage refers to the difference between the expected token amounts added or removed when adjusting liquidity and the actual amounts that are ultimately deposited or withdrawn. A lower slippage tolerance reduces the risk of depositing or withdrawing more or fewer tokens than intended, but it may lead to failed transactions if the market moves too quickly.
- Funder: This can be your wallet, which will fund the pool initialization. If a funder is not specified, the default wallet will be used. You can configure the default wallet through the SDK.
- Create Instructions: Use the appropriate function to generate the necessary instructions.
use orca_whirlpools::{
decrease_liquidity_instructions, increase_liquidity_instructions, set_whirlpools_config_address, DecreaseLiquidityParam, IncreaseLiquidityParam, WhirlpoolsConfigInput
};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::pubkey::Pubkey;
use std::str::FromStr;
use solana_sdk::signature::Signer;
use orca_tx_sender::{
build_and_send_transaction,
set_rpc, get_rpc_client
};
use solana_sdk::commitment_config::CommitmentLevel;
use crate::utils::load_wallet;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
set_rpc("https://api.devnet.solana.com").await?;
set_whirlpools_config_address(WhirlpoolsConfigInput::SolanaDevnet).unwrap();
let wallet = load_wallet();
let position_mint_address = Pubkey::from_str("HqoV7Qv27REUtmd9UKSJGGmCRNx3531t33bDG1BUfo9K").unwrap();
let increase_param = IncreaseLiquidityParam::TokenA(1_000_000);
let decrease_param = DecreaseLiquidityParam::TokenA(1_000_000);
let rpc = get_rpc_client()?;
let increase_result = increase_liquidity_instructions(
&rpc,
position_mint_address,
increase_param,
Some(100),
Some(wallet.pubkey()),
)
.await?;
println!("Liquidity Increase Quote: {:?}", increase_result.quote);
println!("Number of Instructions: {}", increase_result.instructions.len());
let increase_signature = build_and_send_transaction(
increase_result.instructions,
&[&wallet],
Some(CommitmentLevel::Confirmed),
None, // No address lookup tables
).await?;
println!("Increase liquidity transaction sent: {}", increase_signature);
let decrease_result = decrease_liquidity_instructions(
&rpc,
position_mint_address,
decrease_param,
Some(100),
Some(wallet.pubkey()),
)
.await?;
println!("Liquidity Decrease Quote: {:?}", decrease_result.quote);
let decrease_signature = build_and_send_transaction(
decrease_result.instructions,
&[&wallet],
Some(CommitmentLevel::Confirmed),
None, // No address lookup tables
).await?;
println!("Decrease liquidity transaction sent: {}", decrease_signature);
Ok(())
}
3. Usage example
You are creating a bot to manage investors' funds and want to optimize returns. Such a bot could rebalance liquidity based on market signals to maintain a specific target price range or to optimize fee collection during periods of high volatility.
4. Next steps
After adjusting liquidity, you can:
- Monitor Performance: Track your adjusted position to evaluate its performance and earned fees.
- Harvest Rewards: Collect any earned fees and rewards without closing your position.
- Make Further Adjustments: Depending on market conditions, continue to adjust liquidity as needed to maximize returns or manage risk.
By using the SDK to adjust liquidity, you gain flexibility in managing your positions and optimizing your liquidity provision strategy.
Once you've opened a position in an Orca Whirlpool, you may need to adjust the amount of liquidity you've provided to align with market conditions or your strategy. Whether you want to add more liquidity to capture additional fees or withdraw liquidity to reduce exposure or realize profits, the Whirlpools SDK provides functions for both.
This guide explains how to use the SDK functions to increase and decrease the liquidity in your position.
1. Overview of Adjusting Liquidity
The SDK functions for increasing or decreasing liquidity work similarly, enabling you to modify the liquidity of an existing position. You can specify the liquidity directly or provide amounts of token A or token B to increase or decrease liquidity.
With these functions, you can:
- Increase liquidity to potentially earn more fees as trading volume grows.
- Decrease liquidity to reduce exposure or withdraw profits.
2. Getting Started Guide
Adjusting Liquidity in a Position
Adjusting liquidity in an existing position can be done as follows:
- RPC Client: Use a Solana RPC client to interact with the blockchain.
- Position Mint: Provide the mint address of the NFT representing your position. This NFT serves as proof of ownership of the position you want to adjust.
- Liquidity Parameters: Choose how you want to adjust liquidity. You only need to provide one of these parameters, and the function will compute the others in the returned quote based on the current price of the pool and the price range of the position:
liquidity
: Specify the liquidity value to add or remove.tokenA
: Specify the amount of token A to add or withdraw.tokenB
: Specify the amount of token B to add or withdraw.
- Slippage tolerance: Set the maximum slippage tolerance (optional, defaults to 1%). Slippage refers to the difference between the expected token amounts added or removed when adjusting liquidity and the actual amounts that are ultimately deposited or withdrawn. A lower slippage tolerance reduces the risk of depositing or withdrawing more or fewer tokens than intended, but it may lead to failed transactions if the market moves too quickly.
- Funder: This can be your wallet, which will fund the pool initialization. If a funder is not specified, the default wallet will be used. You can configure the default wallet through the SDK.
- Create Instructions: Use the appropriate function to generate the necessary instructions.
import { decreaseLiquidityInstructions, increaseLiquidityInstructions, setWhirlpoolsConfig } from '@orca-so/whirlpools';
import { createSolanaRpc, devnet, address } from '@solana/kit';
import { loadWallet } from './utils';
await setWhirlpoolsConfig('solanaDevnet');
const devnetRpc = createSolanaRpc(devnet('https://api.devnet.solana.com'));
const wallet = await loadWallet();
const positionMint = address("HqoV7Qv27REUtmd9UKSJGGmCRNx3531t33bDG1BUfo9K");
const param = { tokenA: 10n };
const { quote: increaseQuote, instructions: increaseInstructions, callback: sendTxIncrease } = await increaseLiquidity(
devnetRpc,
positionMint,
param,
100,
wallet
);
const { quote: decreaseQuote, instructions: decreaseInstructions, callback: sendTxDecrease } = await decreaseLiquidity(
devnetRpc,
positionMint,
param,
100,
wallet
);
console.log(`Increase quote token max B: ${increaseQuote.tokenEstB}`);
console.log(`Decrease quote token max B: ${decreaseQuote.tokenEstB}`);
// Use the callback to submit the transaction
const txIdIncrease = await sendTxIncrease();
console.log(`Transaction ID: ${txIdIncrease}`);
const txIdDecrease = await sendTxDecrease();
console.log(`Transaction ID: ${txIdDecrease}`);
3. Usage example
You are creating a bot to manage investors' funds and want to optimize returns. Such a bot could rebalance liquidity based on market signals to maintain a specific target price range or to optimize fee collection during periods of high volatility.
4. Next steps
After adjusting liquidity, you can:
- Monitor Performance: Track your adjusted position to evaluate its performance and earned fees.
- Harvest Rewards: Collect any earned fees and rewards without closing your position.
- Make Further Adjustments: Depending on market conditions, continue to adjust liquidity as needed to maximize returns or manage risk.
By using the SDK to adjust liquidity, you gain flexibility in managing your positions and optimizing your liquidity provision strategy.
Whirlpools provide two instructions - increase_liquidity
and decrease_liquidity
to allow users to modify their position's liquidity.
The SDK also provides quote functions (ex. increaseLiquidityQuoteByInputToken
, decreaseLiquidityQuoteByLiquidity
) to help estimate the tokenIn/Out from the liquidity operation.
Using Whirlpool Client
Use the Position
class from the WhirlpoolClient
to fetch and manage your liquidity. Read below for more on the relationship between quote and the transaction.
const position = await client.getPosition(positionAddress);
const preIncreaseData = position.getData();
const increase_quote = increaseLiquidityQuoteByInputToken(
poolInitInfo.tokenMintB,
new Decimal(70),
lowerTick,
upperTick,
Percentage.fromFraction(1, 100),
pool
);
await (
await position.increaseLiquidity(increase_quote, ctx.wallet.publicKey, ctx.wallet.publicKey)
).buildAndExecute();
The Manual Way
For each instruction, calculate the following values:
- liquidityAmount - The total amount of liquidity you would like to deposit/withdraw into your position.
- tokenMax A, B (increase_liquidity) - The maximum amount of token X to add to the position. Note the value here is shifted by the decimal places of the token.
- tokenMin A, B (decrease_liquidity) - The minimum amount of token X to withdraw from the position. Note the value here is shifted by the decimal places of the token.
Getting a Quote
The Typescript SDK provides several quote functions to help generate an estimate based on common user input values.
Increase liquidity quote by input token amount
Given a desired amount of input token (A or B), you can use the quote utility function increaseLiquidityQuoteByInputTokenWithParams
to calculate the liquidityAmount and other tokenMax value required to deposit the desired amount of token into the position.
The quote amount will differ based on the current price (tick) and the desired tick boundaries for the position. The price environment may change from the time of quote to the actual processing of the increase_liquidity
ix. Use the slippage tolerance to adjust the quote values to balance your risk of ix failure and total tokens to deposit.
const whirlpool = await fetcher.getPool(...);
const position = await fetcher.getPosition(...);
// 10 tokens of a token with 6 decimals
const depositTokenBAmount = new BN(10_000_000);
const quote = await increaseLiquidityQuoteByInputTokenWithParams({
tokenMintA: whirlpool.tokenMintA,
tokenMintB: whirlpool.tokenMintB,
tickCurrentIndex: whirlpool.tickCurrentIndex,
sqrtPrice: whirlpool.sqrtPrice,
inputTokenMint: whirlpool.tokenMintB,
inputTokenAmount: desiredTokenBAmount,
tickLowerIndex: position.tickLowerIndex,
tickUpperIndex: position.tickUpperIndex,
slippageTolerance: Percentage.fromFraction(1, 1000),
});
Decrease liquidity quote by input token amount
Given the liquidity amount, use the decreaseLiquidityQuoteByLiquidityWithParams
util function to get an estimate on what's the minimum token A & B you can expect from the decrease_liquidity
instruction call.
Like increase_liquidity
, use the slippage tolerance to adjust the quote values to balance your risk of ix failure and total tokens to deposit.
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress);
// Example: Withdraw 30% of the position
const totalLiquidityInPosition = position.liquidity;
const withdrawLiquidityAmount = totalLiquidityInPosition.mul(new BN(30).div(new BN(100)));
const depositQuote = decreaseLiquidityQuoteByLiquidityWithParams({
withdrawLiquidityAmount,
sqrtPrice: whirlpool.sqrtPrice,
tickCurrentIndex: whirpool.tickCurrentIndex,
tickLowerIndex: position.tickLowerIndex,
tickUpperIndex: position.tickUpperIndex,
slippageTolerance: Percentage.fromFraction(1, 100),
});
Other Parameters
- whirlpool - PublicKey of the whirlpool the position is a part of
- position - PublicKey of the position address. Derivable from PDAUtil.getPosition.
- positionTokenAccount - Associated token address of the position token on the user's wallet.
- tokenOwnerAccount A, B - Associated token address of the tokenA,B on the user's wallet.
- tokenVaults A, B - PublicKey of the token vaults for this
- tickArrayLower, Upper - Lower & upper tick-array accounts that contains the tick indices for the lower, upper bound of the position
- positionAuthority - The address that hosts the position token. This authority must sign the transaction.
Sample Code
Increase liquidity example
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress);
// 10 tokens of a token with 6 decimals
const depositTokenBAmount = new BN(10_000_000);
const depositQuote = increaseLiquidityQuoteByInputTokenWithParams({depositTokenBAmount, ...});
await toTx(ctx, WhirlpoolIx.increaseLiquidityIx(ctx.program, {
...depositQuote,
whirlpool: whirlpoolAddress,
positionAuthority: provider.wallet.publicKey,
position: positionAddress,
positionTokenAccount,
tokenOwnerAccountA,
tokenOwnerAccountB,
tokenVaultA: whirlpool.tokenVaultA,
tokenVaultB: whirlpool.tokenVaultB,
tickArrayLower: position.tickArrayLower,
tickArrayUpper: position.tickArrayUpper,
})).buildAndExecute();
Decrease liquidity example
const whirlpool = await fetcher.getPool(whirlpoolAddress);
const position = await fetcher.getPosition(positionAddress);
const removalQuote = decreaseLiquidityQuoteByLiquidityWithParams({...});
await toTx(ctx, WhirlpoolIx.decreaseLiquidityIx(ctx.program, {
...removalQuote,
whirlpool: whirlpoolAddress,
positionAuthority: provider.wallet.publicKey,
position: positionAddress,
positionTokenAccount,
tokenOwnerAccountA,
tokenOwnerAccountB,
tokenVaultA: whirlpool.tokenVaultA,
tokenVaultB: whirlpool.tokenVaultB,
tickArrayLower: position.tickArrayLower,
tickArrayUpper: position.tickArrayUpper,
})).buildAndExecute();
Common Errors
LiquidityZero
(0x177c) - Provided liquidity amount is zero.LiquidityTooHigh
(0x177d) - Provided liquidity exceeds u128::max.TokenMaxExceeded
(0x1781) - The required token to perform this operation exceeds the user defined amount in increase_liquidity.TokenMinSubceeded
(0x1782) - The required token to perform this operation subceeds the user defined amount in decrease_liquidity.TickNotFound
(0x1779) - The provided tick array accounts do not contain the tick specified in the position.ConstraintRaw
(0x7d3) - TokenVault, TokenAccount mints does not match the values in the provided whirlpool.