交易者交互

安装依赖项:

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

// setup the flashClient as show previously

设置 Pyth 定价:

const connectionFromPyth = new Connection(
    'pythnet-provider-url' // 可以从 triton 获得
)

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

// 替代方案请参见 https://docs.pyth.network/price-feeds/use-real-time-data/off-chain

从 Pyth 获取价格:

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

// POOL_CONFIG 如前所示

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(`未找到 ${token.symbol} 的 priceData`)
        }

        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;
 }

使用相同抵押品开仓:

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";

// 之前的 flash-client 设置

// 当使用相同抵押品开仓时使用此方法,如 BTC 对 BTC 等
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 账户可以通过 beast.flash.trade 从您之前的交易中获取,以便更轻松地构建

    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);
}

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

平仓并以相同代币接收抵押品:

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

// 当您想要平仓并取回相同抵押品代币时使用此方法
// 例如平仓 BTC 仓位并取回 BTC

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

使用不同抵押品开仓:

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) // 根据您的 nft 等级的交易折扣
    )

    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.swapAndOpen(
        outputToken.symbol,
        outputToken.symbol,
        inputToken.symbol,
        collateralWithFee,
        minAmountOutAfterSlippage,
        priceAfterSlippage,
        size,
        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);
}

平仓并以不同代币接收抵押品:

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[] = []

    // 获取您的所有仓位
    const positions = await flashClient.getUserPositions(flashClient.provider.publicKey, POOL_CONFIG);

    // 选择您想要平仓的仓位
    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.closeAndSwap(
        targetToken.symbol,
        userRecievingToken.symbol,
        collateralToken.symbol,
        minAmountOutWithSlippage,
        priceAfterSlippage,
        side,
        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('交易 :>> ', trxId);
}

closePositionWithSwap('USDC')

在现有仓位上设置全部或部分止盈或止损:

注意:

  • 止损:

    • 对于做多必须高于清算价格并低于当前价格

    • 对于做空必须低于清算价格并高于当前价格

  • 止盈:

    • 对于做多必须高于当前价格

    • 对于做空必须低于当前价格

  • 虚拟代币* 做多的止盈必须低于最大利润价格*

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(`市场无开仓仓位:${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,
        })
        
        // 对于虚拟代币,做多的止盈必须低于最大利润价格
        // if (targetCustodyConfig.isVirtual && isVariant(marketConfig.side, 'long')) {
        //     const maxProfitPrice = perpClient.getMaxProfitPriceSync(
        //         position.entryOraclePrice,
        //         false,
        //         isVariant(marketConfig.side, 'long') ? Side.Long : Side.Short,
        //         position.positionAccount
        //     )
        //     const maxProfitPriceUi = maxProfitPrice.toUiPrice(8)
        //     const maxProfitNum = Number(maxProfitPriceUi)
        //     if (takeProfitPriceUi >= maxProfitNum) {
        //         throw Error("止盈必须低于最大利润价格");
        //         return;
        //     }
        // }

        const triggerContractOraclePrice = triggerOraclePrice.toContractOraclePrice()

        const result = await flashClient.placeTriggerOrder(
            targetCustodyConfig.symbol,
            collateralCustodyConfig.symbol,
            isVariant(marketConfig.side, 'long') ? Side.Long : Side.Short,
            triggerContractOraclePrice,
            position.sizeAmount, // 这里可以是部分数量
            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, // 这里可以是部分数量
            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('交易 :>> ', trxId);
}

setTpAndSlForMarket(
    300, // $300
    100, // $100
    new PublicKey('3vHoXbUvGhEHFsLUmxyC6VWsbYDreb1zMn9TAp5ijN5K'), // SOL 做多市场
)

获取当前活跃仓位的清算价格

const getLiquidationPrice = async (positionPubKey : PublicKey) => {
   
    const data =  await flashClient.getLiquidationPriceView(positionPubKey, POOL_CONFIG)
    if(!data){
        throw new Error('未找到仓位')
    }
     const LiqOraclePrice = OraclePrice.from({
                        price: data.price,
                        exponent: new BN(data.exponent),
                        confidence: new BN(0),
                        timestamp: new BN(0),
                    })
    console.log('价格 :>> ', LiqOraclePrice.toUiPrice(6) );
    return LiqOraclePrice.toUiPrice(6) // 6 是清算价格的小数精度,您可以根据需要更改
}

最后更新于

这有帮助吗?