Każda transakcja w sieci Solana zawiera ostatni blockhash — odniesienie do aktualnego stanu sieci, które potwierdza, że transakcja została utworzona „teraz”. Sieć odrzuca każdą transakcję z blockhashem starszym niż około 150 bloków (czyli 60–90 sekund), zapobiegając atakom powtórzeniowym i przeterminowanym zgłoszeniom. To rozwiązanie sprawdza się idealnie w przypadku płatności w czasie rzeczywistym. Jednak nie działa w procesach, które wymagają przerwy między podpisaniem a wysłaniem transakcji, takich jak:
| Scenariusz | Dlaczego standardowe transakcje zawodzą |
|---|---|
| Operacje skarbowe | CFO w Tokio podpisuje, kontroler w Nowym Jorku akceptuje — 90 sekund to za mało |
| Procesy zgodności | Transakcje wymagają przeglądu prawnego/zgodności przed wykonaniem |
| Podpisywanie offline | Maszyny odizolowane od sieci wymagają ręcznego przeniesienia podpisanych transakcji |
| Przygotowanie paczek | Przygotuj listę płac lub wypłaty w godzinach pracy, wykonaj je nocą |
| Koordynacja multi-sig | Wielu akceptantów w różnych strefach czasowych |
| Płatności zaplanowane | Zaplanuj płatności do realizacji w przyszłości |
W tradycyjnych finansach podpisany czek nie traci ważności po 90 sekundach. Niektóre operacje blockchainowe również nie powinny. Trwałe nonce rozwiązują ten problem, zastępując ostatni blockhash przechowywaną, trwałą wartością, która zmienia się tylko podczas użycia — dzięki temu transakcje pozostają ważne, dopóki nie zdecydujesz się ich wysłać.
Jak to działa
Zamiast używać ostatniego blockhasha (ważnego przez około 150 bloków), korzystasz z konta nonce – specjalnego konta, które przechowuje unikalną wartość mogącą zastąpić blockhash. Każda transakcja używająca tego nonce musi "zaktualizować" go jako pierwszą instrukcję. Każda wartość nonce może być użyta tylko w jednej transakcji.
Konto nonce kosztuje około 0,0015 SOL, aby było zwolnione z opłaty rent. Jedno konto nonce = jedna oczekująca transakcja naraz. Dla równoległych operacji utwórz wiele kont nonce.
Utwórz konto nonce
Utworzenie konta nonce wymaga dwóch instrukcji w jednej transakcji:
- Utwórz konto używając
getCreateAccountInstructionz System Program - Zainicjuj je jako nonce używając
getInitializeNonceAccountInstruction
Wygeneruj keypair
Wygeneruj nowy keypair, który posłuży jako adres konta nonce, oraz oblicz wymaganą przestrzeń i rent.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
Utwórz instrukcję utworzenia konta
Utwórz konto należące do System Program z odpowiednią liczbą lamportów, aby było zwolnione z opłaty 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});
Utwórz instrukcję inicjalizacji nonce
Zainicjuj konto jako konto nonce, ustawiając autorytet, który może je aktualizować.
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});
Zbuduj transakcję
Zbuduj transakcję z obiema instrukcjami.
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));
Podpisz i wyślij
Podpisz i wyślij transakcję, aby utworzyć i zainicjować konto nonce.
Zbuduj odroczoną transakcję
Zamiast ostatniego blockhasha użyj blockhash z konta nonce jako okresu
ważności transakcji.
Pobierz nonce
Pobierz dane z konta nonce. Użyj blockhash z konta nonce jako okresu ważności
transakcji.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
Utwórz instrukcję transferu
Utwórz instrukcję dla swojej płatności. Ten przykład pokazuje transfer tokena.
Zbuduj transakcję z trwałym nonce
Użyj setTransactionMessageLifetimeUsingDurableNonce, który ustawia nonce jako
blockhash i automatycznie dodaje instrukcję advance nonce na początku.
Podpisz transakcję
Podpisz transakcję. Teraz używa trwałego nonce zamiast standardowego blockhasha.
Przechowywanie lub wysyłanie transakcji
Po podpisaniu zakoduj transakcję do przechowywania. Gdy będziesz gotowy, wyślij ją do sieci.
Kodowanie do przechowywania
Zakoduj podpisaną transakcję do formatu base64. Przechowaj tę wartość w swojej bazie danych.
Wyślij transakcję
Wyślij podpisaną transakcję, gdy będziesz gotowy. Transakcja pozostaje ważna, dopóki nonce nie zostanie zaktualizowany.
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// =============================================================================
Unieważnienie oczekującej transakcji
Każde konto nonce blockhash można użyć tylko raz. Aby unieważnić oczekującą
transakcję lub przygotować konto nonce do ponownego użycia, zaktualizuj je
ręcznie:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
To wygeneruje nową wartość nonce, przez co każda transakcja podpisana starą wartością stanie się trwale nieważna.
Przepływ akceptacji wielostronnej
Deserializuj transakcję, aby dodać kolejne podpisy, a następnie ponownie ją serializuj do przechowywania lub wysyłki:
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);
Transakcję można serializować, przechowywać i przekazywać pomiędzy akceptantami. Gdy wszystkie wymagane podpisy zostaną zebrane, wyślij ją do sieci.
Wskazówki produkcyjne
Zarządzanie kontami nonce:
- Utwórz pulę kont nonce do równoległego przygotowywania transakcji
- Śledź, które nonce są "w użyciu" (mają oczekujące podpisane transakcje)
- Wdróż recykling nonce po przesłaniu lub porzuceniu transakcji
Bezpieczeństwo:
- Uprawniony do nonce decyduje, czy transakcje mogą zostać unieważnione. Rozważ oddzielenie uprawnień do nonce od sygnatariuszy transakcji dla większej kontroli i rozdziału obowiązków
- Każdy, kto posiada zserializowane bajty transakcji, może przesłać ją do sieci
Powiązane zasoby
Is this page helpful?