ПлатежіРозширені платежі

Відкладене виконання

Кожна транзакція Solana містить нещодавній blockhash — посилання на недавній стан мережі, що підтверджує створення транзакції "зараз". Мережа відхиляє будь-яку транзакцію з blockhash старішим за ~150 блоків (~60-90 секунд), запобігаючи атакам повторного відтворення та застарілим поданням. Це ідеально працює для платежів у реальному часі. Але це порушує робочі процеси, які потребують проміжку між підписанням та поданням, наприклад:

СценарійЧому стандартні транзакції не працюють
Операції казначействаФінансовий директор у Токіо підписує, контролер у Нью-Йорку затверджує — 90 секунд недостатньо
Робочі процеси комплаєнсуТранзакції потребують юридичної перевірки/перевірки відповідності перед виконанням
Підписання холодного сховищаІзольовані машини вимагають ручного передавання підписаних транзакцій
Підготовка пакетівПідготовка зарплати або виплат у робочий час, виконання вночі
Координація мультипідписуКілька затверджувачів у різних часових поясах
Заплановані платежіПланування платежів для виконання в майбутню дату

У традиційних фінансах підписаний чек не втрачає чинності через 90 секунд. Певні блокчейн-операції також не повинні. Довговічні nonce вирішують це, замінюючи нещодавній blockhash збереженим, постійним значенням, яке змінюється лише тоді, коли ви його використовуєте — надаючи вам транзакції, що залишаються дійсними, доки ви не будете готові їх подати.

Як це працює

Замість недавнього blockhash (дійсний ~150 блоків), ви використовуєте nonce-акаунт — спеціальний акаунт, який зберігає унікальне значення, що може використовуватися замість blockhash. Кожна транзакція, яка використовує цей nonce, повинна «просунути» його як першу інструкцію. Кожне значення nonce можна використати лише для однієї транзакції.

Durable Nonce
Standard Blockhash

Nonce-акаунт коштує ~0.0015 SOL для звільнення від rent. Один nonce-акаунт = одна очікувана транзакція одночасно. Для паралельних робочих процесів створіть кілька nonce-акаунтів.

Створення nonce-акаунта

Створення nonce-акаунта вимагає двох інструкцій в одній транзакції:

  1. Створіть акаунт за допомогою getCreateAccountInstruction з System Program
  2. Ініціалізуйте його як nonce за допомогою getInitializeNonceAccountInstruction

Генерація keypair

Згенеруйте новий keypair для використання як адреса nonce-акаунта та обчисліть необхідний простір і rent.

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

Інструкція створення акаунта

Створіть акаунт, що належить System Program, з достатньою кількістю lamports для звільнення від 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
});

Інструкція ініціалізації nonce

Ініціалізуйте акаунт як nonce-акаунт, встановивши authority, який може його просувати.

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
});

Побудова транзакції

Побудуйте транзакцію з обома інструкціями.

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
)
);

Підписання та відправлення

Підпишіть та відправте транзакцію для створення та ініціалізації nonce-акаунта.

Генерація keypair

Згенеруйте новий keypair для використання як адреса nonce-акаунта та обчисліть необхідний простір і rent.

Інструкція створення акаунта

Створіть акаунт, що належить System Program, з достатньою кількістю lamports для звільнення від rent.

Інструкція ініціалізації nonce

Ініціалізуйте акаунт як nonce-акаунт, встановивши authority, який може його просувати.

Побудова транзакції

Побудуйте транзакцію з обома інструкціями.

Підписання та відправлення

Підпишіть та відправте транзакцію для створення та ініціалізації nonce-акаунта.

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

Побудова відкладеної транзакції

Замість нещодавнього blockhash використовуйте blockhash nonce-акаунта як час життя транзакції.

Отримання nonce

Отримайте дані з nonce-акаунта. Використовуйте blockhash з nonce-акаунта як час життя транзакції.

Example Nonce Account Data
{
version: 1,
state: 1,
authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',
blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',
lamportsPerSignature: 5000n
}

Створення інструкції переказу

Створіть інструкцію для вашого платежу. Цей приклад показує переказ токенів.

Побудова транзакції з durable nonce

Використовуйте setTransactionMessageLifetimeUsingDurableNonce, який встановлює nonce як blockhash і автоматично додає інструкцію просування nonce на початок.

Підписання транзакції

Підпишіть транзакцію. Тепер вона використовує durable nonce замість стандартного blockhash.

Отримання nonce

Отримайте дані з nonce-акаунта. Використовуйте blockhash з nonce-акаунта як час життя транзакції.

Example Nonce Account Data
{
version: 1,
state: 1,
authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',
blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',
lamportsPerSignature: 5000n
}

Створення інструкції переказу

Створіть інструкцію для вашого платежу. Цей приклад показує переказ токенів.

Побудова транзакції з durable nonce

Використовуйте setTransactionMessageLifetimeUsingDurableNonce, який встановлює nonce як blockhash і автоматично додає інструкцію просування nonce на початок.

Підписання транзакції

Підпишіть транзакцію. Тепер вона використовує durable nonce замість стандартного blockhash.

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

Зберігання або відправлення транзакції

Після підписання закодуйте транзакцію для зберігання. Коли будете готові, відправте її в мережу.

Кодування для зберігання

Закодуйте підписану транзакцію в base64. Збережіть це значення у вашій базі даних.

Відправлення транзакції

Відправте підписану транзакцію, коли будете готові. Транзакція залишається дійсною, доки nonce не буде просунуто.

Кодування для зберігання

Закодуйте підписану транзакцію в base64. Збережіть це значення у вашій базі даних.

Відправлення транзакції

Відправте підписану транзакцію, коли будете готові. Транзакція залишається дійсною, доки nonce не буде просунуто.

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

Демонстрація

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.

Анулювання очікуваної транзакції

Кожен nonce-акаунт blockhash можна використати лише один раз. Щоб анулювати очікувану транзакцію або підготувати nonce-акаунт для повторного використання, просуньте його вручну:

import { getAdvanceNonceAccountInstruction } from "@solana-program/system";
// Submit this instruction (with a regular blockhash) to invalidate any pending transaction
getAdvanceNonceAccountInstruction({
nonceAccount: nonceAddress,
nonceAuthority
});

Це генерує нове значення nonce, роблячи будь-яку транзакцію, підписану зі старим значенням, назавжди недійсною.

Робочий процес багатостороннього схвалення

Десеріалізуйте транзакцію, щоб додати додаткові підписи, потім серіалізуйте знову для зберігання або відправлення:

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);

Транзакцію можна серіалізувати, зберігати та передавати між учасниками схвалення. Після збору всіх необхідних підписів відправте її в мережу.

Міркування для продакшену

Управління nonce-акаунтами:

  • Створіть пул nonce-акаунтів для паралельної підготовки транзакцій
  • Відстежуйте, які nonce «використовуються» (мають очікувані підписані транзакції)
  • Реалізуйте повторне використання nonce після надсилання або скасування транзакцій

Безпека:

  • Власник nonce контролює, чи можна анулювати транзакції. Розгляньте можливість відокремлення власника nonce від підписувачів транзакцій для додаткового контролю та розподілу обов'язків
  • Будь-хто, хто має серіалізовані байти транзакції, може надіслати її в мережу

Пов'язані ресурси

Is this page helpful?

Керується

© 2026 Фонд Solana.
Всі права захищені.
Залишайтеся на зв'язку