Solana Jupiter API余额充足仍提示资金不足问题排查
Solana Jupiter API兑换SOL转USDT时「资金不足」问题的解决方案
问题概述
在Solana区块链上使用Jupiter API执行SOL转USDT兑换时,持续触发「Insufficient funds(资金不足)」错误,但钱包实际余额充足(0.09 SOL),交易金额仅0.0001 SOL。已确认余额覆盖手续费和免租金要求,且Phantom钱包手动操作可成功完成兑换,但脚本始终报错:
Program Error: "Instruction #4 Failed - insufficient funds" Program returned error: custom program error: 0x1
环境
Node.js @solana/web3.js @solana/spl-token
已执行排查
- 将SOL余额提高至手续费+免租金阈值以上
- Phantom钱包手动操作(更低余额)可完成兑换
- 反复检查脚本账户创建与余额验证逻辑
问题根源
- 未提前初始化USDT关联代币账户:脚本中定义了
ensureTokenAccountExists函数,但未在兑换流程中调用,导致Jupiter交易尝试创建账户时可能因余额计算问题失败 - 错误覆盖交易的recentBlockhash:Jupiter返回的
swapTransaction已包含有效区块哈希,手动重新设置会破坏交易的完整性 - 跳过预检查:
sendRawTransaction启用skipPreflight: true,导致无法提前发现交易中的参数错误
修正后的脚本
require('dotenv').config(); const fetch = require('cross-fetch'); const bs58 = require('bs58'); const { Connection, Keypair, PublicKey, VersionedTransaction, LAMPORTS_PER_SOL, sendAndConfirmTransaction, Transaction } = require('@solana/web3.js'); const RPC_ENDPOINT = 'https://api.mainnet-beta.solana.com'; const connection = new Connection(RPC_ENDPOINT, 'confirmed'); const payer = Keypair.fromSecretKey(bs58.decode(process.env.PRIVATE_KEY)); const { Token, TOKEN_PROGRAM_ID } = require('@solana/spl-token'); async function ensureTokenAccountExists(connection, payer, mintAddress) { const mintPublicKey = new PublicKey(mintAddress); const payerPublicKey = payer.publicKey; // 检查关联代币账户是否存在 const tokenAccount = await Token.getAssociatedTokenAddress( Token.ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, mintPublicKey, payerPublicKey ); let tokenAccountInfo = await connection.getAccountInfo(tokenAccount); if (!tokenAccountInfo) { console.log("USDT代币账户不存在,正在创建..."); const transaction = new Transaction().add( Token.createAssociatedTokenAccountInstruction( Token.ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, mintPublicKey, tokenAccount, payerPublicKey, payerPublicKey ) ); // 发送创建账户交易 await sendAndConfirmTransaction(connection, transaction, [payer], { commitment: 'confirmed' }); console.log("USDT代币账户创建完成"); } else { console.log("USDT代币账户已存在"); } // 验证SOL余额是否足够 const solBalance = await connection.getBalance(payerPublicKey); // 免租金+手续费预留:0.002 SOL足够(USDT账户免租金约0.002 SOL) const minimumRequiredBalance = LAMPORTS_PER_SOL * 0.002; if (solBalance < minimumRequiredBalance) { throw new Error(`SOL余额不足,当前${solBalance/LAMPORTS_PER_SOL} SOL,至少需要${minimumRequiredBalance/LAMPORTS_PER_SOL} SOL`); } } async function getSwapQuote(inputMint, outputMint, amount, slippageBps = 50) { const url = `https://quote-api.jup.ag/v6/quote?inputMint=${inputMint}&outputMint=${outputMint}&amount=${amount}&slippageBps=${slippageBps}`; const response = await fetch(url); if (!response.ok) throw new Error(`获取报价失败:HTTP状态码${response.status}`); return await response.json(); } async function executeSwap(quoteResponse) { console.log("开始执行兑换..."); const swapResponse = await fetch('https://quote-api.jup.ag/v6/swap', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ quoteResponse, userPublicKey: payer.publicKey.toString(), wrapAndUnwrapSol: true, // 开启SOL自动包装/解包,避免手动处理WSOL }) }); if (!swapResponse.ok) { const responseBody = await swapResponse.text(); throw new Error(`兑换交易获取失败:HTTP状态码${swapResponse.status},响应内容:${responseBody}`); } const responseData = await swapResponse.json(); if (responseData.error || !responseData.swapTransaction) { throw new Error(`获取兑换交易失败:${responseData.error}`); } const transactionBuffer = Buffer.from(responseData.swapTransaction, 'base64'); const transaction = VersionedTransaction.deserialize(transactionBuffer); // 不要手动覆盖blockhash,Jupiter返回的交易已包含有效区块哈希 transaction.sign([payer]); console.log("发送兑换交易..."); const txid = await connection.sendRawTransaction(transaction.serialize(), { skipPreflight: false, // 关闭跳过预检查,提前发现错误 preflightCommitment: 'confirmed' }); // 等待交易确认 await connection.confirmTransaction(txid, 'confirmed'); console.log(`兑换完成,交易ID:${txid}`); } (async () => { try { const inputMint = 'So11111111111111111111111111111111111111112'; // SOL const outputMint = 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB'; // USDT const amount = 0.0001 * LAMPORTS_PER_SOL; console.log(`准备兑换 ${amount / LAMPORTS_PER_SOL} SOL 到 USDT...`); // 提前确保USDT账户存在 await ensureTokenAccountExists(connection, payer, outputMint); const quoteResponse = await getSwapQuote(inputMint, outputMint, amount); console.log("报价信息:", JSON.stringify(quoteResponse, null, 2)); if (!quoteResponse.error) { await executeSwap(quoteResponse); } else { throw new Error(`获取报价失败:${quoteResponse.error}`); } } catch (error) { console.error("执行出错:", error.message); } })();
关键修正点
- 调用
ensureTokenAccountExists初始化USDT账户:在兑换前提前创建USDT关联账户,避免Jupiter交易中因账户创建的额外费用导致余额不足 - 移除手动设置recentBlockhash:保留Jupiter返回交易的原始区块哈希,确保交易有效性
- 开启
wrapAndUnwrapSol:让Jupiter自动处理SOL与WSOL的转换,简化流程 - 关闭
skipPreflight:启用预检查,提前发现交易参数错误 - 调整余额阈值:USDT关联账户的免租金仅需约0.002 SOL,无需设置0.1 SOL的高阈值
内容的提问来源于stack exchange,提问作者Val
相关产品推荐
相关产品推荐

