LP Interactions
Creating LP Transactions
Add Liquidity / Mint sFLP
const addLiquidityAndStake = async () => {
const usdcInputAmount = new BN(1_000_000); // $1
// this can be any other token available in the pool, for instance SOL, BTC and ETH
const usdcCustody = POOL_CONFIG.custodies.find(c => c.symbol === 'USDC')!;
const slippageBps: number = 800 // 0.8%
let instructions: TransactionInstruction[] = []
let additionalSigners: Signer[] = []
await flashClient.loadAddressLookupTable(POOL_CONFIG)
// flash-sdk version >= 2.31.6
const { amount: minLpAmountOut, fee } = await flashClient.getAddLiquidityAmountAndFeeView(usdcInputAmount, POOL_CONFIG.poolAddress, usdcCustody.custodyAccount, POOL_CONFIG);
const minLpAmountOutAfterSlippage = minLpAmountOut
.mul(new BN(10 ** BPS_DECIMALS - slippageBps))
.div(new BN(10 ** BPS_DECIMALS))
const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }) // addLiquidity
const addLiquidityAndStakeData = await flashClient.addLiquidityAndStake('USDC', usdcInputAmount, minLpAmountOutAfterSlippage, POOL_CONFIG);
instructions.push(...addLiquidityAndStakeData.instructions)
additionalSigners.push(...addLiquidityAndStakeData.additionalSigners)
const flpStakeAccountPK = PublicKey.findProgramAddressSync(
[Buffer.from('stake'), flashClient.provider.publicKey.toBuffer(), POOL_CONFIG.poolAddress.toBuffer()],
POOL_CONFIG.programId
)[0]
const refreshStakeInstruction = await flashClient.refreshStake('USDC', POOL_CONFIG, [flpStakeAccountPK])
instructions.push(refreshStakeInstruction)
const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
console.log('addLiquidityAndStake trx :>> ', trxId);
}
Add Compounding Liquidity / Mint FLP
const addCompoundingLiquidity = async () => {
// USDC with its decimals
const usdcInputAmount = new BN(1_000_000); // $1
// this can be any other token available in the pool, for instance SOL, BTC and ETH
const usdcCustody = POOL_CONFIG.custodies.find(c => c.symbol === 'USDC')!;
const slippageBps: number = 800 // 0.8%
let instructions: TransactionInstruction[] = []
let additionalSigners: Signer[] = []
await flashClient.loadAddressLookupTable(POOL_CONFIG)
// flash-sdk version >= 2.31.6
const { amount: minLpAmountOut, fee } = await flashClient.getAddCompoundingLiquidityAmountAndFeeView(usdcInputAmount, POOL_CONFIG.poolAddress, usdcCustody.custodyAccount, POOL_CONFIG);
const minLpAmountOutAfterSlippage = minLpAmountOut
.mul(new BN(10 ** BPS_DECIMALS - slippageBps))
.div(new BN(10 ** BPS_DECIMALS))
const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }) // addLiquidity
const addCompoundingLiquidityData = await flashClient.addCompoundingLiquidity(
usdcInputAmount,
minLpAmountOutAfterSlippage,
'USDC',
usdcCustody.mintKey,
POOL_CONFIG
)
instructions.push(...addCompoundingLiquidityData.instructions)
additionalSigners.push(...addCompoundingLiquidityData.additionalSigners)
const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
console.log('addCompoundingLiquidity trx :>> ', trxId);
}
Remove Liquidity / Burn sFLP
const removeSflpLiquidity = async () => {
const usdcCustody = POOL_CONFIG.custodies.find(c => c.symbol === 'USDC')!;
const slippageBps: number = 800 // 0.8%
let instructions: TransactionInstruction[] = []
let additionalSigners: Signer[] = []
await flashClient.loadAddressLookupTable(POOL_CONFIG)
const flpStakeAccountPK = PublicKey.findProgramAddressSync(
[Buffer.from('stake'), flashClient.provider.publicKey.toBuffer(), POOL_CONFIG.poolAddress.toBuffer()],
POOL_CONFIG.programId
)[0]
const flpStakeAccount = await flashClient.program.account.flpStake.fetch(flpStakeAccountPK);
const flpWithPendingAndActive =
flpStakeAccount?.stakeStats.activeAmount.add(flpStakeAccount?.stakeStats.pendingActivation) ??
BN_ZERO
// flash-sdk version >= 2.31.6
const { amount: minTokenAmountOut, fee } = await flashClient.getRemoveLiquidityAmountAndFeeView(flpWithPendingAndActive, POOL_CONFIG.poolAddress, usdcCustody.custodyAccount, POOL_CONFIG);
const { instructions: unstakeInstantInstructions, additionalSigners: unstakeInstantAdditionalSigners } =
await flashClient.unstakeInstant('USDC', flpWithPendingAndActive, POOL_CONFIG)
const { instructions: withdrawStakeInstructions, additionalSigners: withdrawStakeAdditionalSigners } =
await flashClient.withdrawStake(POOL_CONFIG, true, true)
instructions.push(...unstakeInstantInstructions)
additionalSigners.push(...unstakeInstantAdditionalSigners)
instructions.push(...withdrawStakeInstructions)
additionalSigners.push(...withdrawStakeAdditionalSigners)
const minTokenAmountOutAfterSlippage = minTokenAmountOut
.mul(new BN(10 ** BPS_DECIMALS - slippageBps))
.div(new BN(10 ** BPS_DECIMALS))
const removeLiquidityData = await flashClient.removeLiquidity(
'USDC',
flpWithPendingAndActive,
minTokenAmountOutAfterSlippage,
POOL_CONFIG
)
instructions.push(...removeLiquidityData.instructions)
additionalSigners.push(...removeLiquidityData.additionalSigners)
const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }) // addLiquidity
const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
console.log('trx :>> ', trxId);
}
Remove Compounding Liquidity / Burn FLP
const removeFlpLiquidity = async () => {
const usdcCustody = POOL_CONFIG.custodies.find(c => c.symbol === 'USDC')!;
const slippageBps: number = 800 // 0.8%
let instructions: TransactionInstruction[] = []
let additionalSigners: Signer[] = []
const usdcToken = POOL_CONFIG.tokens.find(t => t.symbol === 'USDC')!;
await flashClient.loadAddressLookupTable(POOL_CONFIG)
const account = getAssociatedTokenAddressSync(POOL_CONFIG.compoundingTokenMint, flashClient.provider.publicKey, true)
const walletBalance = await flashClient.provider.connection.getTokenAccountBalance(account, 'processed')
const compoundingTokenBalance = new BN(walletBalance.value.amount)
// flash-sdk version >= 2.31.6
const { amount: minTokenAmountOut, fee } = await flashClient.getRemoveCompoundingLiquidityAmountAndFeeView(compoundingTokenBalance, POOL_CONFIG.poolAddress, usdcCustody.custodyAccount, POOL_CONFIG);
const minTokenAmountOutAfterSlippage = minTokenAmountOut
.mul(new BN(10 ** BPS_DECIMALS - slippageBps))
.div(new BN(10 ** BPS_DECIMALS))
const removeCompoundingLiquidityData = await flashClient.removeCompoundingLiquidity(
compoundingTokenBalance,
minTokenAmountOutAfterSlippage,
'USDC',
usdcToken.mintKey,
POOL_CONFIG,
true
)
instructions.push(...removeCompoundingLiquidityData.instructions)
additionalSigners.push(...removeCompoundingLiquidityData.additionalSigners)
const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }) // addLiquidity
const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
console.log('trx :>> ', trxId);
}
Update FLP/sFLP Price
const setLpTokenPrice = async () => {
await flashClient.loadAddressLookupTable(POOL_CONFIG)
let instructions: TransactionInstruction[] = []
let additionalSigners: Signer[] = []
const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 }) // setLpTokenPrice
// flash-sdk version >= "3.1.10"
const setLpTokenPriceData = await flashClient.setLpTokenPrice(POOL_CONFIG);
instructions.push(...setLpTokenPriceData.instructions)
additionalSigners.push(...setLpTokenPriceData.additionalSigners)
const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
console.log('setLpTokenPrice trx :>> ', trxId);
}
Composing Program Calls for Add/Remove Liquidity
Solana transaction instructions have strict size limits, and current runtime constraints prevent using Address Lookup Tables (LUTs) within inner instructions. To work around this, split the Add/Remove Liquidity flow into two separate instruction sequences that execute in order.
Overview
Prepare LP Token Price
Use the
setLpTokenPrice
instruction to update or initialise the price data for your pool's LP token.This instruction must run before any liquidity operations that depend on the latest price.
Example : refer the above example of
Update FLP/sFLP Price
Add or Remove Liquidity
Invoke the
AddLiquidity
,AddCompoudingLiquidity
orRemoveLiquidity
,RemoveCompoudingLiquidity
instruction in after the LP Token price instuction.By separating the price‐setting logic, you avoid passing large lists of remaining accounts in a single instruction, keeping each instruction well under the size limit.
Example : Add Compounding Liquidity instruction without passing any remaining accounts
let cpi_program = ctx.accounts.perp_program.to_account_info();
let acl_context = Box::new(CpiContext::new(
cpi_program.clone(),
AddCompoundingLiquidity {
owner: ctx.accounts.owner.to_account_info(),
funding_account: ctx.accounts.funding_account.to_account_info(),
compounding_token_account: ctx.accounts.compounding_token_account.to_account_info(),
pool_compounding_lp_vault: ctx.accounts.pool_compounding_lp_vault.to_account_info(),
transfer_authority: ctx.accounts.transfer_authority.to_account_info(),
perpetuals: ctx.accounts.perpetuals.to_account_info(),
pool: ctx.accounts.pool.to_account_info(),
in_custody: ctx.accounts.in_custody.to_account_info(),
in_custody_oracle_account: ctx.accounts.in_custody_oracle_account.to_account_info(),
in_custody_token_account: ctx.accounts.in_custody_token_account.to_account_info(),
reward_custody: ctx.accounts.reward_custody.to_account_info(),
reward_custody_oracle_account: ctx.accounts.reward_custody_oracle_account.to_account_info(),
lp_token_mint: ctx.accounts.lp_token_mint.to_account_info(),
compounding_token_mint: ctx.accounts.compounding_token_mint.to_account_info(),
token_program: ctx.accounts.token_program.to_account_info(),
event_authority: ctx.accounts.event_authority.to_account_info(),
program: ctx.accounts.perp_program.to_account_info(),
ix_sysvar: ctx.accounts.ix_sysvar.to_account_info(),
funding_mint: ctx.accounts.funding_mint.to_account_info(),
funding_token_program: ctx.accounts.funding_token_program.to_account_info(),
},
));
let acl_params = Box::new(AddCompoundingLiquidityParams {
amount_in: params.amount_in,
min_compounding_amount_out: params.min_compounding_amount_out,
});
let compounding_amount = perpetuals::cpi::add_compounding_liquidity(*acl_context, *acl_params)?.get();
Get FLP/sFLP token prices
1) Using SDK (Advanced Integration)
const getLpTokenPrices = async () => {
await flashClient.loadAddressLookupTable(POOL_CONFIG)
const stakedLpPrice = await flashClient.getStakedLpTokenPrice(POOL_CONFIG.poolAddress, POOL_CONFIG); // sFLP price
const compoundingLPTokenPrice = await flashClient.getCompoundingLPTokenPrice(POOL_CONFIG.poolAddress, POOL_CONFIG); // FLP price
console.log('stakedLpPrice :>> ', stakedLpPrice);
console.log('compoundingLPTokenPrice :>> ', compoundingLPTokenPrice);
}
2) Using API (Quick & Easy)
Query the following endpoint to get price data for all pools:
https://api.prod.flash.trade/earn-page/data
✅ Response Format (Example)
The response is a JSON object containing an array of pools
, each representing a unique FLP pool. Here's what each key means:
poolAddress
The unique on-chain address of the liquidity pool
aum
Assets under management (in USD) for this pool
flpTokenSymbol
Symbol for the compounding FLP token (e.g., FLP.1
, FLP.2
)
sFlpTokenSymbol
Symbol for the staked FLP token (e.g., sFLP.1
, sFLP.2
)
flpDailyApy
Daily APY for compounding FLP token (auto-reinvested)
flpWeeklyApy
Weekly APY for compounding FLP token
flpWeeklyApr
Weekly APR for compounding FLP token (non-compounding)
sFlpDailyApr
Daily APR for staked FLP token (non-compounding)
sFlpPrice
Current price of staked FLP token
flpPrice
Current price of compounding FLP token
sFlpPriceWithYield
Derived price of sFLP token if yield is included in valuation
🔎 How to Use
To get token prices:
Match the
flpTokenSymbol
(e.g.,"FLP.1"
) orpoolAddress
to identify your desired pool.Use
flpPrice
for the compounding FLP token andsFlpPrice
for the staked version.
This is the easiest method for frontends and dashboards needing real-time pricing and yield data without setting up the SDK or querying the blockchain.
Last updated
Was this helpful?