Trader Interactions

Install dependencies

yarn add @solana/web3.js @pythnetwork/client @solana/spl-token @coral-xyz/anchor

// setup the flashClient as show previously

Setup Pyth for pricing

const connectionFromPyth = new Connection(
    'pythnet-provider-url' // can get it from triton
)

const pythClient = new PythHttpClient(connectionFromPyth, getPythProgramKeyForCluster('pythnet'))

// for alternatives see https://docs.pyth.network/price-feeds/use-real-time-data/off-chain

Fetch prices from Pyth

import { BN } from "@coral-xyz/anchor";
import { OraclePrice } from 'flash-sdk';

// POOL_CONFIG as show previously

const getPrices = async () => { 
    const pythHttpClientResult = await pythClient.getData()
    
    const priceMap = new Map<string, { price: OraclePrice; emaPrice: OraclePrice }>();

    for (let token of POOL_CONFIG.tokens) {
        const priceData: PriceData = pythHttpClientResult.productPrice.get(token.pythTicker)!
        if (!priceData) {
            throw new Error(`priceData not found for ${token.symbol}`)
        }
        const priceOracle = new OraclePrice({
            price: new BN(priceData?.aggregate.priceComponent.toString()),
            exponent: new BN(priceData?.exponent),
            confidence: new BN(priceData?.confidence!),
            timestamp: new BN(priceData?.timestamp.toString()),
        })

        const emaPriceOracle = new OraclePrice({
            price: new BN(priceData?.emaPrice.valueComponent.toString()),
            exponent: new BN(priceData?.exponent),
            confidence: new BN(priceData?.emaConfidence.valueComponent.toString()),
            timestamp: new BN(priceData?.timestamp.toString()),
        })
        priceMap.set(token.symbol, { price: priceOracle, emaPrice: emaPriceOracle })
    }

    return priceMap;
 }

Open position with same collateral

import { PythHttpClient, getPythProgramKeyForCluster, PriceData } from "@pythnetwork/client";
import { TransactionInstruction, Signer, PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
import { Side, OraclePrice, uiDecimalsToNative, CustodyAccount, Privilege } from "flash-sdk";

// Previous setup of flash-client

// Use this when opening a position with the same collateral so BTC to BTC and so on
const openPosition = async (inputTokenSymbol: string, outputTokenSymbol: string, inputAmount: string, side: Side) => {

    const slippageBps: number = 800 // 0.8%

    const instructions: TransactionInstruction[] = []
    let additionalSigners: Signer[] = []

    const inputToken = POOL_CONFIG.tokens.find(t => t.symbol === inputTokenSymbol)!;
    const outputToken = POOL_CONFIG.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
    
    await flashClient.loadAddressLookupTable(POOL_CONFIG)

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

    const collateralWithFee = uiDecimalsToNative(inputAmount, inputToken.decimals);
    const leverage = 1.1;

    const inputCustody = POOL_CONFIG.custodies.find(c => c.symbol === inputToken.symbol)!;
    const outputCustody = POOL_CONFIG.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]!),
        uiDecimalsToNative(`5`, 2)
    )

    const openPositionData = await flashClient.openPosition(
        outputToken.symbol,
        inputToken.symbol,
        priceAfterSlippage,
        collateralWithFee,
        outputAmount,
        side,
        POOL_CONFIG,
        Privilege.None
    )
    
    // nft accounts can be fetched from one your previous transactions through beast.flash.trade for easier building

    instructions.push(...openPositionData.instructions)
    additionalSigners.push(...openPositionData.additionalSigners)

    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 }) // addLiquidity
    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])

    console.log('trx :>> ', trxId);
}

openPosition('BTC', 'BTC', '0.000147', Side.Long)

Close a position and receive collateral in the same token

import { TransactionInstruction, Signer, PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
import { Side, Privilege } from "flash-sdk";

// Use this when you want to close the position and get the same collateral token back
// For example closing a BTC position getting BTC back 
const closePosition = async (targetTokenSymbol: string, side: Side) => {
    const slippageBps: number = 800 // 0.8%
    const instructions: TransactionInstruction[] = []
    let additionalSigners: Signer[] = []

    const targetToken = POOL_CONFIG.tokens.find(t => t.symbol === targetTokenSymbol)!;
    const userRecievingToken = POOL_CONFIG.tokens.find(t => t.symbol === targetTokenSymbol)!;

   const priceMap = await getPrices();

    const targetTokenPrice = priceMap.get(targetTokenSymbol)!.price

    const priceAfterSlippage = flashClient.getPriceAfterSlippage(false, new BN(slippageBps), targetTokenPrice, side)

    const openPositionData =await flashClient.closePosition(
        targetToken.symbol,
        userRecievingToken.symbol,
        priceAfterSlippage,
        side,
        POOL_CONFIG,
        Privilege.None
    )

    instructions.push(...openPositionData.instructions)
    additionalSigners.push(...openPositionData.additionalSigners)

    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 }) // addLiquidity
    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])
    console.log('trxId :>> ', trxId);
}

