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

# Trader 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.
{% endhint %}

{% hint style="success" %}
For more examples checkout our [TS SDK](https://github.com/flash-trade/flash-trade-sdk) and our [Rust SDK](https://github.com/flash-trade/flash-sdk-rust).
{% endhint %}

### Install Dependencies

```bash
yarn add flash-sdk @coral-xyz/anchor @solana/web3.js @solana/spl-token dotenv
```

Set up the `flashClient` and `POOL_CONFIGS` array as shown on the [Flash SDK landing page](/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk.md#load-every-pool-up-front). Every snippet on this page assumes both are in scope.

### Pool auto-routing

All trade functions below use `getRequiredPoolConfig(targetSymbol, side)` and `getPoolConfigForMarket(marketPk)` to pick the right pool dynamically. Both helpers are defined on the [Flash SDK landing page](/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk.md#picking-the-right-pool-per-trade) — import them once and reuse.

### Fetch prices via Pyth Lazer

Every trade function needs the latest oracle prices (for slippage / size calc). Flash now uses the [Pyth Lazer](https://pyth.network/lazer) HTTP proxy — one GET returns prices for every token in every pool. No Pythnet RPC, no `PythHttpClient`.

```ts
import { BN_ZERO, OraclePrice, PoolConfig } from 'flash-sdk'
import { BN } from '@coral-xyz/anchor'

export const LAZER_PROXY_BASE_URL =
    process.env.LAZER_PROXY_URL ?? 'https://pyth-lazer-proxy-3.dourolabs.app'

interface LazerV1Feed {
    priceFeedId: number
    price: string
    exponent: number
    confidence: number
    marketSession: string
    feedUpdateTimestamp: number // microseconds
}

interface LazerV1Response {
    timestampUs: string
    priceFeeds: LazerV1Feed[]
}

type PoolToken = PoolConfig['tokens'][number]

/**
 * Returns Map<tokenSymbol, { price, emaPrice }> for every token across every pool.
 * Lazer V1 has no separate EMA price — we reuse `price` for both (same trick
 * flash-main-ui uses in workers/lazerPrice.worker.ts).
 */
export const getPrices = async () => {
    const lazerIdToTokens = new Map<number, PoolToken[]>()
    for (const config of POOL_CONFIGS) {
        for (const token of config.tokens) {
            const list = lazerIdToTokens.get(token.lazerId) ?? []
            list.push(token)
            lazerIdToTokens.set(token.lazerId, list)
        }
    }

    const uniqueIds = Array.from(lazerIdToTokens.keys())
    const feedParams = uniqueIds.map((id) => `price_feed_ids=${id}`).join('&')
    const url = `${LAZER_PROXY_BASE_URL}/v1/latest_price?${feedParams}`

    const response = await fetch(url)
    if (!response.ok) {
        throw new Error(`Lazer latest_price HTTP ${response.status} ${response.statusText}`)
    }
    const json = (await response.json()) as LazerV1Response

    const priceMap = new Map<string, { price: OraclePrice; emaPrice: OraclePrice }>()
    for (const feed of json.priceFeeds) {
        const matchedTokens = lazerIdToTokens.get(feed.priceFeedId)
        if (!matchedTokens) continue
        const priceOracle = new OraclePrice({
            price: new BN(feed.price),
            exponent: new BN(feed.exponent),
            confidence: new BN(feed.confidence.toString()),
            timestamp: new BN(Math.floor(feed.feedUpdateTimestamp / 1_000_000)), // µs → s
        })
        for (const token of matchedTokens) {
            priceMap.set(token.symbol, { price: priceOracle, emaPrice: priceOracle })
        }
    }
    return priceMap
}
```

{% hint style="info" %}
The default proxy is public. For production, set `LAZER_PROXY_URL` to your own deployment.
{% endhint %}

### Privilege accounts (Referral & Stake)

Every trade function in this SDK accepts a `(privilege, tokenStakeAccount, userReferralAccount)` triplet. These determine the trader's fee tier:

| `Privilege`          | When                                               | `tokenStakeAccount`                                                      | `userReferralAccount`       |
| -------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ | --------------------------- |
| `Privilege.Stake`    | Trader holds an active FAF token-stake (level ≥ 1) | trader's own `token_stake` PDA                                           | trader's own `referral` PDA |
| `Privilege.Referral` | Trader was referred by another user                | the **referrer's** stake (read from `referral.refererTokenStakeAccount`) | trader's own `referral` PDA |
| `Privilege.None`     | Default                                            | `PublicKey.default`                                                      | `PublicKey.default`         |

{% hint style="info" %}
`Privilege.NFT` is deprecated — use `Stake` or `Referral` instead.
{% endhint %}

#### Deriving the PDAs

Seeds are `['referral', wallet]` and `['token_stake', wallet]`. All mainnet pools share the same `programId`, so you can use `POOL_CONFIGS[0].programId` (or any pool's `programId`).

```ts
import { PublicKey } from '@solana/web3.js'

const PROGRAM_ID = POOL_CONFIGS[0].programId

// PDA for the trader's own referral account.
export const getUserReferralAccountPK = (publicKey: PublicKey) => {
    return PublicKey.findProgramAddressSync(
        [Buffer.from('referral'), publicKey.toBuffer()],
        PROGRAM_ID,
    )[0]
}

// PDA for the trader's own FAF token-stake account.
export const getFafTokenStakeAccountPk = (publicKey: PublicKey) => {
    return PublicKey.findProgramAddressSync(
        [Buffer.from('token_stake'), publicKey.toBuffer()],
        PROGRAM_ID,
    )[0]
}
```

#### Resolving the triplet automatically

Call this once before every trade. It picks the highest privilege tier the wallet actually qualifies for.

```ts
import { Privilege, Referral, TokenStake } from 'flash-sdk'

const resolvePrivilegeAccounts = async (
    trader: PublicKey,
): Promise<{
    privilege: Privilege
    tokenStakeAccount: PublicKey
    userReferralAccount: PublicKey
}> => {
    const userReferralAccountPk = getUserReferralAccountPK(trader)
    const userTokenStakeAccountPk = getFafTokenStakeAccountPk(trader)

    const [referralInfo, tokenStakeInfo] = await connection.getMultipleAccountsInfo([
        userReferralAccountPk,
        userTokenStakeAccountPk,
    ])

    // 1) Own stake takes priority.
    if (tokenStakeInfo) {
        const tokenStake = flashClient.program.coder.accounts.decode<TokenStake>(
            'tokenStake',
            tokenStakeInfo.data,
        )
        if (tokenStake.level >= 1) {
            return {
                privilege: Privilege.Stake,
                tokenStakeAccount: userTokenStakeAccountPk,
                userReferralAccount: userReferralAccountPk,
            }
        }
    }

    // 2) Fall back to a referrer.
    if (referralInfo) {
        const referral = flashClient.program.coder.accounts.decode<Referral>(
            'referral',
            referralInfo.data,
        )
        // refererTokenStakeAccount points at the REFERRER's stake PDA.
        return {
            privilege: Privilege.Referral,
            tokenStakeAccount: referral.refererTokenStakeAccount,
            userReferralAccount: userReferralAccountPk,
        }
    }

    // 3) No privilege.
    return {
        privilege: Privilege.None,
        tokenStakeAccount: PublicKey.default,
        userReferralAccount: PublicKey.default,
    }
}
```

#### Wiring it into a trade

Every trade snippet on this page uses the same pattern:

```ts
const { privilege, tokenStakeAccount, userReferralAccount } =
    await resolvePrivilegeAccounts(flashClient.provider.publicKey)

await flashClient.openPosition(
    /* …other args… */,
    privilege,
    tokenStakeAccount,
    userReferralAccount,
)
```

If you don't care about discounts (or are testing), pass `Privilege.None` and `PublicKey.default` for both account args.

### Open a position

Use this when the input token IS the market's collateral (no swap needed). For everything else, see [Open Position (Auto)](/flash-trade/flash-trade-protocol/build-on-flash/flash-sdk/trader-interactions.md#open-a-position-auto-routed).

```ts
import { BN_ZERO, CustodyAccount, Side, uiDecimalsToNative } from 'flash-sdk'
import { BN } from '@coral-xyz/anchor'
import { ComputeBudgetProgram } from '@solana/web3.js'

const openPosition = async (
    inputTokenSymbol: string,
    outputTokenSymbol: string,
    inputAmount: string,
    side: Side,
    leverage: number,
) => {
    const slippageBps = 800 // 0.8%

    // 1) Resolve pool from (target, side).
    const poolConfig = getRequiredPoolConfig(outputTokenSymbol, side)
    const inputToken  = poolConfig.tokens.find(t => t.symbol === inputTokenSymbol)!
    const outputToken = poolConfig.tokens.find(t => t.symbol === outputTokenSymbol)!

    // 2) Prices.
    const priceMap = await getPrices()
    const inputTokenPrice  = priceMap.get(inputToken.symbol)!.price
    const inputTokenPriceEma = priceMap.get(inputToken.symbol)!.emaPrice
    const outputTokenPrice = priceMap.get(outputToken.symbol)!.price
    const outputTokenPriceEma = priceMap.get(outputToken.symbol)!.emaPrice

    const priceAfterSlippage = flashClient.getPriceAfterSlippage(
        true, new BN(slippageBps), outputTokenPrice, side,
    )

    // 3) Size from leverage + collateral.
    const collateralWithFee = uiDecimalsToNative(inputAmount, inputToken.decimals)
    const inputCustody  = poolConfig.custodies.find(c => c.symbol === inputToken.symbol)!
    const outputCustody = poolConfig.custodies.find(c => c.symbol === outputToken.symbol)!
    const custodies = await flashClient.program.account.custody.fetchMultiple([
        inputCustody.custodyAccount, outputCustody.custodyAccount,
    ])

    const outputAmount = flashClient.getSizeAmountFromLeverageAndCollateral(
        collateralWithFee,
        leverage.toString(),
        outputToken, inputToken, side,
        outputTokenPrice, outputTokenPriceEma,
        CustodyAccount.from(outputCustody.custodyAccount, custodies[1]!),
        inputTokenPrice, inputTokenPriceEma,
        CustodyAccount.from(inputCustody.custodyAccount, custodies[0]!),
        BN_ZERO,
    )

    // 4) Privilege accounts.
    const { privilege, tokenStakeAccount, userReferralAccount } =
        await resolvePrivilegeAccounts(flashClient.provider.publicKey)

    // 5) Build + send.
    const { instructions, additionalSigners } = await flashClient.openPosition(
        outputToken.symbol,
        inputToken.symbol,
        priceAfterSlippage,
        collateralWithFee,
        outputAmount,
        side,
        poolConfig,
        privilege,
        tokenStakeAccount,
        userReferralAccount,
    )

    const cuLimit = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 })
    const cuPrice = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 50 })
    const alts = (await flashClient.getOrLoadAddressLookupTable(poolConfig)).addressLookupTables

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

// Example: 3x long SOL with 0.1 SOL collateral.
await openPosition('SOL', 'SOL', '0.1', Side.Long, 3)
```

### Open a position (auto-routed)

Thin dispatcher: figures out from the `(input, output, side)` triple whether it needs `openPosition` or `openPositionWithSwap` and calls the right one. Use this when you don't want to think about whether the input token matches the market's collateral.

```ts
const openPositionAuto = async (
    inputTokenSymbol: string,
    outputTokenSymbol: string,
    inputAmount: string,
    side: Side,
    leverage: number,
) => {
    const poolConfig = getRequiredPoolConfig(outputTokenSymbol, side)

    const inputToken = poolConfig.tokens.find(t => t.symbol === inputTokenSymbol)
    if (!inputToken) {
        throw new Error(
            `Input token ${inputTokenSymbol} is not listed in pool ${poolConfig.poolName}. ` +
            `Pool tokens: ${poolConfig.tokens.map(t => t.symbol).join(', ')}.`,
        )
    }
    const outputToken = poolConfig.tokens.find(t => t.symbol === outputTokenSymbol)!

    // Is there a market with this exact target + side + collateral? If so, no swap.
    const directMarket = poolConfig.markets.find(
        m =>
            m.targetMint.equals(outputToken.mintKey) &&
            m.collateralMint.equals(inputToken.mintKey) &&
            isVariant(m.side, 'long') === isVariant(side, 'long'),
    )

    return directMarket
        ? openPosition(inputTokenSymbol, outputTokenSymbol, inputAmount, side, leverage)
        : openPositionWithSwap(inputTokenSymbol, outputTokenSymbol, inputAmount, side, leverage)
}

// Examples
await openPositionAuto('SOL',  'SOL',   '0.1', Side.Long, 3)   // no swap
await openPositionAuto('USDC', 'TRUMP', '25',  Side.Long, 2)   // swap-and-open
```

### Open a position with a swap

Used when the input token isn't already the market's collateral. The SDK runs the swap and the open in a single tx via `swapAndOpen`.

```ts
import { BN_ZERO, CustodyAccount, PoolAccount, PoolDataClient, Side, uiDecimalsToNative } from 'flash-sdk'
import { getMint } from '@solana/spl-token'

const openPositionWithSwap = async (
    inputTokenSymbol: string,
    outputTokenSymbol: string,
    inputAmount: string,
    side: Side,
    leverage: number,
) => {
    const slippageBps = 800

    const poolConfig = getRequiredPoolConfig(outputTokenSymbol, side)
    const inputToken  = poolConfig.tokens.find(t => t.symbol === inputTokenSymbol)!
    const outputToken = poolConfig.tokens.find(t => t.symbol === outputTokenSymbol)!

    const priceMap = await getPrices()
    const inputTokenPrice  = priceMap.get(inputToken.symbol)!.price
    const inputTokenPriceEma = priceMap.get(inputToken.symbol)!.emaPrice
    const outputTokenPrice = priceMap.get(outputToken.symbol)!.price
    const outputTokenPriceEma = priceMap.get(outputToken.symbol)!.emaPrice

    const priceAfterSlippage = flashClient.getPriceAfterSlippage(
        true, new BN(slippageBps), outputTokenPrice, side,
    )

    const collateralWithFee = uiDecimalsToNative(inputAmount, inputToken.decimals)
    const inputCustody  = poolConfig.custodies.find(c => c.symbol === inputToken.symbol)!
    const outputCustody = poolConfig.custodies.find(c => c.symbol === outputToken.symbol)!
    const custodies = await flashClient.program.account.custody.fetchMultiple([
        inputCustody.custodyAccount, outputCustody.custodyAccount,
    ])

    const poolAccount = PoolAccount.from(
        poolConfig.poolAddress,
        await flashClient.program.account.pool.fetch(poolConfig.poolAddress),
    )
    const allCustodies = await flashClient.program.account.custody.all()
    const lpMintData = await getMint(connection, poolConfig.stakedLpTokenMint)
    const poolDataClient = new PoolDataClient(
        poolConfig, poolAccount, lpMintData,
        allCustodies.map(c => CustodyAccount.from(c.publicKey, c.account)),
    )
    const lpStats = poolDataClient.getLpStats(priceMap)

    const inputCustodyAccount  = CustodyAccount.from(inputCustody.custodyAccount, custodies[0]!)
    const outputCustodyAccount = CustodyAccount.from(outputCustody.custodyAccount, custodies[1]!)

    const size = flashClient.getSizeAmountWithSwapSync(
        collateralWithFee, leverage.toString(), side, poolAccount,
        inputTokenPrice,  inputTokenPriceEma,  inputCustodyAccount,
        outputTokenPrice, outputTokenPriceEma, outputCustodyAccount,
        outputTokenPrice, outputTokenPriceEma, outputCustodyAccount,
        outputTokenPrice, outputTokenPriceEma, outputCustodyAccount,
        lpStats.totalPoolValueUsd, poolConfig, BN_ZERO,
    )

    const { privilege, tokenStakeAccount, userReferralAccount } =
        await resolvePrivilegeAccounts(flashClient.provider.publicKey)

    const { instructions, additionalSigners } = await flashClient.swapAndOpen(
        outputToken.symbol, outputToken.symbol, inputToken.symbol,
        collateralWithFee, priceAfterSlippage, size, side,
        poolConfig, privilege, tokenStakeAccount, userReferralAccount,
    )

    const cuLimit = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 })
    const alts = (await flashClient.getOrLoadAddressLookupTable(poolConfig)).addressLookupTables
    const trxId = await flashClient.sendTransaction([cuLimit, ...instructions], { alts, additionalSigners })
    console.log(`https://explorer.solana.com/tx/${trxId}`)
}

// Example: pay 25 USDC, swap+open 2x long TRUMP.
await openPositionWithSwap('USDC', 'TRUMP', '25', Side.Long, 2)
```

### Close a position

One unified call. Pass `options.userReceivingTokenSymbol` to receive the proceeds as a different token (triggers `closeAndSwap` automatically); omit it to receive the position's collateral directly.

```ts
import { AddressLookupTableAccount } from '@solana/web3.js'

const closePosition = async (
    positionPubKey: PublicKey,
    options: {
        userReceivingTokenSymbol?: string // defaults to the position's collateral
        slippageBps?: number              // defaults to 800 (0.8%)
    } = {},
) => {
    const slippageBps = options.slippageBps ?? 800

    // 1) Resolve position -> pool -> market -> tokens.
    const positionData = await flashClient.program.account.position.fetch(positionPubKey)
    const poolConfig   = getPoolConfigForMarket(positionData.market)
    const marketConfig =
        poolConfig.markets.find(m => m.marketAccount.equals(positionData.market)) ??
        poolConfig.marketsDeprecated.find(m => m.marketAccount.equals(positionData.market))
    if (!marketConfig) throw new Error(`market ${positionData.market.toBase58()} not found`)

    const targetToken      = poolConfig.tokens.find(t => t.mintKey.equals(marketConfig.targetMint))!
    const collateralToken  = poolConfig.tokens.find(t => t.mintKey.equals(marketConfig.collateralMint))!
    const side             = marketConfig.side!

    const receivingSymbol  = options.userReceivingTokenSymbol ?? collateralToken.symbol
    const userReceivingToken = poolConfig.tokens.find(t => t.symbol === receivingSymbol)!
    const needsSwap        = !userReceivingToken.mintKey.equals(collateralToken.mintKey)

    // 2) Price + slippage on the target leg.
    const priceMap = await getPrices()
    const priceAfterSlippage = flashClient.getPriceAfterSlippage(
        false, new BN(slippageBps), priceMap.get(targetToken.symbol)!.price, side,
    )

    // 3) Privilege.
    const { privilege, tokenStakeAccount, userReferralAccount } =
        await resolvePrivilegeAccounts(flashClient.provider.publicKey)

    // 4) Route.
    const closeData = needsSwap
        ? await flashClient.closeAndSwap(
            targetToken.symbol, userReceivingToken.symbol, collateralToken.symbol,
            priceAfterSlippage, side, poolConfig,
            privilege, tokenStakeAccount, userReferralAccount,
        )
        : await flashClient.closePosition(
            targetToken.symbol, userReceivingToken.symbol,
            priceAfterSlippage, side, poolConfig,
            privilege, tokenStakeAccount, userReferralAccount,
        )

    const cuLimit = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 })
    const alts: AddressLookupTableAccount[] =
        (await flashClient.getOrLoadAddressLookupTable(poolConfig)).addressLookupTables
    const trxId = await flashClient.sendTransaction(
        [cuLimit, ...closeData.instructions],
        { alts, additionalSigners: closeData.additionalSigners },
    )
    console.log(`https://explorer.solana.com/tx/${trxId}`)
}

// Close into collateral:
await closePosition(positionPk)

// Close + receive USDC instead:
await closePosition(positionPk, { userReceivingTokenSymbol: 'USDC' })

// Custom slippage:
await closePosition(positionPk, { slippageBps: 1500 })
```

{% hint style="info" %}
The pool is derived from the position's `market` field — callers don't need to know which pool a position lives in. Positions on retired markets still close because the lookup falls back to `marketsDeprecated`.
{% endhint %}

### Add collateral

Top up an existing position. If `depositTokenSymbol` differs from the position's collateral, the SDK swaps it in the same transaction via `swapAndAddCollateral`.

```ts
const addCollateral = async (
    positionPubKey: PublicKey,
    depositAmount: string,                // UI amount of the deposit token
    options: { depositTokenSymbol?: string } = {},
) => {
    const positionData = await flashClient.program.account.position.fetch(positionPubKey)
    const poolConfig   = getPoolConfigForMarket(positionData.market)
    const marketConfig =
        poolConfig.markets.find(m => m.marketAccount.equals(positionData.market)) ??
        poolConfig.marketsDeprecated.find(m => m.marketAccount.equals(positionData.market))!

    const targetToken     = poolConfig.tokens.find(t => t.mintKey.equals(marketConfig.targetMint))!
    const collateralToken = poolConfig.tokens.find(t => t.mintKey.equals(marketConfig.collateralMint))!
    const side            = marketConfig.side!

    const depositTokenSymbol = options.depositTokenSymbol ?? collateralToken.symbol
    const depositToken = poolConfig.tokens.find(t => t.symbol === depositTokenSymbol)!
    const needsSwap    = !depositToken.mintKey.equals(collateralToken.mintKey)

    const amountIn = uiDecimalsToNative(depositAmount, depositToken.decimals)

    const data = needsSwap
        ? await flashClient.swapAndAddCollateral(
            targetToken.symbol, depositToken.symbol, collateralToken.symbol,
            amountIn, side, positionPubKey, poolConfig,
        )
        : await flashClient.addCollateral(
            amountIn, targetToken.symbol, depositToken.symbol,
            side, positionPubKey, poolConfig,
        )

    const cuLimit = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 })
    const alts = (await flashClient.getOrLoadAddressLookupTable(poolConfig)).addressLookupTables
    const trxId = await flashClient.sendTransaction(
        [cuLimit, ...data.instructions],
        { alts, additionalSigners: data.additionalSigners },
    )
    console.log(`https://explorer.solana.com/tx/${trxId}`)
}

