> For the complete documentation index, see [llms.txt](https://docs.flash.trade/flash-trade/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.flash.trade/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk/lp-interactions.md).

# LP Interactions

{% hint style="warning" %}

#### Note

Your primary method should be through the [Flash Trade API](/flash-trade/flash-trade-protocol/build-on-flash/flash-trade-api.md) — the SDK is the secondary method.&#x20;
{% endhint %}

{% hint style="info" %}

#### **Pick one before you deposit:**

* **sFLP (staked LP)** — `addLiquidityAndStake` mints LP and stakes it in one tx. Pool fees accrue as a *claimable* USDC balance you collect via `collectStakeFees`. Unstaking goes through `unstakeInstant` + `withdrawStake` before the underlying tokens can be returned.
* **FLP (compounding LP)** — `addCompoundingLiquidity` mints a rebasing SPL token whose price grows as fees accrue. Burn it with `removeCompoundingLiquidity`. **No manual claim step.**

Unless you specifically want manual control over rewards, prefer **FLP**.
{% endhint %}

Liquidity is **pool-scoped** — each pool has its own sFLP / FLP mints and its own custodies. The examples below use a single `POOL_CONFIG` (e.g. `PoolConfig.fromIdsByName('Crypto.1', 'mainnet-beta')`). Set this up the same way as the [Flash SDK landing page](/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk.md#install-the-flash-sdk), but pick one pool instead of loading the full `POOL_CONFIGS` array.

### Creating LP Transactions

#### Add Liquidity / Mint sFLP

```typescript
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 })

    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

```typescript
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 })

    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

```typescript
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 })

    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
    console.log('trx :>> ', trxId);
}
```

#### Collect sFLP stake fees

sFLP holders accrue USDC rewards in their stake account. Use `collectStakeFees` to sweep them to your USDC ATA. (FLP holders don't need this — fees auto-compound into the token's price.)

```typescript
const collectStakeFees = async () => {
    const { instructions, additionalSigners } = await flashClient.collectStakeFees(
        'USDC',
        POOL_CONFIG,
    )

    const cuLimit = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 })
    const alts = (await flashClient.getOrLoadAddressLookupTable(POOL_CONFIG)).addressLookupTables

    const trxId = await flashClient.sendTransaction(
        [cuLimit, ...instructions],
        { alts, additionalSigners },
    )
    console.log(`https://explorer.solana.com/tx/${trxId}`)
}
```

#### Remove Compounding Liquidity / Burn FLP

```typescript
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 })

    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
    console.log('trx :>> ', trxId);
}
```

#### Update FLP/sFLP Price

{% hint style="warning" %}
**Keeper-only.** Normal users don't call `setLpTokenPrice` — it's invoked by the protocol's price-update keepers (and CPI'd by Composability flows shown below). The snippet is included here for completeness and for integrators building their own composability layer.
{% endhint %}

```typescript
const setLpTokenPrice = async () => {
    await flashClient.loadAddressLookupTable(POOL_CONFIG)

    let instructions: TransactionInstruction[] = []
    let additionalSigners: Signer[] = []
    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 400_000 })

    // 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 to the `Update FLP/sFLP Price` section above.
2. **Add or Remove Liquidity**
   * Invoke the `AddLiquidityAndStake`, `AddCompoundingLiquidity`, `RemoveLiquidity`, or `RemoveCompoundingLiquidity` instruction after the LP token price instruction.
   * 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:

```rust
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)**

```typescript
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
```

The response is a JSON object containing an array of `pools`, each representing a unique FLP pool.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.flash.trade/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk/lp-interactions.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
