1. Introduction to Positions in Pools
A position in a liquidity pool represents your contribution of liquidity, which allows traders to swap between tokens while you earn a share of the trading fees. When you open a position, you decide how much liquidity to add, and this liquidity can later be adjusted or removed.- Splash Pools: Provide liquidity without specifying a price range. Ideal for those seeking a simple way to start providing liquidity.
- Concentrated Liquidity Pools: Allow you to provide liquidity within a specified price range, enabling higher capital efficiency but requiring more advanced management.
⚠️ Risk of Divergence loss: The ratio of Token A to Token B that you deposit as liquidity is determined by several factors, including the current price. As trades occur against the pool, the amounts of Token A and Token B in the pool — and in your position — will change, which affects the price of the tokens relative to each other. This can work to your advantage, but it may also result in the combined value of your tokens (including any earned fees and rewards) being lower than when you initially provided liquidity.
2. Getting Started Guide
The usual action of opening a position consists of two instruction callsinitializeTickArrayto initialize the tick arrays that would host your desired ticks for your position if they do not exist yet.Whirlpool.openPositionorWhirlpool.openPositionWithMetadatato mint the position and define the tick rangeincreaseLiquidityto transfer tokens from your wallet into a position.
Whirlpool.openPosition function now supports both traditional and Token2022-based position NFTs. To utilize Token2022, provide the Token2022 ProgramId as the tokenProgramId parameter when calling openPosition. This will mint the NFT using Token2022, which leverages the MetadataPointer and TokenMetadata extensions, eliminating the need for Metaplex metadata accounts.
Opening Position with Metadata
By usingWhirlpool.openPositionWithMetadata, users have the option of appending Metaplex metadata onto the Token Program position NFT. Doing so will allow the token to be identifiable in tracking websites or wallets as a Whirlpool NFT. The drawback is it will require more compute-budget and will incurr Metaplex fees of 0.01 SOL.
Initialize Tick Array accounts if needed
For liquidity to exist in the Whirlpool, the tick-array that contains that particular tick must be initialized. Calculate the start_index of the required tick array and use theinitialize_tick_array instruction to initialize it.
More often than not, tick-arrays are already created. But if you want your code to be defensive, you should do a check prior to invoking open_position. To understand more on how Tick-Arrays work in Whirlpools, read here.
TypeScript
Open a Concentrated Liqudity Position with WhirlpoolClient
WhirlpoolClient’sopenPosition method bundles the open and increase liquidity instructions into a single transaction for you. Below is a code sample to create a position for the SOL/devUSDC pool in the tick range of -138176 and -22976, with the intention to deposit 1 SOL into the position.
TypeScript
Open a Splash Pool Position with WhirlpoolClient
Opening a position in a splash pool is almost identical to opening a concentrated liquidity position. The only difference is where the lower and upper bounds are set. For splash pools, defined the position’s range to the global boundaries usingMIN_TICK_INDEX and MAX_TICK_INDEX.
TypeScript
The Manual way
Follow the instructions below if you would like to have more control over your instruction building process. Note thatopen_position does not add liquidity to a position. Follow the next article “Modify Liquidity” to add liquidity.
Determine position parameters
To open a position against a Whirlpool, you must first define certain parameters of your position to invoke theopen_position instruction.
WhirlpoolKey- The public key for the Whirlpool that the position will host liquidity in.tickLowerIndex,tickUpperIndex- The tick index bounds for the position. Must be an initializable index.positionMintAddress- A generated empty Keypair that will be initialized to a token mint.positionPda- Derived address of the position account viagetPositionPdapositionTokenAccountAddress- This is the account that will hold the minted position token. It is the associated token address of the position-mint.
TypeScript
3. Usage examples
Opening a Position in a Splash Pool
Suppose you want to provide 1,000,000 tokens of Token A at a price of 0.0001 SOL. You will also need to provide 100 SOL as Token B to match the price. By using the SDK to open full range positions, you ensure that your liquidity is spread evenly across all price levels. This approach is ideal if you are launching a new token and want to facilitate easy swaps for traders.Opening a Position in a Concentrated Liquidity Pool
If you want to maximize capital efficiency, you can open a position in a Concentrated Liquidity Pool. For example, if the current price is at 0.01 and you want to maximize profitability, you could use the SDK to deposit liquidity between the price range of 0.009 and 0.011. This approach allows you to focus your liquidity in a narrow range, making it more effective and potentially more profitable.Autonomous capital allocation
An LP management agent can useopenConcentratedPosition as its execution step after deciding on a strategy. For example: query the pool’s current price via Monitor Pools, compute an optimal price range based on recent volatility, calculate the deposit amount using tokenA or tokenB parameters, and open the position. The returned positionMint uniquely identifies the position for all subsequent monitoring, harvesting, and closing operations.
Next Steps
After opening a position, you can:- Add or Remove Liquidity: Adjust the amount of liquidity in your position based on market conditions.
- Harvest Rewards: Collect rewards and fees without closing the position.
- Monitor Performance: Track your position’s performance and earned fees.
- Close Position: When you decide to exit, close the position and withdraw the provided tokens along with any earned fees.
Common Errors
InvalidTickIndex(0x177a)- tickLowerIndex is higher than upper tickUpperIndex
- Some tick indices is not an initializable index (not a multiple of tickSpacing). Use
TickUtil.getInitializableTickIndexto get the closest initializable tick to your index. - Some tick indices is out of bounds
NotRentExempt(0x0)- Usually, the TickArray that houses your tickLowerIndex or tickUpperIndex has not been initialized. Use the
WhirlpoolClient.initTickArrayForTicksorWhirlpoolIx.initTickArrayIxto initialize the array at the derived startTickIndex. - Alternatively, if this failure is from
init_tick_array, the tick array has already been initialized.
- Usually, the TickArray that houses your tickLowerIndex or tickUpperIndex has not been initialized. Use the
