UNPKG

@flipflop-sdk/node

Version:

FlipFlop Node.js SDK for programmatic token operations

204 lines 10.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.sellToken = sellToken; const web3_js_1 = require("@solana/web3.js"); const spl_token_1 = require("@solana/spl-token"); const bn_js_1 = __importDefault(require("bn.js")); const display_pool_1 = require("./display-pool"); const config_1 = require("../config"); const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2"); const constants_1 = require("../constants"); async function sellToken(options) { try { const connection = new web3_js_1.Connection(options.rpc, "confirmed"); const seller = options.seller; const networkType = (0, config_1.getNetworkType)(options.rpc); const config = config_1.CONFIGS[networkType]; // 初始化 Raydium SDK const raydium = await raydium_sdk_v2_1.Raydium.load({ connection, owner: seller, cluster: networkType, disableFeatureCheck: true, disableLoadToken: true, blockhashCommitment: "finalized", }); // 获取池子信息 const poolInfo = await (0, display_pool_1.getPoolInfoByRpc)(connection, raydium, options.mint, spl_token_1.NATIVE_MINT, options.rpc); if (!poolInfo) { return { success: false, message: `No CPMM pool found for token ${options.mint}. You can specify poolAddress parameter to use a specific pool.`, }; } if (!poolInfo.data || !poolInfo.success) { return { success: false, message: `No CPMM pool data found for token ${options.mint}.`, }; } const poolInfoData = poolInfo.data; const isToken0Sol = poolInfoData.mintA.equals(spl_token_1.NATIVE_MINT); const inputMint = new web3_js_1.PublicKey(options.mint); // 输入是要卖出的代币 const outputMint = spl_token_1.NATIVE_MINT; // 输出是SOL const inputVault = isToken0Sol ? poolInfoData.vaultB : poolInfoData.vaultA; // 代币的vault const outputVault = isToken0Sol ? poolInfoData.vaultA : poolInfoData.vaultB; // SOL的vault const amountIn = new bn_js_1.default(options.amount).mul(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)); // 获取池子储备量 const tokenReserve = isToken0Sol ? new bn_js_1.default(poolInfoData.quoteReserve) : new bn_js_1.default(poolInfoData.baseReserve); const solReserve = isToken0Sol ? new bn_js_1.default(poolInfoData.baseReserve) : new bn_js_1.default(poolInfoData.quoteReserve); // 使用 CPMM 公式计算预期输出数量:amountOut = (amountIn * reserveOut) / (reserveIn + amountIn) const amountOutExpected = amountIn .mul(solReserve) .div(tokenReserve.add(amountIn)); // 应用滑点保护(默认 5% 滑点) const slippagePercent = options.slippage || 5; const slippageMultiplier = new bn_js_1.default(10000 - slippagePercent * 100); // 5% = 500 basis points const minAmountOut = amountOutExpected .mul(slippageMultiplier) .div(new bn_js_1.default(10000)); // 检查用户 SOL 余额(用于交易费用) const userSolBalance = await connection.getBalance(seller.publicKey); const requiredSolForFees = 0.01 * web3_js_1.LAMPORTS_PER_SOL; // 预留 0.01 SOL 作为交易费用 if (userSolBalance < requiredSolForFees) { return { success: false, message: `Insufficient SOL balance for transaction fees. Required: ${requiredSolForFees / web3_js_1.LAMPORTS_PER_SOL} SOL, Available: ${userSolBalance / web3_js_1.LAMPORTS_PER_SOL} SOL`, }; } const instructions = []; // 添加计算预算指令 instructions.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100000 })); // 获取用户的代币账户地址 const sellerInputTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(inputMint, seller.publicKey); // 检查代币账户是否存在并检查余额 try { const tokenAccountInfo = await (0, spl_token_1.getAccount)(connection, sellerInputTokenAccount); const tokenBalance = new bn_js_1.default(tokenAccountInfo.amount.toString()); if (tokenBalance.lt(amountIn)) { return { success: false, message: `Insufficient token balance. Required: ${amountIn .div(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)) .toString()} tokens, Available: ${tokenBalance .div(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)) .toString()} tokens`, }; } } catch (error) { if (error instanceof spl_token_1.TokenAccountNotFoundError) { return { success: false, message: `Token account not found for mint ${options.mint}. Please ensure you have tokens to sell.`, }; } return { success: false, message: `Error checking token balance: ${error.message}`, }; } // 获取或创建 WSOL 账户 const sellerOutputTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(spl_token_1.NATIVE_MINT, seller.publicKey); // 检查 WSOL 账户是否存在,如果不存在则创建 try { await (0, spl_token_1.getAccount)(connection, sellerOutputTokenAccount); } catch (error) { if (error instanceof spl_token_1.TokenAccountNotFoundError) { // 创建 WSOL 关联代币账户 instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(seller.publicKey, sellerOutputTokenAccount, seller.publicKey, spl_token_1.NATIVE_MINT)); } else { return { success: false, message: `Error checking WSOL account: ${error.message}`, }; } } // 构建权限地址 const [authority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.AUTH_SEED)], poolInfoData.programId); // 构建交换指令 const swapInstruction = (0, raydium_sdk_v2_1.makeSwapCpmmBaseInInstruction)(poolInfoData.programId, // programId seller.publicKey, // payer authority, // authority new web3_js_1.PublicKey(config.cpSwapConfigAddress), // configId poolInfoData.poolAddress, // poolId sellerInputTokenAccount, // inputTokenAccount sellerOutputTokenAccount, // outputTokenAccount inputVault, // inputVault outputVault, // outputVault spl_token_1.TOKEN_PROGRAM_ID, // inputTokenProgramId spl_token_1.TOKEN_PROGRAM_ID, // outputTokenProgramId inputMint, // inputMint outputMint, // outputMint (0, raydium_sdk_v2_1.getPdaObservationId)(poolInfoData.programId, poolInfoData.poolAddress) .publicKey, amountIn, minAmountOut); instructions.push(swapInstruction); // 构建并发送交易 const { blockhash } = await connection.getLatestBlockhash(); const message = new web3_js_1.TransactionMessage({ payerKey: seller.publicKey, recentBlockhash: blockhash, instructions, }).compileToV0Message(); const tx = new web3_js_1.VersionedTransaction(message); tx.sign([seller]); const sig = await connection.sendTransaction(tx, { skipPreflight: false, preflightCommitment: "confirmed", }); await connection.confirmTransaction(sig, "confirmed"); try { const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, sellerOutputTokenAccount); const wsolBalance = new bn_js_1.default(wsolAccountInfo.amount.toString()); if (wsolBalance.gt(new bn_js_1.default(0))) { // 如果有 WSOL 余额,将其转换回 SOL const closeInstructions = []; // 创建关闭 WSOL 账户的指令,这会将 WSOL 转换回 SOL closeInstructions.push((0, spl_token_1.createCloseAccountInstruction)(sellerOutputTokenAccount, seller.publicKey, seller.publicKey)); const { blockhash: closeBlockhash } = await connection.getLatestBlockhash(); const closeMessage = new web3_js_1.TransactionMessage({ payerKey: seller.publicKey, recentBlockhash: closeBlockhash, instructions: closeInstructions, }).compileToV0Message(); const closeTx = new web3_js_1.VersionedTransaction(closeMessage); closeTx.sign([seller]); const closeSig = await connection.sendTransaction(closeTx); await connection.confirmTransaction(closeSig, "confirmed"); // console.log(`WSOL account cleaned up, ${wsolBalance.toNumber() / LAMPORTS_PER_SOL} SOL converted back`); } } catch (error) { return { success: false, message: `Error cleaning up WSOL account: ${error.message}`, }; } return { success: true, data: { mintAddress: options.mint, tokenAmount: options.amount, solAmount: minAmountOut.div(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)).toNumber(), poolAddress: poolInfoData.poolAddress, txId: sig, }, }; } catch (error) { return { success: false, message: `Error selling token: ${error.message}`, }; } } //# sourceMappingURL=sell-token.js.map