import { ComputeBudgetProgram } from "@solana/web3.js"; import { PoolConfig, Side } from "flash-sdk"; import { BN } from "@coral-xyz/anchor"; import { SolanaAgentKit } from "../index"; import { CLOSE_POSITION_CU, marketSdkInfo, marketTokenMap, getNftTradingAccountInfo, fetchOraclePrice, createPerpClient, get_flash_privilege, } from "../utils/flashUtils"; import { FlashCloseTradeParams } from "../types"; /** * Closes an existing position on Flash.Trade * @param agent SolanaAgentKit instance * @param params Trade parameters * @returns Transaction signature */ export async function flashCloseTrade( agent: SolanaAgentKit, params: FlashCloseTradeParams, ): Promise { try { const { token, side } = params; // Get market ID from token and side using marketTokenMap const tokenMarkets = marketTokenMap[token]; if (!tokenMarkets) { throw new Error(`Token ${token} not supported for trading`); } const sideEntry = tokenMarkets[side]; if (!sideEntry) { throw new Error(`${side} side not available for ${token}`); } const market = sideEntry.marketID; // Validate market data using marketSdkInfo const marketData = marketSdkInfo[market]; if (!marketData) { throw new Error(`Invalid market configuration for ${token}/${side}`); } // Get token information const [targetSymbol, collateralSymbol] = marketData.tokenPair.split("/"); // Fetch oracle prices const [targetPrice] = await Promise.all([ fetchOraclePrice(targetSymbol), fetchOraclePrice(collateralSymbol), ]); // Initialize pool configuration and perpClient const poolConfig = PoolConfig.fromIdsByName( marketData.pool, "mainnet-beta", ); const perpClient = createPerpClient(agent.connection, agent.wallet); // Calculate price after slippage const slippageBpsBN = new BN(100); // 1% slippage const sideEnum = side === "long" ? Side.Long : Side.Short; const priceWithSlippage = perpClient.getPriceAfterSlippage( false, // isEntry = false for closing position slippageBpsBN, targetPrice.price, sideEnum, ); // Get NFT trading account info const tradingAccounts = await getNftTradingAccountInfo( agent.wallet_address, perpClient, poolConfig, collateralSymbol, ); if ( !tradingAccounts.nftTradingAccountPk || !tradingAccounts.nftReferralAccountPK || !tradingAccounts.nftOwnerRebateTokenAccountPk ) { throw new Error("Required NFT trading accounts not found"); } // Build and send transaction const { instructions, additionalSigners } = await perpClient.closePosition( targetSymbol, collateralSymbol, priceWithSlippage, sideEnum, poolConfig, get_flash_privilege(agent), tradingAccounts.nftTradingAccountPk, tradingAccounts.nftReferralAccountPK, tradingAccounts.nftOwnerRebateTokenAccountPk, ); const computeBudgetIx = ComputeBudgetProgram.setComputeUnitLimit({ units: CLOSE_POSITION_CU, }); return await perpClient.sendTransaction( [computeBudgetIx, ...instructions], { additionalSigners: additionalSigners, alts: perpClient.addressLookupTables, prioritizationFee: 5000000, }, ); } catch (error) { throw new Error(`Flash trade close failed: ${error}`); } }