@flipflop-sdk/node
Version:
FlipFlop Node.js SDK for programmatic token operations
282 lines • 13.5 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.createPool = void 0;
const web3_js_1 = require("@solana/web3.js");
const spl_token_1 = require("@solana/spl-token");
const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2");
const config_1 = require("../config");
const bn_js_1 = __importDefault(require("bn.js"));
const utils_1 = require("../utils");
const createPool = async (options) => {
if (!options.rpc) {
return {
success: false,
message: "RPC url not provided",
};
}
if (!options.mintA || !options.mintB) {
return {
success: false,
message: "Mint A or Mint B not provided",
};
}
if (options.mintA === options.mintB) {
return {
success: false,
message: "Mint A and Mint B cannot be the same",
};
}
if (!options.amountA || options.amountA <= 0) {
return {
success: false,
message: "Amount A must be greater than 0",
};
}
if (!options.amountB || options.amountB <= 0) {
return {
success: false,
message: "Amount B must be greater than 0",
};
}
const connection = new web3_js_1.Connection(options.rpc, "confirmed");
const config = config_1.CONFIGS[(0, config_1.getNetworkType)(options.rpc)];
try {
// Initialize Raydium SDK
const raydium = await raydium_sdk_v2_1.Raydium.load({
connection,
cluster: (0, config_1.getNetworkType)(options.rpc),
owner: options.creator,
disableFeatureCheck: true,
disableLoadToken: true,
blockhashCommitment: "finalized",
});
// Calculate amounts in smallest units
const amountA = new bn_js_1.default(options.amountA).mul(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL));
const amountB = new bn_js_1.default(options.amountB).mul(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL));
// 检查哪个是 WSOL (NATIVE_MINT)
const isTokenAWSOL = options.mintA.equals(spl_token_1.NATIVE_MINT);
const isTokenBWSOL = options.mintB.equals(spl_token_1.NATIVE_MINT);
if (!isTokenAWSOL && !isTokenBWSOL) {
return {
success: false,
message: "One of the tokens must be SOL (WSOL) to create a pool",
};
}
// 确定 WSOL 相关的参数
const wsolMint = isTokenAWSOL ? options.mintA : options.mintB;
const wsolAmount = isTokenAWSOL ? amountA : amountB;
const tokenMint = isTokenAWSOL ? options.mintB : options.mintA;
const tokenAmount = isTokenAWSOL ? amountB : amountA;
// Get or create associated token accounts for both tokens
const ataA = await (0, spl_token_1.getAssociatedTokenAddress)(options.mintA, options.creator.publicKey);
const ataB = await (0, spl_token_1.getAssociatedTokenAddress)(options.mintB, options.creator.publicKey);
const wsolATA = isTokenAWSOL ? ataA : ataB;
const tokenATA = isTokenAWSOL ? ataB : ataA;
const instructions = [];
// 添加计算预算指令
instructions.push(web3_js_1.ComputeBudgetProgram.setComputeUnitLimit({ units: 400000 }), web3_js_1.ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 100000 }));
// 检查并创建代币账户(非 WSOL)
try {
await (0, spl_token_1.getAccount)(connection, tokenATA);
}
catch (error) {
if (error instanceof spl_token_1.TokenAccountNotFoundError) {
instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(options.creator.publicKey, tokenATA, options.creator.publicKey, tokenMint));
}
else {
throw error;
}
}
// 检查代币余额是否足够
try {
const tokenAccountInfo = await (0, spl_token_1.getAccount)(connection, tokenATA);
const currentTokenBalance = new bn_js_1.default(tokenAccountInfo.amount.toString());
if (currentTokenBalance.lt(tokenAmount)) {
return {
success: false,
message: `Insufficient token balance. Required: ${tokenAmount
.div(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL))
.toString()}, Available: ${currentTokenBalance
.div(new bn_js_1.default(web3_js_1.LAMPORTS_PER_SOL))
.toString()}`,
};
}
}
catch (error) {
if (error instanceof spl_token_1.TokenAccountNotFoundError) {
return {
success: false,
message: `Token account not found. Please ensure you have sufficient tokens to create the pool.`,
};
}
throw error;
}
// ###### WSOL 账户检查和创建逻辑 ######
// 检查用户的 SOL 余额
const userSolBalance = await connection.getBalance(options.creator.publicKey);
const requiredSolForFees = 0.02 * web3_js_1.LAMPORTS_PER_SOL; // 预留 0.02 SOL 作为交易费用
const totalRequiredSol = wsolAmount.add(new bn_js_1.default(requiredSolForFees));
if (userSolBalance < totalRequiredSol.toNumber()) {
return {
success: false,
message: `Insufficient SOL balance. Required: ${totalRequiredSol.toNumber() / web3_js_1.LAMPORTS_PER_SOL} SOL (including fees), Available: ${userSolBalance / web3_js_1.LAMPORTS_PER_SOL} SOL`,
};
}
// 检查 WSOL 账户是否存在
try {
const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, wsolATA);
// WSOL 账户存在,检查余额是否足够
const currentWsolBalance = new bn_js_1.default(wsolAccountInfo.amount.toString());
if (currentWsolBalance.lt(wsolAmount)) {
// WSOL 余额不足,需要包装更多 SOL
const additionalSolNeeded = wsolAmount.sub(currentWsolBalance);
console.log(`WSOL balance insufficient. Need additional ${additionalSolNeeded.toNumber() / web3_js_1.LAMPORTS_PER_SOL} SOL`);
// 添加包装 SOL 的指令
instructions.push(web3_js_1.SystemProgram.transfer({
fromPubkey: options.creator.publicKey,
toPubkey: wsolATA,
lamports: additionalSolNeeded.toNumber(),
}), (0, spl_token_1.createSyncNativeInstruction)(wsolATA));
}
}
catch (error) {
if (error instanceof spl_token_1.TokenAccountNotFoundError) {
// WSOL 账户不存在,需要创建并包装 SOL
console.log(`Creating WSOL account and wrapping ${wsolAmount.toNumber() / web3_js_1.LAMPORTS_PER_SOL} SOL`);
// 创建 WSOL 关联代币账户
instructions.push((0, spl_token_1.createAssociatedTokenAccountInstruction)(options.creator.publicKey, wsolATA, options.creator.publicKey, spl_token_1.NATIVE_MINT));
// 转移 SOL 到 WSOL 账户并同步
instructions.push(web3_js_1.SystemProgram.transfer({
fromPubkey: options.creator.publicKey,
toPubkey: wsolATA,
lamports: wsolAmount.toNumber(),
}), (0, spl_token_1.createSyncNativeInstruction)(wsolATA));
}
else {
throw error;
}
}
// Create CPMM pool using Raydium SDK with API v3 compatible settings
const startTime = options.startTime || Math.floor(Date.now() / 1000);
// Ensure proper mint ordering for API v3 compatibility
const mintA = options.mintA;
const mintB = options.mintB;
const [mint0, mint1] = (0, utils_1.compareMints)(mintA, mintB) < 0 ? [mintA, mintB] : [mintB, mintA];
// Ensure amounts are correctly ordered based on mint sorting
const isReversed = mint0.equals(mintB);
const finalAmountA = isReversed ? amountB : amountA;
const finalAmountB = isReversed ? amountA : amountB;
// Create the pool with API v3 compatible parameters
const createPoolParams = {
programId: new web3_js_1.PublicKey(config.cpSwapProgram),
poolFeeAccount: new web3_js_1.PublicKey(config.createPoolFeeReceive),
mintA: {
address: mint0.toString(),
programId: spl_token_1.TOKEN_PROGRAM_ID.toString(),
decimals: 9,
},
mintB: {
address: mint1.toString(),
programId: spl_token_1.TOKEN_PROGRAM_ID.toString(),
decimals: 9,
},
mintAAmount: finalAmountA,
mintBAmount: finalAmountB,
startTime: new bn_js_1.default(startTime),
feeConfig: {
id: config.cpSwapConfigAddress,
index: 0,
tradeFeeRate: 25, // 0.25% fee (in basis points)
protocolFeeRate: 0,
fundFeeRate: 0,
createPoolFee: "0",
},
associatedOnly: false,
ownerInfo: {
useSOLBalance: true,
},
txVersion: raydium_sdk_v2_1.TxVersion.LEGACY,
};
// 如果有预处理指令,先执行它们
if (instructions.length > 0) {
const { blockhash: prepareBlockhash } = await connection.getLatestBlockhash();
const prepareMessage = new web3_js_1.TransactionMessage({
payerKey: options.creator.publicKey,
recentBlockhash: prepareBlockhash,
instructions,
}).compileToV0Message();
const prepareTx = new web3_js_1.VersionedTransaction(prepareMessage);
prepareTx.sign([options.creator]);
const prepareSig = await connection.sendTransaction(prepareTx, {
skipPreflight: false,
preflightCommitment: "confirmed",
});
await connection.confirmTransaction(prepareSig, "confirmed");
console.log(`Preparation transaction successful! Signature: ${prepareSig}`);
}
const { execute, extInfo } = await raydium.cpmm.createPool(createPoolParams);
// Execute the pool creation transaction with proper error handling
let poolCreationResult;
try {
const { txId } = await execute({
sendAndConfirm: true,
});
poolCreationResult = {
success: true,
data: {
signature: String(txId),
poolAddress: extInfo.address.poolId,
mintA: mint0,
mintB: mint1,
amountA: finalAmountA,
amountB: finalAmountB,
creator: options.creator.publicKey,
},
};
}
catch (error) {
return {
success: false,
message: `Failed to create pool with transaction: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
// ###### 创建池子成功后,清理多余的 WSOL ######
try {
const wsolAccountInfo = await (0, spl_token_1.getAccount)(connection, wsolATA);
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)(wsolATA, options.creator.publicKey, options.creator.publicKey));
const { blockhash: closeBlockhash } = await connection.getLatestBlockhash();
const closeMessage = new web3_js_1.TransactionMessage({
payerKey: options.creator.publicKey,
recentBlockhash: closeBlockhash,
instructions: closeInstructions,
}).compileToV0Message();
const closeTx = new web3_js_1.VersionedTransaction(closeMessage);
closeTx.sign([options.creator]);
const closeSig = await connection.sendTransaction(closeTx);
await connection.confirmTransaction(closeSig, "confirmed");
console.log(`WSOL account cleaned up, ${remainingWsolBalance.toNumber() / web3_js_1.LAMPORTS_PER_SOL} SOL converted back`);
}
}
catch (error) {
console.log("Error cleaning up WSOL account:", error);
// 不抛出错误,因为主要交易已经成功
}
return poolCreationResult;
}
catch (error) {
return {
success: false,
message: `Failed to create pool with SDK: ${error instanceof Error ? error.message : "Unknown error"}`,
};
}
};
exports.createPool = createPool;
//# sourceMappingURL=create-pool.js.map