UNPKG

@flipflop-sdk/node

Version:

FlipFlop Node.js SDK for programmatic token operations

225 lines 11.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.buyToken = buyToken; 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 buyToken(options) { if (!options.rpc) { return { success: false, message: "RPC url not provided", }; } if (!options.mint) { return { success: false, message: "Token mint not provided", }; } if (!options.amount) { return { success: false, message: "Token amount not provided", }; } if (!options.payer) { return { success: false, message: "Payer not provided", }; } try { const connection = new web3_js_1.Connection(options.rpc, "confirmed"); const payer = options.payer; const networkType = (0, config_1.getNetworkType)(options.rpc); const config = config_1.CONFIGS[networkType]; const raydium = await raydium_sdk_v2_1.Raydium.load({ owner: options.payer, connection, cluster: networkType, disableFeatureCheck: true, disableLoadToken: true, blockhashCommitment: "finalized", }); const poolInfo = await (0, display_pool_1.getPoolInfoByRpc)(connection, raydium, spl_token_1.NATIVE_MINT, options.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.success) { return { success: false, message: poolInfo.message || "Unknown error", }; } const poolInfoData = poolInfo.data; const isToken0Sol = poolInfoData.mintA.equals(spl_token_1.NATIVE_MINT); const inputMint = spl_token_1.NATIVE_MINT; // 输入是SOL const outputMint = new web3_js_1.PublicKey(options.mint); // 输出是目标代币 const inputVault = isToken0Sol ? poolInfoData.vaultA : poolInfoData.vaultB; // SOL的vault const outputVault = isToken0Sol ? poolInfoData.vaultB : poolInfoData.vaultA; // 代币的vault // 使用BN直接计算,避免精度丢失 const amountOut = new bn_js_1.default(options.amount).mul(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)); // const maxAmountIn = new BN(options.maxSolAmount).mul(new BN(LAMPORTS_PER_SOL)); // SOL 有 9 位小数 const solReserve = isToken0Sol ? new bn_js_1.default(poolInfoData.baseReserve) : new bn_js_1.default(poolInfoData.quoteReserve); const tokenReserve = isToken0Sol ? new bn_js_1.default(poolInfoData.quoteReserve) : new bn_js_1.default(poolInfoData.baseReserve); const amountInRequired = amountOut .mul(solReserve) .div(tokenReserve.sub(amountOut)); const slippagePercent = options.slippage || 5; const slippageMultiplier = new bn_js_1.default(10000 + slippagePercent * 100); // 1% = 100 basis points const maxAmountIn = amountInRequired .mul(slippageMultiplier) .div(new bn_js_1.default(10000)); const payerInputTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(inputMint, payer.publicKey); const payerOutputTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(outputMint, payer.publicKey); // console.log('token account', payerInputTokenAccount.toBase58()); // console.log('wsol account', payerOutputTokenAccount.toBase58()); const [authority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.AUTH_SEED)], new web3_js_1.PublicKey(poolInfoData.programId)); const instructions = []; try { await (0, spl_token_1.getAccount)(connection, payerOutputTokenAccount); } catch (error) { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, payerOutputTokenAccount, payer.publicKey, outputMint)); } try { const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, payerInputTokenAccount); // 检查 WSOL 账户余额是否足够 const currentWsolBalance = new bn_js_1.default(wsolAccountInfo.amount.toString()); if (currentWsolBalance.lt(maxAmountIn)) { // WSOL 余额不足,需要包装更多 SOL const additionalSolNeeded = maxAmountIn.sub(currentWsolBalance); // 检查用户的 SOL 余额是否足够 const solBalance = await connection.getBalance(payer.publicKey); const requiredSolForTx = additionalSolNeeded.add(new bn_js_1.default(5000)); // 预留交易费用 if (solBalance < requiredSolForTx.toNumber()) { return { success: false, message: `Insufficient SOL balance. Available: ${solBalance / web3_js_1.LAMPORTS_PER_SOL} SOL, Required: ${requiredSolForTx.toNumber() / web3_js_1.LAMPORTS_PER_SOL} SOL`, }; } // 添加包装 SOL 的指令 instructions.push(web3_js_1.SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: payerInputTokenAccount, lamports: additionalSolNeeded.toNumber(), }), (0, spl_token_1.createSyncNativeInstruction)(payerInputTokenAccount)); } } catch (error) { // WSOL 账户不存在的情况 if (error.name === "TokenAccountNotFoundError") { // 检查用户的 SOL 余额是否足够创建账户并包装 const solBalance = await connection.getBalance(payer.publicKey); const requiredSolForTx = maxAmountIn.add(new bn_js_1.default(5000)); // 预留交易费用 if (solBalance < requiredSolForTx.toNumber()) { return { success: false, message: `Insufficient SOL balance. Available: ${solBalance / web3_js_1.LAMPORTS_PER_SOL} SOL, Required: ${requiredSolForTx.toNumber() / web3_js_1.LAMPORTS_PER_SOL} SOL`, }; } instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, payerInputTokenAccount, payer.publicKey, inputMint)); // 创建WSOL账户并wrap所需的SOL instructions.push(web3_js_1.SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: payerInputTokenAccount, lamports: maxAmountIn.toNumber(), }), (0, spl_token_1.createSyncNativeInstruction)(payerInputTokenAccount)); } else { return { success: false, message: `Error checking WSOL account: ${error.message}`, }; } } instructions.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000 })); const swapInstruction = (0, raydium_sdk_v2_1.makeSwapCpmmBaseOutInstruction)(new web3_js_1.PublicKey(poolInfoData.programId), // programId payer.publicKey, // payer authority, // authority new web3_js_1.PublicKey(config.cpSwapConfigAddress), // configId poolInfoData.poolAddress, // poolId payerInputTokenAccount, // inputTokenAccount payerOutputTokenAccount, // 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)(new web3_js_1.PublicKey(poolInfoData.programId), new web3_js_1.PublicKey(poolInfoData.poolAddress)).publicKey, maxAmountIn, amountOut); instructions.push(swapInstruction); const { blockhash } = await connection.getLatestBlockhash(); const message = new web3_js_1.TransactionMessage({ payerKey: payer.publicKey, recentBlockhash: blockhash, instructions, }).compileToV0Message(); const tx = new web3_js_1.VersionedTransaction(message); tx.sign([payer]); const sig = await connection.sendTransaction(tx, { skipPreflight: false, preflightCommitment: "confirmed", }); await connection.confirmTransaction(sig, "confirmed"); // 添加清理 WSOL 账户的逻辑 try { const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, payerInputTokenAccount); const remainingWsolBalance = new bn_js_1.default(wsolAccountInfo.amount.toString()); if (remainingWsolBalance.gt(new bn_js_1.default(0))) { // 如果还有剩余的 WSOL,将其转换回 SOL const closeInstructions = []; // 创建关闭 WSOL 账户的指令,这会将剩余的 WSOL 转换回 SOL closeInstructions.push((0, spl_token_1.createCloseAccountInstruction)(payerInputTokenAccount, payer.publicKey, payer.publicKey)); const { blockhash: closeBlockhash } = await connection.getLatestBlockhash(); const closeMessage = new web3_js_1.TransactionMessage({ payerKey: payer.publicKey, recentBlockhash: closeBlockhash, instructions: closeInstructions, }).compileToV0Message(); const closeTx = new web3_js_1.VersionedTransaction(closeMessage); closeTx.sign([payer]); const closeSig = await connection.sendTransaction(closeTx); await connection.confirmTransaction(closeSig, "confirmed"); } } catch (error) { return { success: false, message: `Error while unwrap WSOL: ${error.message}`, }; } return { success: true, data: { mintAddress: new web3_js_1.PublicKey(options.mint), solAmount: maxAmountIn.toNumber() / web3_js_1.LAMPORTS_PER_SOL, tokenAmount: options.amount, poolAddress: poolInfoData.poolAddress, txId: sig, }, }; } catch (error) { return { success: false, message: error.message, }; } } //# sourceMappingURL=buy-token.js.map