PłatnościZaawansowane płatności

Wykonanie odroczone

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:

ScenariuszDlaczego standardowe transakcje zawodzą
Operacje skarboweCFO w Tokio podpisuje, kontroler w Nowym Jorku akceptuje — 90 sekund to za mało
Procesy zgodnościTransakcje wymagają przeglądu prawnego/zgodności przed wykonaniem
Podpisywanie offlineMaszyny odizolowane od sieci wymagają ręcznego przeniesienia podpisanych transakcji
Przygotowanie paczekPrzygotuj listę płac lub wypłaty w godzinach pracy, wykonaj je nocą
Koordynacja multi-sigWielu akceptantów w różnych strefach czasowych
Płatności zaplanowaneZaplanuj 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.

Durable Nonce
Standard Blockhash

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:

  1. Utwórz konto używając getCreateAccountInstruction z System Program
  2. 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.

Create Nonce Account
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.

Create Nonce Account
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ć.

Create Nonce Account
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.

Create Nonce Account
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.

Wygeneruj keypair

Wygeneruj nowy keypair, który posłuży jako adres konta nonce, oraz oblicz wymaganą przestrzeń i rent.

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.

Utwórz instrukcję inicjalizacji nonce

Zainicjuj konto jako konto nonce, ustawiając autorytet, który może je aktualizować.

Zbuduj transakcję

Zbuduj transakcję z obiema instrukcjami.

Podpisz i wyślij

Podpisz i wyślij transakcję, aby utworzyć i zainicjować konto nonce.

Create Nonce Account
const nonceKeypair = await generateKeyPairSigner();
const nonceSpace = BigInt(getNonceSize());
const nonceRent = await rpc
.getMinimumBalanceForRentExemption(nonceSpace)
.send();

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.

Example Nonce Account Data
{
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.

Pobierz nonce

Pobierz dane z konta nonce. Użyj blockhash z konta nonce jako okresu ważności transakcji.

Example Nonce Account Data
{
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.

Build Deferred Transaction
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);

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.

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.

Store and Execute
const signedTransaction =
await signTransactionMessageWithSigners(transactionMessage);
const base64EncodedTransaction =
getBase64EncodedWireTransaction(signedTransaction);
// Store base64EncodedTransaction in your database

Demo

Demo
// Generate keypairs for sender and recipient
const 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 accounts
const { 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 nonce
const createNonceAccountIx = getCreateAccountInstruction({
payer: sender,
newAccount: nonceKeypair,
lamports: nonceRent,
space: nonceSpace,
programAddress: SYSTEM_PROGRAM_ADDRESS
});
// Instruction to initialize the nonce account
const initNonceIx = getInitializeNonceAccountInstruction({
nonceAccount: nonceKeypair.address,
nonceAuthority: sender.address
});
// Build and send nonce account creation transaction
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
)
);
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 account
const { 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 instruction
const 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 time
const base64EncodedTransaction =
getBase64EncodedWireTransaction(signedTransaction);
console.log("\nBase64 Encoded Transaction:", base64EncodedTransaction);
// Send the encoded transaction, blockhash does not expire
await 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
// =============================================================================
Console
Click to execute the code.

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 transaction
getAdvanceNonceAccountInstruction({
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 transaction
const txBytes = getBase64Decoder().decode(serializedString);
const partiallySignedTx = getTransactionDecoder().decode(txBytes);
// Each approver adds their signature
const fullySignedTx = await partiallySignTransaction(
[newSigner],
partiallySignedTx
);
// Serialize again for storage or submission
const 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?

Spis treści

Edytuj stronę

Zarządzane przez

© 2026 Solana Foundation.
Wszelkie prawa zastrzeżone.
Bądź na bieżąco