// Deposit 0.05 SOL into a SOL-collateralized position:
await addCollateral(positionPk, '0.05')

// Deposit 5 USDC and swap into the position's collateral:
await addCollateral(positionPk, '5', { depositTokenSymbol: 'USDC' })
```

### Remove collateral

Withdraw collateral from an existing position. If `withdrawTokenSymbol` differs from the collateral, the SDK swaps on the way out via `removeCollateralAndSwap`.

{% hint style="warning" %}
**The amount is denominated in USD, not collateral-token units.** The on-chain program reduces collateral by a USD value (with `USD_DECIMALS = 6`), so `'10'` means $10 worth.
{% endhint %}

```ts
import { USD_DECIMALS } from 'flash-sdk'

const removeCollateral = async (
    positionPubKey: PublicKey,
    withdrawAmountUsd: string,            // UI USD amount (e.g. '10' = $10)
    options: { withdrawTokenSymbol?: string } = {},
) => {
    const positionData = await flashClient.program.account.position.fetch(positionPubKey)
    const poolConfig   = getPoolConfigForMarket(positionData.market)
    const marketConfig =
        poolConfig.markets.find(m => m.marketAccount.equals(positionData.market)) ??
        poolConfig.marketsDeprecated.find(m => m.marketAccount.equals(positionData.market))!

    const targetToken     = poolConfig.tokens.find(t => t.mintKey.equals(marketConfig.targetMint))!
    const collateralToken = poolConfig.tokens.find(t => t.mintKey.equals(marketConfig.collateralMint))!
    const side            = marketConfig.side!

    const withdrawTokenSymbol = options.withdrawTokenSymbol ?? collateralToken.symbol
    const withdrawToken = poolConfig.tokens.find(t => t.symbol === withdrawTokenSymbol)!
    const needsSwap     = !withdrawToken.mintKey.equals(collateralToken.mintKey)

    const collateralDeltaUsd = uiDecimalsToNative(withdrawAmountUsd, USD_DECIMALS)

    const data = needsSwap
        ? await flashClient.removeCollateralAndSwap(
            targetToken.symbol, collateralToken.symbol, withdrawToken.symbol,
            collateralDeltaUsd, side, poolConfig,
        )
        : await flashClient.removeCollateral(
            collateralDeltaUsd, targetToken.symbol, withdrawToken.symbol,
            side, positionPubKey, poolConfig,
        )

    const cuLimit = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 })
    const alts = (await flashClient.getOrLoadAddressLookupTable(poolConfig)).addressLookupTables
    const trxId = await flashClient.sendTransaction(
        [cuLimit, ...data.instructions],
        { alts, additionalSigners: data.additionalSigners },
    )
    console.log(`https://explorer.solana.com/tx/${trxId}`)
}

