Elke Solana-transactie bevat een recente blockhash—een verwijzing naar een recente netwerkstatus die bewijst dat de transactie "nu" is aangemaakt. Het netwerk weigert elke transactie met een blockhash ouder dan ~150 blokken (~60-90 seconden), waardoor replay-aanvallen en verouderde inzendingen worden voorkomen. Dit werkt perfect voor realtime betalingen. Maar het breekt workflows die een tijdsverschil tussen ondertekening en indiening nodig hebben, zoals:
| Scenario | Waarom standaard transacties falen |
|---|---|
| Treasury-operaties | CFO in Tokyo ondertekent, controller in NYC keurt goed—90 seconden is niet genoeg |
| Compliance-workflows | Transacties hebben juridische/compliance-beoordeling nodig voor uitvoering |
| Cold storage-ondertekening | Air-gapped machines vereisen handmatige overdracht van ondertekende transacties |
| Batchvoorbereiding | Bereid salarisbetalingen of uitkeringen voor tijdens kantooruren, voer 's nachts uit |
| Multi-sig coördinatie | Meerdere goedkeurders in verschillende tijdzones |
| Geplande betalingen | Plan betalingen die op een toekomstige datum worden uitgevoerd |
In traditionele financiën verloopt een ondertekende cheque niet na 90 seconden. Bepaalde blockchain-operaties zouden dat ook niet moeten doen. Durable nonces lossen dit op door de recente blockhash te vervangen door een opgeslagen, persistente waarde die alleen vooruitgaat wanneer je deze gebruikt—waardoor je transacties krijgt die geldig blijven totdat je klaar bent om ze in te dienen.
Hoe het werkt
In plaats van een recente blockhash (geldig ~150 blokken), gebruik je een nonce-account, een speciaal account dat een unieke waarde opslaat die in plaats van een blockhash kan worden gebruikt. Elke transactie die deze nonce gebruikt, moet deze "verhogen" als eerste instructie. Elke nonce-waarde kan slechts voor één transactie worden gebruikt.
Het nonce-account kost ~0,0015 SOL voor rent-vrijstelling. Eén nonce-account = één lopende transactie tegelijk. Voor parallelle workflows maak je meerdere nonce-accounts aan.
Een nonce-account aanmaken
Het aanmaken van een nonce-account vereist twee instructies in één transactie:
- Maak het account aan met
getCreateAccountInstructionvan het System Program - Initialiseer het als nonce met
getInitializeNonceAccountInstruction
Genereer keypair
Genereer een nieuw keypair om te gebruiken als het nonce-accountadres en bereken de benodigde ruimte en rent.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
Maak account-instructie
Maak het account aan dat eigendom is van het System Program met voldoende lamports voor rent-vrijstelling.
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});
Initialiseer nonce-instructie
Initialiseer het account als een nonce-account en stel de autoriteit in die het kan verhogen.
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});
Bouw transactie
Bouw een transactie met beide instructies.
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));
Onderteken en verzend
Onderteken en verstuur de transactie om het nonce-account aan te maken en te initialiseren.
Een uitgestelde transactie bouwen
In plaats van een recente blockhash, gebruik je de blockhash van het
nonce-account als de levensduur van de transactie.
Haal de nonce op
Haal de gegevens op uit het nonce-account. Gebruik de blockhash van het
nonce-account als de levensduur van de transactie.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
Maak een overboekingsinstructie
Maak de instructie voor je betaling. Dit voorbeeld toont een token-overboeking.
Bouw transactie met duurzame nonce
Gebruik setTransactionMessageLifetimeUsingDurableNonce die de nonce instelt
als de blockhash en automatisch de advance nonce-instructie vooraan plaatst.
Onderteken transactie
Onderteken de transactie. Deze gebruikt nu de duurzame nonce in plaats van een standaard blockhash.
Transactie opslaan of verzenden
Codeer de transactie na ondertekening voor opslag. Verzend deze naar het netwerk wanneer je klaar bent.
Coderen voor opslag
Codeer de ondertekende transactie naar base64. Sla deze waarde op in je database.
Transactie verzenden
Verzend de ondertekende transactie wanneer je klaar bent. De transactie blijft geldig totdat de nonce wordt verhoogd.
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// =============================================================================
Een lopende transactie ongeldig maken
Elk nonce-account blockhash kan slechts één keer worden gebruikt. Om een
lopende transactie ongeldig te maken of het nonce-account voor te bereiden op
hergebruik, verhoog je deze handmatig:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
Dit genereert een nieuwe nonce-waarde, waardoor elke transactie die met de oude waarde is ondertekend permanent ongeldig wordt.
Workflow voor goedkeuring door meerdere partijen
Deserialiseer de transactie om extra handtekeningen toe te voegen, en serialiseer vervolgens opnieuw voor opslag of indiening:
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);
De transactie kan worden geserialiseerd, opgeslagen en doorgegeven tussen goedkeurders. Zodra alle vereiste handtekeningen zijn verzameld, dien je deze in bij het netwerk.
Overwegingen voor productie
Nonce-accountbeheer:
- Maak een pool van nonce-accounts voor parallelle transactievoorbereiding
- Houd bij welke nonces "in gebruik" zijn (met ondertekende transacties in behandeling)
- Implementeer nonce-recycling nadat transacties zijn ingediend of afgebroken
Beveiliging:
- De nonce-autoriteit bepaalt of transacties ongeldig kunnen worden gemaakt. Overweeg om de nonce-autoriteit te scheiden van transactieondertekenaars voor extra controle en functiescheiding
- Iedereen met de geserialiseerde transactiebytes kan deze naar het netwerk verzenden
Gerelateerde bronnen
Is this page helpful?