Solana单签名执行多笔定时交易问题及代码报错咨询
解决方案:Solana一次性签名多笔延迟交易
错误原因
你遇到的This transaction has already been processed错误,本质是两个核心问题:
- 所有交易使用了同一个
recentBlockhash,Solana网络会将签名、blockhash、指令完全一致的交易判定为重复,第一笔执行后后续交易直接被拒绝。 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
相关产品推荐
相关产品推荐

