Ogni transazione Solana include un blockhash recente, un riferimento a uno stato recente della rete che dimostra che la transazione è stata creata "ora". La rete rifiuta qualsiasi transazione con un blockhash più vecchio di ~150 blocchi (~60-90 secondi), prevenendo attacchi replay e invii obsoleti. Questo funziona perfettamente per i pagamenti in tempo reale. Ma interrompe i flussi di lavoro che richiedono un intervallo tra firma e invio, come:
| Scenario | Perché le transazioni standard falliscono |
|---|---|
| Operazioni di tesoreria | Il CFO a Tokyo firma, il Controller a NYC approva: 90 secondi non bastano |
| Flussi di conformità | Le transazioni richiedono revisione legale/di conformità prima dell'esecuzione |
| Firma da cold storage | Le macchine air-gapped richiedono trasferimento manuale delle transazioni firmate |
| Preparazione batch | Prepara buste paga o erogazioni durante l'orario lavorativo, esegui durante la notte |
| Coordinamento multi-firma | Più approvatori in fusi orari diversi |
| Pagamenti programmati | Programma pagamenti da eseguire in una data futura |
Nella finanza tradizionale, un assegno firmato non scade in 90 secondi. Anche certe operazioni blockchain non dovrebbero. I nonce durevoli risolvono questo problema sostituendo il blockhash recente con un valore persistente memorizzato che avanza solo quando lo usi, offrendoti transazioni che rimangono valide finché non sei pronto a inviarle.
Come funziona
Invece di un blockhash recente (valido per ~150 blocchi), utilizzi un account nonce, un account speciale che memorizza un valore univoco che può essere utilizzato al posto di un blockhash. Ogni transazione che utilizza questo nonce deve "avanzarlo" come prima istruzione. Ogni valore nonce può essere utilizzato solo per una transazione.
L'account nonce costa ~0.0015 SOL per l'esenzione dal rent. Un account nonce = una transazione in sospeso alla volta. Per flussi di lavoro paralleli, crea più account nonce.
Creare un account nonce
La creazione di un account nonce richiede due istruzioni in una singola transazione:
- Creare l'account utilizzando
getCreateAccountInstructiondal System Program - Inizializzarlo come nonce utilizzando
getInitializeNonceAccountInstruction
Generare keypair
Genera una nuova keypair da utilizzare come indirizzo dell'account nonce e calcola lo spazio richiesto e il rent.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
Istruzione di creazione account
Crea l'account di proprietà del System Program con abbastanza lamport per l'esenzione dal rent.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();const createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});
Istruzione di inizializzazione nonce
Inizializza l'account come account nonce, impostando l'autorità che può avanzarlo.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();const createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});const initNonceIx = getInitializeNonceAccountInstruction({nonceAccount: nonceKeypair.address,nonceAuthority: sender.address});
Costruire la transazione
Costruisci una transazione con entrambe le istruzioni.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();const createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});const initNonceIx = getInitializeNonceAccountInstruction({nonceAccount: nonceKeypair.address,nonceAuthority: sender.address});const { value: blockhash } = await rpc.getLatestBlockhash().send();const createNonceTx = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),(tx) =>appendTransactionMessageInstructions([createNonceAccountIx, initNonceIx],tx));
Firmare e inviare
Firma e invia la transazione per creare e inizializzare l'account nonce.
Costruire una transazione differita
Invece di un blockhash recente, usa il blockhash dell'account nonce come
durata della transazione.
Recupera il nonce
Recupera i dati dall'account nonce. Usa il blockhash dall'account nonce come
durata della transazione.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
Crea istruzione di trasferimento
Crea l'istruzione per il tuo pagamento. Questo esempio mostra un trasferimento di token.
Costruisci transazione con nonce durevole
Usa setTransactionMessageLifetimeUsingDurableNonce che imposta il nonce come
blockhash e antepone automaticamente l'istruzione di avanzamento del nonce.
Firma transazione
Firma la transazione. Ora usa il nonce durevole invece di un blockhash standard.
Memorizzare o inviare la transazione
Dopo la firma, codifica la transazione per la memorizzazione. Quando sei pronto, inviala alla rete.
Codificare per la memorizzazione
Codifica la transazione firmata in base64. Memorizza questo valore nel tuo database.
Inviare la transazione
Invia la transazione firmata quando sei pronto. La transazione rimane valida finché il nonce non viene avanzato.
Demo
// Generate keypairs for sender and recipientconst sender = await generateKeyPairSigner();const recipient = await generateKeyPairSigner();console.log("Sender Address:", sender.address);console.log("Recipient Address:", recipient.address);// Demo Setup: Create RPC connection, mint, and token accountsconst { rpc, rpcSubscriptions, mint } = await demoSetup(sender, recipient);// =============================================================================// Step 1: Create a Nonce Account// =============================================================================const nonceKeypair = await generateKeyPairSigner();console.log("\nNonce Account Address:", nonceKeypair.address);const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();// Instruction to create new account for the nonceconst createNonceAccountIx = getCreateAccountInstruction({payer: sender,newAccount: nonceKeypair,lamports: nonceRent,space: nonceSpace,programAddress: SYSTEM_PROGRAM_ADDRESS});// Instruction to initialize the nonce accountconst initNonceIx = getInitializeNonceAccountInstruction({nonceAccount: nonceKeypair.address,nonceAuthority: sender.address});// Build and send nonce account creation transactionconst { value: blockhash } = await rpc.getLatestBlockhash().send();const createNonceTx = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) => setTransactionMessageLifetimeUsingBlockhash(blockhash, tx),(tx) =>appendTransactionMessageInstructions([createNonceAccountIx, initNonceIx],tx));const signedCreateNonceTx =await signTransactionMessageWithSigners(createNonceTx);assertIsTransactionWithBlockhashLifetime(signedCreateNonceTx);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedCreateNonceTx,{ commitment: "confirmed" });console.log("Nonce Account created.");// =============================================================================// Step 2: Token Payment with Durable Nonce// =============================================================================// Fetch current nonce value from the nonce accountconst { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);console.log("Nonce Account data:", nonceData);const [senderAta] = await findAssociatedTokenPda({mint: mint.address,owner: sender.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});const [recipientAta] = await findAssociatedTokenPda({mint: mint.address,owner: recipient.address,tokenProgram: TOKEN_2022_PROGRAM_ADDRESS});console.log("\nMint Address:", mint.address);console.log("Sender Token Account:", senderAta);console.log("Recipient Token Account:", recipientAta);const transferInstruction = getTransferInstruction({source: senderAta,destination: recipientAta,authority: sender.address,amount: 250_000n // 0.25 tokens});// Create transaction message using durable nonce lifetime// setTransactionMessageLifetimeUsingDurableNonce automatically prepends// the AdvanceNonceAccount instructionconst transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) =>setTransactionMessageLifetimeUsingDurableNonce({nonce: nonceData.blockhash as string as Nonce,nonceAccountAddress: nonceKeypair.address,nonceAuthorityAddress: nonceData.authority},tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);assertIsTransactionWithDurableNonceLifetime(signedTransaction);const transactionSignature = getSignatureFromTransaction(signedTransaction);// Encode the transaction to base64, optionally save and send at a later timeconst base64EncodedTransaction =getBase64EncodedWireTransaction(signedTransaction);console.log("\nBase64 Encoded Transaction:", base64EncodedTransaction);// Send the encoded transaction, blockhash does not expireawait rpc.sendTransaction(base64EncodedTransaction, {encoding: "base64",skipPreflight: true}).send();console.log("\n=== Token Payment with Durable Nonce Complete ===");console.log("Transaction Signature:", transactionSignature);// =============================================================================// Demo Setup Helper Function// =============================================================================
Invalidare una transazione in sospeso
Ogni account nonce blockhash può essere usato solo una volta. Per invalidare
una transazione in sospeso o preparare l'account nonce per il riutilizzo,
avanzalo manualmente:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
Questo genera un nuovo valore nonce, rendendo permanentemente invalida qualsiasi transazione firmata con il vecchio valore.
Flusso di lavoro per approvazione multi-parte
Deserializza la transazione per aggiungere firme aggiuntive, quindi serializza nuovamente per la memorizzazione o l'invio:
import {getBase64Decoder,getTransactionDecoder,getBase64EncodedWireTransaction,partiallySignTransaction} from "@solana/kit";// Deserialize the stored transactionconst txBytes = getBase64Decoder().decode(serializedString);const partiallySignedTx = getTransactionDecoder().decode(txBytes);// Each approver adds their signatureconst fullySignedTx = await partiallySignTransaction([newSigner],partiallySignedTx);// Serialize again for storage or submissionconst serialized = getBase64EncodedWireTransaction(fullySignedTx);
La transazione può essere serializzata, memorizzata e passata tra gli approvatori. Una volta raccolte tutte le firme richieste, invia alla rete.
Considerazioni per la produzione
Gestione degli account nonce:
- Crea un pool di account nonce per la preparazione parallela delle transazioni
- Tieni traccia di quali nonce sono "in uso" (hanno transazioni firmate in sospeso)
- Implementa il riciclo dei nonce dopo l'invio o l'abbandono delle transazioni
Sicurezza:
- L'autorità nonce controlla se le transazioni possono essere invalidate. Considera di separare l'autorità nonce dai firmatari delle transazioni per un controllo aggiuntivo e separazione dei compiti
- Chiunque abbia i byte della transazione serializzata può inviarla alla rete
Risorse correlate
Is this page helpful?