You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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钱包手动操作(更低余额)可完成兑换
  • 反复检查脚本账户创建与余额验证逻辑

问题根源

  1. 未提前初始化USDT关联代币账户:脚本中定义了ensureTokenAccountExists函数,但未在兑换流程中调用,导致Jupiter交易尝试创建账户时可能因余额计算问题失败
  2. 错误覆盖交易的recentBlockhash:Jupiter返回的swapTransaction已包含有效区块哈希,手动重新设置会破坏交易的完整性
  3. 跳过预检查: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);
    }
})();

关键修正点

  1. 调用ensureTokenAccountExists初始化USDT账户:在兑换前提前创建USDT关联账户,避免Jupiter交易中因账户创建的额外费用导致余额不足
  2. 移除手动设置recentBlockhash:保留Jupiter返回交易的原始区块哈希,确保交易有效性
  3. 开启wrapAndUnwrapSol:让Jupiter自动处理SOL与WSOL的转换,简化流程
  4. 关闭skipPreflight:启用预检查,提前发现交易参数错误
  5. 调整余额阈值:USDT关联账户的免租金仅需约0.002 SOL,无需设置0.1 SOL的高阈值

内容的提问来源于stack exchange,提问作者Val

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:24:56