// Withdraw $10 as the collateral token:
await removeCollateral(positionPk, '10')

// Withdraw $10 as USDC (swap on exit):
await removeCollateral(positionPk, '10', { withdrawTokenSymbol: 'USDC' })
```

### List user positions

`getAllUserPositions` is the programmatic API; `displayUserPositions` adds a CLI table with live mark price, liquidation price, PnL, leverage, and accrued borrow fees pulled from `flashClient.getPositionData`.

```ts
import { isVariant, PositionAccount } from 'flash-sdk'

const getAllUserPositions = async (trader: PublicKey = flashClient.provider.publicKey) => {
    const perPool = await Promise.all(
        POOL_CONFIGS.map(async (poolConfig) => {
            const positions = await flashClient.getUserPositions(trader, poolConfig)
            return positions
                .filter((p) => (p as { isActive?: boolean }).isActive)
                .flatMap((position) => {
                    const market =
                        poolConfig.markets.find(m => m.marketAccount.equals(position.market)) ??
                        poolConfig.marketsDeprecated.find(m => m.marketAccount.equals(position.market))
                    if (!market) return []
                    const targetToken     = poolConfig.tokens.find(t => t.mintKey.equals(market.targetMint))
                    const collateralToken = poolConfig.tokens.find(t => t.mintKey.equals(market.collateralMint))
                    if (!targetToken || !collateralToken) return []
                    return [{ position, poolConfig, market, targetToken, collateralToken }]
                })
        }),
    )
    return perPool.flat()
}
```

For a pretty CLI table with PnL / leverage / liquidation price, call `flashClient.getPositionData(positionAccount, poolConfig, trader)` per row — it returns everything in one simulated on-chain call:

```ts
const positionAccount = PositionAccount.from(position.pubkey, position)
const data = await flashClient.getPositionData(positionAccount, poolConfig, trader)
// data.pnlWithFeeUsd, data.pnlPercentageWithFee, data.leverage,
// data.liquidationPrice, data.pnl.borrowFeeUsd, …
```

The full table renderer (column widths, ANSI coloring, short pubkey) lives in [`examples/src/trade.ts → displayUserPositions`](https://github.com/flash-trade/flash-trade-sdk/blob/main/examples/src/trade.ts).

{% hint style="info" %}
&#x20;`getPositionData` is a `simulateTransaction` call — one per position. Fine for a CLI; for many positions in a hot loop, fetch raw `Position` accounts and compute PnL off-chain.
{% endhint %}

### Liquidation price

Returns the position's current liquidation price as a UI string (6-decimal precision).

```ts
const getLiquidationPrice = async (positionPubKey: PublicKey) => {
    const positionData = await flashClient.program.account.position.fetch(positionPubKey)
    const poolConfig   = getPoolConfigForMarket(positionData.market)
    const data = await flashClient.getLiquidationPriceView(positionPubKey, poolConfig)
    if (!data) throw new Error('position not found')

    return OraclePrice.from({
        price:     data.price,
        exponent:  new BN(data.exponent),
        confidence: BN_ZERO,
        timestamp: BN_ZERO,
    }).toUiPrice(6)
}

