UNPKG

@flipflop-sdk/node

Version:

FlipFlop Node.js SDK for programmatic token operations

259 lines 12.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.addLiquidity = void 0; const web3_js_1 = require("@solana/web3.js"); const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2"); const spl_token_1 = require("@solana/spl-token"); const config_1 = require("../config"); const bn_js_1 = __importDefault(require("bn.js")); const display_pool_1 = require("./display-pool"); const utils_1 = require("../utils"); const constants_1 = require("../constants"); const addLiquidity = async (options) => { // Validate required parameters if (!options.rpc) { return { success: false, message: "Missing rpc parameter", }; } if (!options.mint) { return { success: false, message: "Missing mint parameter", }; } if (!options.tokenAmount || options.tokenAmount <= 0) { return { success: false, message: "Invalid tokenAmount parameter", }; } if (options.slippage === undefined || options.slippage < 0) { return { success: false, message: "Invalid slippage parameter", }; } if (!options.payer) { return { success: false, message: "Missing provider parameter", }; } const connection = new web3_js_1.Connection(options.rpc, "confirmed"); const networkType = (0, config_1.getNetworkType)(options.rpc); const config = config_1.CONFIGS[networkType]; try { // Initialize Raydium SDK with proper cluster const raydium = await raydium_sdk_v2_1.Raydium.load({ owner: options.payer, connection, cluster: networkType, disableFeatureCheck: true, disableLoadToken: true, blockhashCommitment: "finalized", }); // Get token mint const mintPubkey = new web3_js_1.PublicKey(options.mint); // Check token balance const tokenAccounts = await connection.getTokenAccountsByOwner(options.payer.publicKey, { mint: mintPubkey }); if (tokenAccounts.value.length === 0) { return { success: false, message: `No token account found for mint ${options.mint}`, }; } // Parse token account data const tokenAccountInfo = spl_token_1.AccountLayout.decode(tokenAccounts.value[0].account.data); const availableTokenBalance = new bn_js_1.default(tokenAccountInfo.amount.toString()) .div(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)) .toNumber(); if (availableTokenBalance < options.tokenAmount) { return { success: false, message: `Insufficient token balance. Available: ${availableTokenBalance}, Required: ${options.tokenAmount}`, }; } // Fallback to mint-based discovery using API 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; // Calculate required SOL amount based on pool ratio const tokenAmountBN = new bn_js_1.default(options.tokenAmount).mul(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL)); // Ensure we have valid reserve values const mintAmountA = String(poolInfoData.baseReserve || "0"); const mintAmountB = String(poolInfoData.quoteReserve || "0"); const cleanMintAmountA = mintAmountA.replace(/[^0-9]/g, "") || "1000000000"; const cleanMintAmountB = mintAmountB.replace(/[^0-9]/g, "") || "1000000000"; // Determine token and SOL reserves based on which mint is which let tokenReserve; let solReserve; const tokenMint = new web3_js_1.PublicKey(options.mint); if (poolInfoData.mintA.equals(tokenMint)) { tokenReserve = new bn_js_1.default(cleanMintAmountA); solReserve = new bn_js_1.default(cleanMintAmountB); } else { tokenReserve = new bn_js_1.default(cleanMintAmountB); solReserve = new bn_js_1.default(cleanMintAmountA); } // Handle zero reserves if (tokenReserve.isZero()) { return { success: false, message: "Cannot calculate SOL amount: token reserve is zero", }; } // Calculate required SOL amount const requiredSolAmountBN = tokenAmountBN.mul(solReserve).div(tokenReserve); // Check SOL balance const solBalance = await connection.getBalance(options.payer.publicKey); // console.log("SOL balance:", solBalance / 1e9); // console.log("Required SOL:", requiredSolAmount); if (solBalance < requiredSolAmountBN.toNumber()) { return { success: false, message: `Insufficient SOL balance. Available: ${solBalance / 1e9}, Required: ${requiredSolAmountBN.toNumber() / 1e9}`, }; } // Create slippage percentage const slippagePercent = new raydium_sdk_v2_1.Percent(options.slippage, 100); const result = await doAddLiquidityInstruction(connection, poolInfoData, new web3_js_1.PublicKey(options.mint), requiredSolAmountBN, tokenAmountBN, options.payer, slippagePercent); return { success: true, data: result, }; } catch (error) { return { success: false, message: `Failed to add liquidity: ${error instanceof Error ? error.message : "Unknown error"}`, }; } }; exports.addLiquidity = addLiquidity; async function doAddLiquidityInstruction(connection, poolInfo, mint, solAmount, tokenAmount, payer, slippagePercent) { const [mint0, mint1] = (0, utils_1.compareMints)(spl_token_1.NATIVE_MINT, mint) < 0 ? [spl_token_1.NATIVE_MINT, mint] : [mint, spl_token_1.NATIVE_MINT]; const isSolFirst = mint0.equals(spl_token_1.NATIVE_MINT); const userSolAccount = await (0, spl_token_1.getAssociatedTokenAddress)(spl_token_1.NATIVE_MINT, payer.publicKey); const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(mint, payer.publicKey); const mintA = mint0; const mintB = mint1; const tokenAccountA = isSolFirst ? userSolAccount : userTokenAccount; const tokenAccountB = isSolFirst ? userTokenAccount : userSolAccount; const vaultA = isSolFirst ? new web3_js_1.PublicKey(poolInfo.vaultA) : new web3_js_1.PublicKey(poolInfo.vaultB); const vaultB = isSolFirst ? new web3_js_1.PublicKey(poolInfo.vaultB) : new web3_js_1.PublicKey(poolInfo.vaultA); const userLpAccount = await (0, spl_token_1.getAssociatedTokenAddress)(new web3_js_1.PublicKey(poolInfo.mintLp), payer.publicKey); const [authority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.AUTH_SEED)], new web3_js_1.PublicKey(poolInfo.programId)); const slippageMultiplier = new bn_js_1.default(10000 + (slippagePercent.numerator.toNumber() * 10000) / slippagePercent.denominator.toNumber()); const maxSolAmount = solAmount.mul(slippageMultiplier).div(new bn_js_1.default(10000)); const maxTokenAmount = tokenAmount.mul(slippageMultiplier).div(new bn_js_1.default(10000)); const amountMaxA = isSolFirst ? maxSolAmount : maxTokenAmount; const amountMaxB = isSolFirst ? maxTokenAmount : maxSolAmount; // 添加指令数组 const instructions = []; // 检查并创建 WSOL 账户 try { const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, userSolAccount); // 检查 WSOL 账户余额是否足够 if (new bn_js_1.default(wsolAccountInfo.amount.toString()).lt(maxSolAmount)) { // WSOL 余额不足,需要包装更多 SOL const additionalSolNeeded = maxSolAmount.sub(new bn_js_1.default(wsolAccountInfo.amount.toString())); instructions.push(web3_js_1.SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: userSolAccount, lamports: additionalSolNeeded.toNumber(), }), (0, spl_token_1.createSyncNativeInstruction)(userSolAccount)); } } catch (error) { // WSOL 账户不存在,创建账户并包装所需的 SOL instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, userSolAccount, payer.publicKey, spl_token_1.NATIVE_MINT), web3_js_1.SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: userSolAccount, lamports: maxSolAmount.toNumber(), }), (0, spl_token_1.createSyncNativeInstruction)(userSolAccount)); } // 检查并创建目标代币账户 try { await (0, spl_token_1.getAccount)(connection, userTokenAccount); } catch (error) { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, userTokenAccount, payer.publicKey, mint)); } // 检查并创建 LP 代币账户 try { await (0, spl_token_1.getAccount)(connection, userLpAccount); } catch (error) { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, userLpAccount, payer.publicKey, new web3_js_1.PublicKey(poolInfo.mintLp))); } let estimatedLpAmount; if (new bn_js_1.default(poolInfo.lpAmount).isZero()) { estimatedLpAmount = bn_js_1.default.min(solAmount, tokenAmount); } else { const poolSolReserve = new bn_js_1.default(poolInfo.baseReserve || "0"); const totalLpSupply = new bn_js_1.default(poolInfo.lpAmount); if (poolSolReserve.gt(new bn_js_1.default(0)) && totalLpSupply.gt(new bn_js_1.default(0))) { estimatedLpAmount = solAmount.mul(totalLpSupply).div(poolSolReserve); } else { estimatedLpAmount = bn_js_1.default.min(solAmount, tokenAmount); } } // estimatedLpAmount = estimatedLpAmount.mul(new BN(98)).div(new BN(100)); // 添加计算预算指令 instructions.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1000 })); const addLiquidityIx = (0, raydium_sdk_v2_1.makeDepositCpmmInInstruction)(new web3_js_1.PublicKey(poolInfo.programId), // programId payer.publicKey, // owner authority, // authority new web3_js_1.PublicKey(poolInfo.poolAddress), // poolId userLpAccount, // lpTokenAccount tokenAccountA, // tokenAccountA (SOL) tokenAccountB, // tokenAccountB (Token) vaultA, vaultB, mintA, mintB, new web3_js_1.PublicKey(poolInfo.mintLp), estimatedLpAmount, amountMaxA, amountMaxB); instructions.push(addLiquidityIx); 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); await connection.confirmTransaction(sig, "confirmed"); return { signature: sig, mintAddress: mint, tokenAmount: tokenAmount, solAmount: solAmount, lpTokenAmount: estimatedLpAmount, poolAddress: poolInfo.poolAddress, }; } //# sourceMappingURL=add-liquidity.js.map