> 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-v2/lp-interactions.md).

# LP Interactions

{% hint style="info" %}

#### **Two LP tokens — pick one before you deposit:**

* **FLP — auto-compounding** (`addCompoundingLiquidityWithAction`). Mints a token whose value grows as fees compound automatically. **No manual claim step.**
* **sFLP — staked** (`addLiquidityAndStakeWithAction`). Earns a daily yield you claim separately via `collectStakeRewardWithAction`.

You can move between the two with `migrateStakeWithAction` (sFLP → FLP) and `migrateFlpWithAction` (FLP → sFLP).
{% endhint %}

Liquidity is **pool-scoped** — each pool has its own FLP/sFLP mints and custodies (`poolConfig.compoundingTokenMint` is the FLP mint). Set up `flashClient`, `poolConfig`, and `walletKeypair` as shown on the [Flash SDK v2 landing page](/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk-v2.md#setting-up-the-client). Every snippet assumes they're in scope, and that you've loaded the pool's [address lookup tables](/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk-v2.md#sending-transactions) into an `alts` array (LP transactions are large).

### The flow model

Each liquidity operation is a multi-step flow you drive yourself. You submit a `*WithAction` transaction, wait for its **receipt** to be ready, submit the matching `*Er` commit, then wait for settlement and read the outcome. Pass `queueErAction: false` so you stay in control of every step.

```
1. submit   addCompoundingLiquidityWithAction   sendAndConfirmTransaction
2. wait      receipt becomes ready
3. commit   addCompoundingLiquidityEr           sendAndConfirmErTransaction (throwaway payer)
4. settle    receipt closes → read the outcome
```

{% hint style="warning" %}
**A confirmed transaction is not a successful deposit.** On a slippage or cap miss the program refunds you and the transaction still succeeds. Always read the receipt's decision field before treating the mint/burn as done.
{% endhint %}

Helper pollers used by every flow (a missing account reads back with zero lamports, not `null`):

```ts
const exists = (info: { lamports: number } | null) => info !== null && info.lamports > 0
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))

const pollReady = async (pk: PublicKey, timeoutMs = 30_000) => {
  const deadline = Date.now() + timeoutMs
  while (Date.now() < deadline) {
    if (exists(await flashClient.erConnection!.getAccountInfo(pk).catch(() => null))) return
    await sleep(1_000)
  }
  throw new Error(`receipt ${pk.toBase58()} not ready`)
}

const pollClosed = async (pk: PublicKey, timeoutMs = 60_000) => {
  const deadline = Date.now() + timeoutMs
  let seen = false
  while (Date.now() < deadline) {
    const info = await flashClient.connection.getAccountInfo(pk).catch(() => null)
    if (exists(info)) seen = true
    else if (seen) return // existed, now gone → settled
    await sleep(3_000)
  }
  throw new Error(`receipt ${pk.toBase58()} never closed`)
}
```

Each operation pairs a `*WithAction` builder with an `*Er` commit, a `*Settle`, and a receipt-address finder:

| Operation              | submit (`*WithAction`)                 | commit (`*Er`)                 | settle                             | receipt finder                         |
| ---------------------- | -------------------------------------- | ------------------------------ | ---------------------------------- | -------------------------------------- |
| Mint FLP (compounding) | `addCompoundingLiquidityWithAction`    | `addCompoundingLiquidityEr`    | `addCompoundingLiquiditySettle`    | `findCompDepositReceiptAddress`        |
| Burn FLP               | `removeCompoundingLiquidityWithAction` | `removeCompoundingLiquidityEr` | `removeCompoundingLiquiditySettle` | `findCompWithdrawReceiptAddress`       |
| Mint sFLP (staked)     | `addLiquidityAndStakeWithAction`       | `addLiquidityAndStakeEr`       | `addLiquidityAndStakeSettle`       | `findStakingDepositReceiptAddress`     |
| Burn sFLP              | `removeLiquidityWithAction`            | `removeLiquidityEr`            | `removeLiquiditySettle`            | `findStakingWithdrawReceiptAddress`    |
| sFLP → FLP             | `migrateStakeWithAction`               | `migrateStakeEr`               | `migrateStakeSettle`               | `findMigrateStakeReceiptAddress`       |
| FLP → sFLP             | `migrateFlpWithAction`                 | `migrateFlpEr`                 | `migrateFlpSettle`                 | `findMigrateFlpReceiptAddress`         |
| Collect sFLP rewards   | `collectStakeRewardWithAction`         | —                              | —                                  | `findCollectStakeRewardReceiptAddress` |

{% hint style="info" %}
Operations that produce **staked sFLP** — minting sFLP, and the FLP → sFLP / sFLP → FLP migrations — need one extra step after settlement: `refreshStakeEr`, which activates the newly staked amount. See [Activating a stake](#activating-a-stake) below.
{% endhint %}

### Mint FLP (auto-compounding)

The full four-step flow. Other operations follow the identical shape — only the builders and receipt change.

```ts
import { Keypair } from '@solana/web3.js'
import { getAssociatedTokenAddressSync, createAssociatedTokenAccountIdempotentInstruction } from '@solana/spl-token'
import { findCompDepositReceiptAddress } from '@flash_trade/flash-sdk-v2'

const mintFlp = async (inSymbol: string, amountIn: BN) => {
  const owner = flashClient.wallet
  const inCustody = poolConfig.custodies.find(
    (c) => c.mintKey.equals(poolConfig.getTokenFromSymbol(inSymbol).mintKey),
  )!
  const fundingAccount = getAssociatedTokenAddressSync(inCustody.mintKey, owner)
  const flpAccount = getAssociatedTokenAddressSync(poolConfig.compoundingTokenMint, owner) // FLP ATA
  const [receipt] = findCompDepositReceiptAddress(owner, inCustody.mintKey, flashClient.programId)

  // 1) Submit. Prepend an idempotent FLP ATA-create so the mint has a destination.
  const res = await flashClient.addCompoundingLiquidityWithAction(poolConfig, {
    inSymbol,
    fundingAccount,
    compoundingTokenAccount: flpAccount,
    amountIn,
    minCompoundingAmountOut: new BN(0), // set from a view quote + slippage
    queueErAction: false,
  })
  const createAtaIx = createAssociatedTokenAccountIdempotentInstruction(
    owner, flpAccount, owner, poolConfig.compoundingTokenMint,
  )
  await flashClient.sendAndConfirmTransaction([createAtaIx, ...res.instructions], {
    additionalSigners: res.additionalSigners, skipPreflight: true, alts,
  })

  // 2) Wait for the receipt.
  await pollReady(receipt)

  // 3) Commit, signed by a throwaway payer.
  const erPayer = Keypair.generate()
  const erRes = await flashClient.addCompoundingLiquidityEr(poolConfig, {
    inSymbol, fundingAccount, compoundingTokenAccount: flpAccount, payer: erPayer.publicKey,
  })
  await flashClient.sendAndConfirmErTransaction(erRes.instructions, [erPayer])

  // 4) Wait for settlement.
  await pollClosed(receipt)
}
```

### Mint sFLP (staked)

sFLP isn't minted to an ATA — it's staked. After settlement, **activate the stake** with `refreshStakeEr`.

```ts
import { findStakingDepositReceiptAddress } from '@flash_trade/flash-sdk-v2'

const mintSflp = async (inSymbol: string, amountIn: BN) => {
  const owner = flashClient.wallet
  const inCustody = poolConfig.custodies.find(
    (c) => c.mintKey.equals(poolConfig.getTokenFromSymbol(inSymbol).mintKey),
  )!
  const fundingAccount = getAssociatedTokenAddressSync(inCustody.mintKey, owner)
  const [receipt] = findStakingDepositReceiptAddress(owner, inCustody.mintKey, flashClient.programId)

  const res = await flashClient.addLiquidityAndStakeWithAction(poolConfig, {
    inSymbol, fundingAccount, amountIn, minLpAmountOut: new BN(0), queueErAction: false,
  })
  await flashClient.sendAndConfirmTransaction(res.instructions, {
    additionalSigners: res.additionalSigners, skipPreflight: true, alts,
  })

  await pollReady(receipt)

  const erPayer = Keypair.generate()
  const erRes = await flashClient.addLiquidityAndStakeEr(poolConfig, {
    inSymbol, fundingAccount, payer: erPayer.publicKey,
  })
  await flashClient.sendAndConfirmErTransaction(erRes.instructions, [erPayer])

  await pollClosed(receipt)

  // Activate the newly staked sFLP (see "Activating a stake").
  await activateStake()
}
```

### Activating a stake

`refreshStakeEr` promotes a newly staked amount to active. Call it after minting sFLP, and after either migration. It's a single commit signed by a throwaway payer:

```ts
const activateStake = async () => {
  const erPayer = Keypair.generate()
  const { instructions } = await flashClient.refreshStakeEr(poolConfig, { payer: erPayer.publicKey })
  await flashClient.sendAndConfirmErTransaction(instructions, [erPayer])
}
```

### Burn FLP

```ts
import { findCompWithdrawReceiptAddress } from '@flash_trade/flash-sdk-v2'

const outCustody = poolConfig.custodies.find(
  (c) => c.mintKey.equals(poolConfig.getTokenFromSymbol('USDC').mintKey),
)!
const receivingAccount = getAssociatedTokenAddressSync(outCustody.mintKey, flashClient.wallet)
const flpAccount = getAssociatedTokenAddressSync(poolConfig.compoundingTokenMint, flashClient.wallet)

await flashClient.removeCompoundingLiquidityWithAction(poolConfig, {
  outSymbol: 'USDC',
  receivingAccount,
  compoundingTokenAccount: flpAccount,
  compoundingAmountIn: new BN(1_000_000), // FLP to burn
  minAmountOut: new BN(0),
  queueErAction: false,
})
// …then pollReady → removeCompoundingLiquidityEr([erPayer]) → pollClosed
```

### Burn sFLP

```ts
import { findStakingWithdrawReceiptAddress } from '@flash_trade/flash-sdk-v2'

await flashClient.removeLiquidityWithAction(poolConfig, {
  outSymbol: 'USDC',
  receivingAccount,
  unstakeAmount: new BN(1_000_000), // staked LP to unstake + burn
  minAmountOut: new BN(0),
  queueErAction: false,
})
// …then pollReady → removeLiquidityEr([erPayer]) → pollClosed
```

### Collect sFLP staking rewards

sFLP earns a daily yield as a claimable balance. FLP holders don't need this — fees auto-compound into the token's value.

```ts
const receivingTokenAccount = getAssociatedTokenAddressSync(
  poolConfig.getTokenFromSymbol('USDC').mintKey, flashClient.wallet,
)
const res = await flashClient.collectStakeRewardWithAction(poolConfig, {
  receivingTokenAccount,
  rewardSymbol: 'USDC',     // optional, defaults to USDC
  includeTokenStake: false, // pass true to apply your FAF stake fee boost
})
await flashClient.sendAndConfirmTransaction(res.instructions, { additionalSigners: res.additionalSigners, alts })
```

### Migrate between FLP and sFLP

The migration that produces **sFLP** (`migrateFlpWithAction`, FLP → sFLP) — and the reverse — need `refreshStakeEr` after settlement.

```ts
const flpAccount = getAssociatedTokenAddressSync(poolConfig.compoundingTokenMint, flashClient.wallet)

// sFLP → FLP (staked → compounding)
await flashClient.migrateStakeWithAction(poolConfig, {
  compoundingTokenAccount: flpAccount,
  amount: new BN(1_000_000), // staked sFLP to migrate
  queueErAction: false,
})
// …pollReady → migrateStakeEr([erPayer]) → pollClosed → activateStake()

// FLP → sFLP (compounding → staked)
await flashClient.migrateFlpWithAction(poolConfig, {
  compoundingTokenAccount: flpAccount,
  compoundingTokenAmount: new BN(1_000_000), // FLP to migrate
  queueErAction: false,
})
// …pollReady → migrateFlpEr([erPayer]) → pollClosed → activateStake()
```

### Reading the outcome / resuming

After settlement, read the receipt's decision field from raw account bytes (a refund zeroes it). If a flow is interrupted, re-run it in **resume** mode: check whether the receipt is still pending its commit (drive the `*Er` step) or already processed (run the matching `*Settle`).

{% hint style="info" %}
A still-open receipt blocks new operations of the same type for that wallet, so always finalize (or resume) before retrying.
{% endhint %}

### Quotes (views)

Quote amounts and fees before depositing, then derive `minAmountOut` from a slippage tolerance:

```ts
// AmountAndFee = { amount: BN, fee: BN }
const sflpAdd    = await flashClient.views.getAddLiquidityAmountAndFee(poolConfig, { symbol: 'USDC', amountIn: new BN(1_000_000) })
const sflpRemove = await flashClient.views.getRemoveLiquidityAmountAndFee(poolConfig, { symbol: 'USDC', lpAmountIn: new BN(1_000_000) })
const flpAdd     = await flashClient.views.getAddCompoundingLiquidityAmountAndFee(poolConfig, { inSymbol: 'USDC', amountIn: new BN(1_000_000) })
const flpRemove  = await flashClient.views.getRemoveCompoundingLiquidityAmountAndFee(poolConfig, { outSymbol: 'USDC', compoundingAmountIn: new BN(1_000_000) })

const lpPrice  = await flashClient.views.getLpTokenPrice(poolConfig)          // BN — staked LP (sFLP) price
const flpPrice = await flashClient.views.getCompoundingTokenPrice(poolConfig) // BN — FLP price

const slippageBps = new BN(100) // 1%
const minOut = flpAdd.amount.mul(new BN(10_000).sub(slippageBps)).div(new BN(10_000))
```


---

# 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-v2/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.