const liqPx = await getLiquidationPrice(positionPk)
console.log(`liquidates at $${liqPx}`)
```

### Set full or partial Take Profit / Stop Loss on an existing position

{% hint style="info" %}

#### ***NOTE:***

* ***Stop Loss:***
  * *Must be above Liquidation Price and below Current Price for LONG*
  * *Must be below Liquidation Price and above Current Price for SHORT*
* ***Take Profit:***
  * *Must be above Current Price for LONG*
  * *Must be below Current Price for SHORT*
* ***Virtual tokens** Take Profit must be below Max Profit Price for LONG*
  {% endhint %}

```ts
import { BN_ZERO, isVariant, OraclePrice, Side, uiDecimalsToNative } from 'flash-sdk'

const setTpAndSlForMarket = async (
    takeProfitPriceUi: number | undefined,
    stopLossPriceUi: number | undefined,
    market: PublicKey,
) => {
    const poolConfig = getPoolConfigForMarket(market)
    const marketConfig = poolConfig.markets.find(f => f.marketAccount.equals(market))
    if (!marketConfig) return

    const targetCustodyConfig = poolConfig.custodies.find(c => c.custodyAccount.equals(marketConfig.targetCustody))!
    const collateralCustodyConfig = poolConfig.custodies.find(c => c.custodyAccount.equals(marketConfig.collateralCustody))!

    let instructions: TransactionInstruction[] = []
    let additionalSigners: Signer[] = []
    let COMPUTE_LIMIT = 0

    const position = (await flashClient.getUserPositions(flashClient.provider.publicKey, poolConfig))
        .filter(f => !f.sizeAmount.isZero())
        .find(p => p.market.equals(market))

    if (!position) throw new Error(`No open position for market : ${market.toBase58()}`)

    if (takeProfitPriceUi) {
        const triggerPriceNative = uiDecimalsToNative(takeProfitPriceUi.toString(), targetCustodyConfig.decimals)

        const triggerOraclePrice = new OraclePrice({
            price: new BN(triggerPriceNative.toString()),
            exponent: (new BN(targetCustodyConfig.decimals)).neg(),
            confidence: BN_ZERO,
            timestamp: BN_ZERO,
        })

        const triggerContractOraclePrice = triggerOraclePrice.toContractOraclePrice()

        const result = await flashClient.placeTriggerOrder(
            targetCustodyConfig.symbol,
            collateralCustodyConfig.symbol,
            isVariant(marketConfig.side, 'long') ? Side.Long : Side.Short,
            triggerContractOraclePrice,
            position.sizeAmount, // can be partial amount here
            false,
            collateralCustodyConfig.custodyId,
            poolConfig,
        )
        instructions.push(...result.instructions)
        additionalSigners.push(...result.additionalSigners)
        COMPUTE_LIMIT = 90_000
    }

    if (stopLossPriceUi) {
        const triggerPriceNative = uiDecimalsToNative(stopLossPriceUi.toString(), targetCustodyConfig.decimals)

        const triggerOraclePrice = new OraclePrice({
            price: new BN(triggerPriceNative.toString()),
            exponent: (new BN(targetCustodyConfig.decimals)).neg(),
            confidence: BN_ZERO,
            timestamp: BN_ZERO,
        })

        const triggerContractOraclePrice = triggerOraclePrice.toContractOraclePrice()

        const result = await flashClient.placeTriggerOrder(
            targetCustodyConfig.symbol,
            collateralCustodyConfig.symbol,
            isVariant(marketConfig.side, 'long') ? Side.Long : Side.Short,
            triggerContractOraclePrice,
            position.sizeAmount,
            true,
            collateralCustodyConfig.custodyId,
            poolConfig,
        )
        instructions.push(...result.instructions)
        additionalSigners.push(...result.additionalSigners)
        COMPUTE_LIMIT = COMPUTE_LIMIT + 90_000
    }

    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_LIMIT })
    await flashClient.loadAddressLookupTable(poolConfig)

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

setTpAndSlForMarket(
    300, // $300
    100, // $100
    new PublicKey('3vHoXbUvGhEHFsLUmxyC6VWsbYDreb1zMn9TAp5ijN5K'), // SOL long market
)
```

### Common gotchas

* **`RPC_URL is not set`** — your `.env` isn't loaded. Make sure `examples/.env` exists and `dotenv.config()` is called.
* **`Insufficient SOL Funds`** — fund the `ANCHOR_WALLET` keypair (`solana-keygen pubkey $ANCHOR_WALLET` to see the address).
* **`No pool found with a … market for X`** — the symbol isn't tradeable on mainnet, or you typo'd it. Inspect `POOL_CONFIGS[i].markets`.
* **Position lookup returns nothing** — `getAllUserPositions` filters `isActive: false`. Closed positions stay on-chain but are skipped.
* **Privilege account is `PublicKey.default`** — that's fine; it means the wallet has no FAF stake and no referral and will trade at the default rate.


---

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