closePosition('BTC', Side.Long);

Open position with different Collateral

import { getMint } from "@solana/spl-token";
import { TransactionInstruction, Signer, PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
import { Side, uiDecimalsToNative, PoolAccount, PoolDataClient, CustodyAccount, BN_ZERO, BPS_DECIMALS, Privilege } from "flash-sdk";

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

    const slippageBps: number = 800 // 0.8%

    const instructions: TransactionInstruction[] = []
    let additionalSigners: Signer[] = []

    const inputToken = POOL_CONFIG.tokens.find(t => t.symbol === inputTokenSymbol)!;
    const outputToken = POOL_CONFIG.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

    await flashClient.loadAddressLookupTable(POOL_CONFIG)

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

    const collateralWithFee = uiDecimalsToNative(inputAmount, inputToken.decimals);
    const leverage = 1.1;

    const inputCustody = POOL_CONFIG.custodies.find(c => c.symbol === inputToken.symbol)!;
    const outputCustody = POOL_CONFIG.custodies.find(c => c.symbol === outputToken.symbol)!;

    const custodies = await flashClient.program.account.custody.fetchMultiple([inputCustody.custodyAccount, outputCustody.custodyAccount]);
    const poolAccount = PoolAccount.from(POOL_CONFIG.poolAddress, await flashClient.program.account.pool.fetch(POOL_CONFIG.poolAddress));

    const allCustodies = await flashClient.program.account.custody.all()

    const lpMintData = await getMint(flashClient.provider.connection, POOL_CONFIG.stakedLpTokenMint);

    const poolDataClient = new PoolDataClient(
        POOL_CONFIG,
        poolAccount,
        lpMintData,
        [...allCustodies.map(c => CustodyAccount.from(c.publicKey, c.account))],
    )

    let lpStats = poolDataClient.getLpStats(await getPrices())

    const inputCustodyAccount = CustodyAccount.from(inputCustody.custodyAccount, custodies[0]!);
    const ouputCustodyAccount = CustodyAccount.from(outputCustody.custodyAccount, custodies[1]!);
    
    const size = flashClient.getSizeAmountWithSwapSync(
        collateralWithFee,
        leverage.toString(),
        Side.Long,
        poolAccount,
        inputTokenPrice,
        inputTokenPriceEma,
        inputCustodyAccount,
        outputTokenPrice,
        outputTokenPriceEma,
        ouputCustodyAccount,
        outputTokenPrice,
        outputTokenPriceEma,
        ouputCustodyAccount,
        outputTokenPrice,
        outputTokenPriceEma,
        ouputCustodyAccount,
        lpStats.totalPoolValueUsd,
        POOL_CONFIG,
        uiDecimalsToNative(`${5}`, 2) // trading discount depending on your nft level
    )

    const minAmountOut = flashClient.getSwapAmountAndFeesSync(
        collateralWithFee,
        BN_ZERO,
        poolAccount,
        inputTokenPrice,
        inputTokenPriceEma,
        CustodyAccount.from(inputCustody.custodyAccount, custodies[0]!),
        outputTokenPrice,
        outputTokenPriceEma,
        CustodyAccount.from(outputCustody.custodyAccount, custodies[1]!),
        lpStats.totalPoolValueUsd,
        POOL_CONFIG
    ).minAmountOut

    const minAmountOutAfterSlippage = minAmountOut
        .mul(new BN(10 ** BPS_DECIMALS - slippageBps))
        .div(new BN(10 ** BPS_DECIMALS))

    const openPositionData = await flashClient.openPositionWithSwap(
        outputToken.symbol,
        outputToken.symbol,
        inputToken.symbol,
        collateralWithFee,
        minAmountOutAfterSlippage,
        priceAfterSlippage,
        size,
        side,
        POOL_CONFIG,
        POOL_CONFIG,
        Privilege.None
    )

    instructions.push(...openPositionData.instructions)
    additionalSigners.push(...openPositionData.additionalSigners)

    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 }) // addLiquidity
    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])

    console.log('trx :>> ', trxId);
}

