constconnectionFromPyth=newConnection('pythnet-provider-url'// can get it from triton)constpythClient=newPythHttpClient(connectionFromPyth,getPythProgramKeyForCluster('pythnet'))// for alternatives see https://docs.pyth.network/price-feeds/use-real-time-data/off-chain
Fetch Prices From Pyth:
Open Position with Same Collateral:
Close a Position and Receive Collateral in the Same Token:
Open position with different Collateral:
Close Position and Receive Collateral in a Different Token:
Set Full or Partial Take Profit or 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 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
Getting Liquidation Price of Current Active Position
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;
}
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)
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);
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,
})
// also for Virtual tokens Take Profit must be below Max Profit Price for LONG
// 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("Take Profit must be below Max Profit Price");
// 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, // 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
)
const getLiquidationPrice = async (positionPubKey : PublicKey) => {
const data = await flashClient.getLiquidationPriceView(positionPubKey, POOL_CONFIG)
if(!data){
throw new Error('position not found')
}
const LiqOraclePrice = OraclePrice.from({
price: data.price,
exponent: new BN(data.exponent),
confidence: new BN(0),
timestamp: new BN(0),
})
console.log('price :>> ', LiqOraclePrice.toUiPrice(6) );
return LiqOraclePrice.toUiPrice(6) // 6 is the decimals precision for liquidation price, you can change it based on your needs
}