@flipflop-sdk/node
Version:
FlipFlop Node.js SDK for programmatic token operations
204 lines • 10.3 kB
JavaScript
"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