For the complete documentation index, see llms.txt. This page is also available as Markdown.

Trader Interactions

Note

Install Dependencies

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. 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 — 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 HTTP proxy — one GET returns prices for every token in every pool. No Pythnet RPC, no PythHttpClient.

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
}

The default proxy is public. For production, set LAZER_PROXY_URL to your own deployment.

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

Privilege.NFT is deprecated — use Stake or Referral instead.

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

Resolving the triplet automatically

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

Wiring it into a trade

Every trade snippet on this page uses the same pattern:

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

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.

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.

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.

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.

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.

Remove collateral

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

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.

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:

The full table renderer (column widths, ANSI coloring, short pubkey) lives in examples/src/trade.ts → displayUserPositions.

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.

Liquidation price

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

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

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

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

Last updated

Was this helpful?