@flipflop-sdk/node
Version:
FlipFlop Node.js SDK for programmatic token operations
225 lines • 11.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.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