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

Solana单签名执行多笔定时交易问题及代码报错咨询

解决方案:Solana一次性签名多笔延迟交易

错误原因

你遇到的This transaction has already been processed错误,本质是两个核心问题:

  1. 所有交易使用了同一个recentBlockhash,Solana网络会将签名、blockhash、指令完全一致的交易判定为重复,第一笔执行后后续交易直接被拒绝。
  2. recentBlockhash有有效期(约150个区块/几分钟),延迟发送的后续交易大概率会因为blockhash过期而失败。

核心解决思路

使用Solana的Nonce账户实现持久化交易标识:

  • Nonce账户生成的"伪blockhash"不会随区块过期,每笔交易消耗一个新的nonce值,确保交易唯一性。
  • 用户只需一次性签名所有交易,后续可按时间间隔发送,无需重复签名。

代码实现步骤

1. 导入依赖

确保导入Solana Web3.js的Nonce相关工具:

import { NonceAccount, SystemProgram, Transaction, PublicKey, LAMPORTS_PER_SOL, Keypair } from '@solana/web3.js';

2. 修改交易生成与签名逻辑

const SolTransaction = useCallback(async () => {
    // 输入校验
    if (!publicKey) throw new WalletNotConnectedError();
    if (!signAllTransactions) throw new Error("Sign transaction not found.");
    if (!buyAllocation || !numberOfBuys) throw new Error("Inputs not valid.");

    try {
        const solPerBuy = buyAllocation / numberOfBuys;
        const lamports = Math.floor(solPerBuy * LAMPORTS_PER_SOL);
        const toPubkey = new PublicKey("37cpaE3rPcurbXPTRfiocKojaLRPqMSk1wDAUnb3PiDu");

        // 生成Nonce账户密钥对
        const nonceAccountKeypair = Keypair.generate();
        // 计算Nonce账户所需最小租金
        const nonceRent = await connection.getMinimumBalanceForRentExemption(NonceAccount.SIZE);

        // 1. 创建Nonce账户的交易
        const createNonceTx = new Transaction().add(
            SystemProgram.createAccount({
                fromPubkey: publicKey,
                newAccountPubkey: nonceAccountKeypair.publicKey,
                lamports: nonceRent,
                space: NonceAccount.SIZE,
                programId: SystemProgram.programId,
            }),
            SystemProgram.nonceInitialize({
                noncePubkey: nonceAccountKeypair.publicKey,
                authorizedPubkey: publicKey, // 仅当前用户可操作该Nonce账户
            })
        );

        // 2. 创建多笔带Nonce的转账交易
        let transactions: Transaction[] = [createNonceTx];
        const initialNonce = (await connection.getNonce(nonceAccountKeypair.publicKey)).nonce;

        for (let i = 0; i < numberOfBuys; i++) {
            const tx = new Transaction().add(
                // 自动更新Nonce值,确保交易唯一性
                SystemProgram.nonceAdvance({
                    noncePubkey: nonceAccountKeypair.publicKey,
                    authorizedPubkey: publicKey,
                }),
                SystemProgram.transfer({
                    fromPubkey: publicKey,
                    toPubkey: toPubkey,
                    lamports: lamports,
                })
            );

            tx.feePayer = publicKey;
            tx.recentBlockhash = initialNonce; // 使用Nonce作为持久化标识
            transactions.push(tx);
        }

        // 批量签名所有交易(包括创建Nonce账户的交易)
        const signedTransactions = await signAllTransactions(transactions);

        return { signedTransactions, nonceAccountKeypair };
    } catch (error) {
        console.error("交易生成失败:", error);
        return false;
    }
}, [publicKey, buyAllocation, numberOfBuys, signAllTransactions, connection]);

3. 按间隔发送签名后的交易

const executeDelayedTransactions = async () => {
    const result = await SolTransaction();
    if (result === false) return;

    const { signedTransactions, nonceAccountKeypair } = result;

    // 先执行创建Nonce账户的交易
    try {
        const createNonceSig = await connection.sendRawTransaction(signedTransactions[0].serialize());
        await connection.confirmTransaction(createNonceSig, "singleGossip");
        console.log("Nonce账户创建完成:", createNonceSig);
    } catch (err) {
        console.error("Nonce账户创建失败:", err);
        return;
    }

    // 按时间间隔执行转账交易
    for (let i = 1; i < signedTransactions.length; i++) {
        try {
            const txSig = await connection.sendRawTransaction(signedTransactions[i].serialize());
            await connection.confirmTransaction(txSig, "singleGossip");
            console.log(`第${i}笔转账完成:`, txSig);
        } catch (err) {
            console.error(`第${i}笔转账失败:`, err);
        }
        // 延迟指定时间
        await new Promise(resolve => setTimeout(resolve, timeInterval * 1000));
    }

    // 可选:交易完成后关闭Nonce账户,取回租金
    try {
        const closeNonceTx = new Transaction().add(
            SystemProgram.nonceClose({
                noncePubkey: nonceAccountKeypair.publicKey,
                destinationPubkey: publicKey,
                authorizedPubkey: publicKey,
            })
        );
        closeNonceTx.feePayer = publicKey;
        closeNonceTx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
        
        const signedCloseTx = await signTransaction(closeNonceTx);
        const closeSig = await connection.sendRawTransaction(signedCloseTx.serialize());
        await connection.confirmTransaction(closeSig, "singleGossip");
        console.log("Nonce账户已关闭,租金取回:", closeSig);
    } catch (err) {
        console.error("Nonce账户关闭失败:", err);
    }
};

// 调用执行函数
executeDelayedTransactions();

关键说明

  • Nonce账户租金:创建Nonce账户需要支付少量SOL作为租金,交易完成后关闭账户可全额取回。
  • 交易唯一性:每笔转账前的nonceAdvance指令会自动更新Nonce值,确保每笔交易的标识唯一,不会被网络判定为重复。
  • 钱包兼容性:部分钱包可能对批量签名多笔交易有数量限制,建议测试主流钱包(如Phantom、Solflare)的兼容性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 03:15:01