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

  1. 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

  2. Add or Remove Liquidity

    • Invoke the AddLiquidity , AddCompoudingLiquidity or RemoveLiquidity , 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:

Field
Description

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") or poolAddress to identify your desired pool.

  • Use flpPrice for the compounding FLP token and sFlpPrice 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?