UNPKG

@flipflop-sdk/node

Version:

FlipFlop Node.js SDK for programmatic token operations

277 lines 12.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.getLpTokenAmount = void 0; exports.removeLiquidity = removeLiquidity; 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 bn_js_1 = __importDefault(require("bn.js")); const display_pool_1 = require("./display-pool"); const config_1 = require("../config"); const utils_1 = require("../utils"); const constants_1 = require("../constants"); async function removeLiquidity(options) { try { const { rpc, payer, mint, slippage = 1 } = options; // Validate inputs if (!mint) { return { success: false, message: "Token mints are required", }; } const connection = new web3_js_1.Connection(options.rpc, "confirmed"); const networkType = (0, config_1.getNetworkType)(options.rpc); const config = config_1.CONFIGS[networkType]; // Initialize Raydium SDK 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; // Get LP token amount const lpTokenMint = new web3_js_1.PublicKey(poolInfoData.mintLp); const lpTokenInfo = await connection.getAccountInfo(lpTokenMint); if (!lpTokenInfo) { return { success: false, message: "Failed to get LP token information", }; } const lpToken = await (0, exports.getLpTokenAmount)(connection, payer.publicKey, lpTokenMint); if (!lpToken.success || !lpToken.data) { return { success: false, message: lpToken.message || "Unknown error", }; } const lpTokenAmount = lpToken.data.amount .mul(new bn_js_1.default(options.removePercentage)) .div(new bn_js_1.default(100)); const result = await doRemoveLiquidityInstruction(connection, poolInfoData, lpTokenAmount, payer, new raydium_sdk_v2_1.Percent(slippage, 100)); // console.log("Liquidity removed successfully. Transaction ID:", result.signature); // console.log("Pool ID:", poolInfo.poolAddress); return { success: true, data: { signature: result.signature, tokenAAmount: result.tokenAAmount, tokenBAmount: result.tokenBAmount, }, }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : "Unknown error occurred", }; } } // 在 doRemoveLiquidityInstruction 函数中修复 async function doRemoveLiquidityInstruction(connection, poolInfo, lpTokenAmountBN, payer, slippagePercent) { const [authority] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from(constants_1.AUTH_SEED)], new web3_js_1.PublicKey(poolInfo.programId)); // 确保mint排序正确 const mintA = new web3_js_1.PublicKey(poolInfo.mintA); const mintB = new web3_js_1.PublicKey(poolInfo.mintB); const [mint0, mint1] = (0, utils_1.compareMints)(mintA, mintB) < 0 ? [mintA, mintB] : [mintB, mintA]; const isAFirst = mint0.equals(mintA); // 获取正确的用户代币账户 const userTokenAccountA = await (0, spl_token_1.getAssociatedTokenAddress)(mint0, payer.publicKey); const userTokenAccountB = await (0, spl_token_1.getAssociatedTokenAddress)(mint1, payer.publicKey); const userLpAccount = await (0, spl_token_1.getAssociatedTokenAddress)(new web3_js_1.PublicKey(poolInfo.mintLp), payer.publicKey); // 计算预期获得的代币数量(基于池子比例) const poolTokenAReserve = new bn_js_1.default(poolInfo.baseReserve || "0"); const poolTokenBReserve = new bn_js_1.default(poolInfo.quoteReserve || "0"); const totalLpSupply = new bn_js_1.default(poolInfo.lpAmount); let expectedTokenAAmount = new bn_js_1.default(0); let expectedTokenBAmount = new bn_js_1.default(0); if (totalLpSupply.gt(new bn_js_1.default(0))) { expectedTokenAAmount = lpTokenAmountBN .mul(poolTokenAReserve) .div(totalLpSupply); expectedTokenBAmount = lpTokenAmountBN .mul(poolTokenBReserve) .div(totalLpSupply); } // 根据排序调整预期数量 const expectedAmount0 = isAFirst ? expectedTokenAAmount : expectedTokenBAmount; const expectedAmount1 = isAFirst ? expectedTokenBAmount : expectedTokenAAmount; const slippageMultiplier = new bn_js_1.default(10000 - (slippagePercent.numerator.toNumber() * 10000) / slippagePercent.denominator.toNumber()); const minAmount0 = expectedAmount0.mul(slippageMultiplier).div(new bn_js_1.default(10000)); const minAmount1 = expectedAmount1.mul(slippageMultiplier).div(new bn_js_1.default(10000)); // 根据排序调整vault const vault0 = isAFirst ? new web3_js_1.PublicKey(poolInfo.vaultA) : new web3_js_1.PublicKey(poolInfo.vaultB); const vault1 = isAFirst ? new web3_js_1.PublicKey(poolInfo.vaultB) : new web3_js_1.PublicKey(poolInfo.vaultA); // console.log("Remove liquidity parameters:", { // lpTokenAmount: lpTokenAmountBN.toString(), // expectedAmount0: expectedAmount0.toString(), // expectedAmount1: expectedAmount1.toString(), // minAmount0: minAmount0.toString(), // minAmount1: minAmount1.toString(), // mint0: mint0.toString(), // mint1: mint1.toString(), // }); // 检查并创建必要的代币账户 const instructions = []; // 检查 tokenA 账户(可能是 WSOL) try { await (0, spl_token_1.getAccount)(connection, userTokenAccountA); } catch (error) { if (error.name === "TokenAccountNotFoundError") { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, userTokenAccountA, payer.publicKey, mint0)); } } // 检查 tokenB 账户(可能是 WSOL) try { await (0, spl_token_1.getAccount)(connection, userTokenAccountB); } catch (error) { if (error.name === "TokenAccountNotFoundError") { instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(payer.publicKey, userTokenAccountB, payer.publicKey, mint1)); } } // 创建withdraw指令 const withdrawIx = (0, raydium_sdk_v2_1.makeWithdrawCpmmInInstruction)(new web3_js_1.PublicKey(poolInfo.programId), // programId payer.publicKey, // owner authority, // authority new web3_js_1.PublicKey(poolInfo.poolAddress), // poolId userLpAccount, // lpTokenAccount userTokenAccountA, // tokenAccountA (正确的账户) userTokenAccountB, // tokenAccountB (正确的账户) vault0, // vaultA (正确排序) vault1, // vaultB (正确排序) mint0, // mintA (正确排序) mint1, // mintB (正确排序) new web3_js_1.PublicKey(poolInfo.mintLp), // lpMint lpTokenAmountBN, // lpAmount minAmount0, // minimumAmountA (正确排序) minAmount1 // minimumAmountB (正确排序) ); instructions.push(withdrawIx); // 构建并发送交易 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"); // 添加 WSOL 清理逻辑 try { // 检查是否有 WSOL 需要清理 const wsolAccount = mint0.equals(spl_token_1.NATIVE_MINT) ? userTokenAccountA : mint1.equals(spl_token_1.NATIVE_MINT) ? userTokenAccountB : null; if (wsolAccount) { const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, wsolAccount); const wsolBalance = new bn_js_1.default(wsolAccountInfo.amount.toString()); if (wsolBalance.gt(new bn_js_1.default(0))) { // 创建关闭 WSOL 账户的指令,将剩余 WSOL 转换回 SOL const closeInstructions = [ (0, spl_token_1.createCloseAccountInstruction)(wsolAccount, 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) { console.warn(`Warning: Failed to cleanup WSOL account: ${error.message}`); // 不抛出错误,因为主要的移除流动性操作已经成功 } return { signature: sig, tokenAAmount: expectedTokenAAmount, tokenBAmount: expectedTokenBAmount, lpTokenAmount: lpTokenAmountBN, poolAddress: new web3_js_1.PublicKey(poolInfo.poolAddress), }; } const getLpTokenAmount = async (connection, owner, lpTokenMint) => { const lpTokenInfo = await connection.getAccountInfo(lpTokenMint); if (!lpTokenInfo) { return { success: false, message: "Failed to get LP token information", }; } // 获取用户的LP代币账户地址 const userLpTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(lpTokenMint, owner); // 获取用户LP代币账户信息 const userLpAccountInfo = await connection.getAccountInfo(userLpTokenAccount); if (!userLpAccountInfo) { return { success: false, message: "User does not have LP tokens for this pool", }; } // 解析LP代币余额 const userLpAccountData = spl_token_1.AccountLayout.decode(userLpAccountInfo.data); const userLpBalance = new bn_js_1.default(userLpAccountData.amount.toString()); // 获取LP代币的小数位数 const lpMintAccountInfo = await connection.getAccountInfo(lpTokenMint); if (!lpMintAccountInfo) { return { success: false, message: "Failed to get LP mint information", }; } const lpMintData = spl_token_1.MintLayout.decode(lpMintAccountInfo.data); const lpDecimals = lpMintData.decimals; if (userLpBalance.lt(new bn_js_1.default(0))) { return { success: false, message: "Insufficient LP token balance", }; } return { success: true, data: { amount: userLpBalance, decimals: lpDecimals, }, }; }; exports.getLpTokenAmount = getLpTokenAmount; //# sourceMappingURL=remove-liquidity.js.map