Jede Solana-Transaktion enthält einen aktuellen Blockhash – ein Verweis auf einen aktuellen Netzwerkstatus, der beweist, dass die Transaktion "jetzt" erstellt wurde. Das Netzwerk lehnt jede Transaktion mit einem Blockhash ab, der älter als ca. 150 Blöcke (etwa 60–90 Sekunden) ist, um Replay-Angriffe und veraltete Einreichungen zu verhindern. Das funktioniert perfekt für Echtzeitzahlungen. Aber es scheitert bei Workflows, die eine Lücke zwischen Signatur und Einreichung benötigen, wie zum Beispiel:
| Szenario | Warum Standard-Transaktionen scheitern |
|---|---|
| Treasury-Operationen | CFO in Tokio signiert, Controller in NYC genehmigt – 90 Sekunden reichen nicht |
| Compliance-Workflows | Transaktionen müssen vor der Ausführung rechtlich/compliance-geprüft werden |
| Cold Storage Signing | Air-gapped Maschinen erfordern manuellen Transfer signierter Transaktionen |
| Batch-Vorbereitung | Lohn- oder Auszahlungslisten tagsüber vorbereiten, nachts ausführen |
| Multi-Sig-Koordination | Mehrere Genehmiger über verschiedene Zeitzonen hinweg |
| Geplante Zahlungen | Zahlungen zur Ausführung an einem zukünftigen Datum terminieren |
Im traditionellen Finanzwesen verfällt ein unterschriebener Scheck nicht nach 90 Sekunden. Bestimmte Blockchain-Operationen sollten das auch nicht. Dauerhafte Nonces lösen dieses Problem, indem sie den aktuellen Blockhash durch einen gespeicherten, persistenten Wert ersetzen, der sich nur beim Verwenden weiterentwickelt – so bleiben Transaktionen gültig, bis Sie sie einreichen möchten.
Funktionsweise
Anstelle eines aktuellen Blockhash (gültig für ~150 Blöcke) verwenden Sie ein Nonce-Konto, ein spezielles Konto, das einen eindeutigen Wert speichert, der anstelle eines Blockhash verwendet werden kann. Jede Transaktion, die diesen Nonce verwendet, muss ihn als erste Anweisung "fortschreiten" lassen. Jeder Nonce-Wert kann nur für eine Transaktion verwendet werden.
Das Nonce-Konto kostet ~0,0015 SOL für die Mietbefreiung. Ein Nonce-Konto = eine ausstehende Transaktion gleichzeitig. Für parallele Workflows erstellen Sie mehrere Nonce-Konten.
Ein Nonce-Konto erstellen
Das Erstellen eines Nonce-Kontos erfordert zwei Anweisungen in einer einzigen Transaktion:
- Erstellen Sie das Konto mit
getCreateAccountInstructionaus dem System Program - Initialisieren Sie es als Nonce mit
getInitializeNonceAccountInstruction
Keypair generieren
Generieren Sie ein neues Keypair, das als Nonce-Kontoadresse verwendet wird, und berechnen Sie den erforderlichen Speicherplatz und die Miete.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
Konto-Anweisung erstellen
Erstellen Sie das Konto im Besitz des System Program mit ausreichend Lamports für die Mietbefreiung.
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});
Nonce-Anweisung initialisieren
Initialisieren Sie das Konto als Nonce-Konto und legen Sie die Autorität fest, die es fortschreiten lassen kann.
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});
Transaktion erstellen
Erstellen Sie eine Transaktion mit beiden Anweisungen.
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));
Signieren und senden
Signieren und senden Sie die Transaktion, um das Nonce-Konto zu erstellen und zu initialisieren.
Eine verzögerte Transaktion erstellen
Anstelle eines aktuellen Blockhashs verwenden Sie den blockhash des
Nonce-Kontos als Lebensdauer der Transaktion.
Nonce abrufen
Rufen Sie die Daten vom Nonce-Konto ab. Verwenden Sie den blockhash vom
Nonce-Konto als Lebensdauer der Transaktion.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
Transfer-Anweisung erstellen
Erstellen Sie die Anweisung für Ihre Zahlung. Dieses Beispiel zeigt einen Token-Transfer.
Transaktion mit dauerhaftem Nonce erstellen
Verwenden Sie setTransactionMessageLifetimeUsingDurableNonce, das den Nonce
als Blockhash festlegt und automatisch die Advance-Nonce-Anweisung voranstellt.
Transaktion signieren
Signieren Sie die Transaktion. Sie verwendet jetzt den dauerhaften Nonce anstelle eines Standard-Blockhashs.
Transaktion speichern oder senden
Nach dem Signieren kodieren Sie die Transaktion zur Speicherung. Wenn bereit, senden Sie sie an das Netzwerk.
Für Speicherung kodieren
Kodieren Sie die signierte Transaktion in Base64. Speichern Sie diesen Wert in Ihrer Datenbank.
Transaktion senden
Senden Sie die signierte Transaktion, wenn bereit. Die Transaktion bleibt gültig, bis der Nonce fortgeschritten wird.
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// =============================================================================
Ausstehende Transaktion ungültig machen
Jedes Nonce-Konto blockhash kann nur einmal verwendet werden. Um eine
ausstehende Transaktion ungültig zu machen oder das Nonce-Konto für die
Wiederverwendung vorzubereiten, schreiten Sie es manuell fort:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
Dies erzeugt einen neuen Nonce-Wert und macht jede mit dem alten Wert signierte Transaktion dauerhaft ungültig.
Workflow für mehrseitige Genehmigung
Deserialisieren Sie die Transaktion, um zusätzliche Signaturen hinzuzufügen, und serialisieren Sie sie dann erneut zur Speicherung oder Übermittlung:
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);
Die Transaktion kann serialisiert, gespeichert und zwischen Genehmigern weitergegeben werden. Sobald alle erforderlichen Signaturen gesammelt wurden, übermitteln Sie sie an das Netzwerk.
Überlegungen für den Produktivbetrieb
Nonce-Kontenverwaltung:
- Erstellen Sie einen Pool von Nonce-Konten für die parallele Transaktionsvorbereitung
- Verfolgen Sie, welche Nonces „in Verwendung" sind (ausstehende signierte Transaktionen haben)
- Implementieren Sie Nonce-Recycling, nachdem Transaktionen übermittelt oder abgebrochen wurden
Sicherheit:
- Die Nonce-Autorität kontrolliert, ob Transaktionen ungültig gemacht werden können. Erwägen Sie, die Nonce-Autorität von den Transaktionsunterzeichnern zu trennen, um zusätzliche Kontrolle und Aufgabentrennung zu erreichen
- Jeder mit den serialisierten Transaktionsbytes kann diese an das Netzwerk übermitteln
Verwandte Ressourcen
Is this page helpful?