Close position and receive collateral in a different token

import { TransactionInstruction, Signer, PublicKey, ComputeBudgetProgram } from "@solana/web3.js";
import { CustodyAccount, PositionAccount, getUnixTs, Privilege } from "flash-sdk";

const closePositionWithSwap = async (userRecievingTokenSymbol: string) => {  
    const slippageBps: number = 800 // 0.8%

    const instructions: TransactionInstruction[] = []
    let additionalSigners: Signer[] = []

    // get all your positions
    const positions = await flashClient.getUserPositions(flashClient.provider.publicKey, POOL_CONFIG);

    // choose the position you want to close
    const positionToClose = positions[1];

    const marketConfig = POOL_CONFIG.markets.find(f => f.marketAccount.equals(positionToClose.market))!;

    const custodies = await flashClient.program.account.custody.fetchMultiple([marketConfig.targetCustody, marketConfig.collateralCustody]);

    const userRecievingToken = POOL_CONFIG.tokens.find(t => t.symbol === userRecievingTokenSymbol)!;
    const targetCustodyAccount = CustodyAccount.from(marketConfig.targetCustody, custodies[0]!);
    const collateralCustodyAccount = CustodyAccount.from(marketConfig.collateralCustody, custodies[1]!);
    const side = marketConfig.side!;
    const positionAccount = PositionAccount.from(positionToClose.pubkey, positionToClose);;

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

    const priceMap = await getPrices()

    const targetTokenPrice = priceMap.get(targetToken.symbol)!.price
    const targetTokenPriceEma = priceMap.get(targetToken.symbol)!.emaPrice
    const collateralTokenPrice = priceMap.get(collateralToken.symbol)!.price
    const collateralTokenPriceEma = priceMap.get(collateralToken.symbol)!.emaPrice
    const userRecievingTokenPrice = priceMap.get(userRecievingToken.symbol)!.price

    const { closeAmount, feesAmount } = flashClient.getFinalCloseAmountSync(
        positionAccount,
        marketConfig.targetCustody.equals(marketConfig.collateralCustody),
        marketConfig.side,
        targetTokenPrice,
        targetTokenPriceEma,
        targetCustodyAccount,
        collateralTokenPrice,
        collateralTokenPriceEma,
        collateralCustodyAccount,
        new BN(getUnixTs()),
        POOL_CONFIG
    )

    const receiveUsd = collateralTokenPrice.getAssetAmountUsd(closeAmount, collateralToken.decimals)

    const minAmountOut = userRecievingTokenPrice.getTokenAmount(
        receiveUsd,
        userRecievingToken.decimals
    )

    const priceAfterSlippage = flashClient.getPriceAfterSlippage(false, new BN(slippageBps), targetTokenPrice, side)

    const minAmountOutWithSlippage = minAmountOut
        .mul(new BN(100 - Number(0.8)))
        .div(new BN(100))
    
    const closePositionWithSwapData = await flashClient.closePositionWithSwap(
        targetToken.symbol,
        userRecievingToken.symbol,
        collateralToken.symbol,
        minAmountOutWithSlippage,
        priceAfterSlippage,
        side,
        POOL_CONFIG,
        POOL_CONFIG,
        Privilege.None
    )

    instructions.push(...closePositionWithSwapData.instructions)
    additionalSigners.push(...closePositionWithSwapData.additionalSigners)

    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: 600_000 }) // addLiquidity
    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])

    console.log('trx :>> ', trxId);
}

closePositionWithSwap('USDC')

Set take full or partial take profit or stop loss on an existing position

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

    if (!marketConfig) return

    const targetCustodyConfig = POOL_CONFIG.custodies.find(c => c.custodyAccount.equals(marketConfig.targetCustody))!;
    const collateralCustodyConfig = POOL_CONFIG.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, POOL_CONFIG)).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,
            POOL_CONFIG
        )
        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, // can be partial amount here
            true,
            collateralCustodyConfig.custodyId,
            POOL_CONFIG
        )
        instructions.push(...result.instructions)
        additionalSigners.push(...result.additionalSigners)
        COMPUTE_LIMIT = COMPUTE_LIMIT + 90_000
    }

    const setCULimitIx = ComputeBudgetProgram.setComputeUnitLimit({ units: COMPUTE_LIMIT })

    await flashClient.loadAddressLookupTable(POOL_CONFIG)

    const trxId = await flashClient.sendTransaction([setCULimitIx, ...instructions])

    console.log('trx :>> ', trxId);
}

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

For more examples checkout our ts sdk and our rust sdk.

Last updated