ПлатежиРасширенные платежи

Отложенное выполнение

Каждая транзакция в Solana содержит недавний blockhash — ссылку на актуальное состояние сети, подтверждающую, что транзакция создана «сейчас». Сеть отклоняет любую транзакцию с blockhash старше примерно 150 блоков (60–90 секунд), предотвращая повторные атаки и устаревшие отправки. Это отлично работает для платежей в реальном времени. Но такой подход не подходит для сценариев, где между подписанием и отправкой требуется пауза, например:

СценарийПочему стандартные транзакции не подходят
Казначейские операцииCFO в Токио подписывает, контролёр в Нью-Йорке утверждает — 90 секунд недостаточно
Комплаенс-процедурыТранзакции требуют юридической/комплаенс-проверки перед выполнением
Подпись в холодном хранилищеAir-gapped устройства требуют ручной передачи подписанных транзакций
Подготовка пакетовПодготовка зарплаты или выплат в рабочее время, выполнение ночью
Координация мультиподписиНесколько утверждающих в разных часовых поясах
Запланированные платежиПлатежи, которые должны быть выполнены в будущем

В традиционных финансах подписанный чек не теряет силу через 90 секунд. Некоторые операции в блокчейне тоже не должны. Долговечные nonces решают эту задачу, заменяя недавний 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, который сможет продвигать nonce.

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.

Сборка транзакции

Соберите транзакцию с обеими инструкциями.

Подпись и отправка

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

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

Сборка отложенной транзакции

Вместо недавнего blockhash используйте значение nonce из nonce-аккаунта как срок действия транзакции.

Получить nonce

Получите данные из nonce-аккаунта. Используйте значение nonce из nonce-аккаунта как срок действия транзакции.

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

Создать инструкцию перевода

Создайте инструкцию для вашего платежа. В этом примере показан перевод токенов.

Сборка транзакции с долговечным nonce

Используйте setTransactionMessageLifetimeUsingDurableNonce, чтобы установить nonce как blockhash и автоматически добавить инструкцию advance nonce в начало.

Подписать транзакцию

Подпишите транзакцию. Теперь она использует долговечный nonce вместо стандартного blockhash.

Получить nonce

Получите данные из nonce-аккаунта. Используйте значение nonce из nonce-аккаунта как срок действия транзакции.

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

Создать инструкцию перевода

Создайте инструкцию для вашего платежа. В этом примере показан перевод токенов.

Сборка транзакции с долговечным nonce

Используйте setTransactionMessageLifetimeUsingDurableNonce, чтобы установить nonce как blockhash и автоматически добавить инструкцию advance nonce в начало.

Подписать транзакцию

Подпишите транзакцию. Теперь она использует долговечный 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-аккаунтов для параллельной подготовки транзакций
  • Отслеживайте, какие nonces "используются" (имеют неподтверждённые подписанные транзакции)
  • Реализуйте повторное использование nonces после отправки или отмены транзакций

Безопасность:

  • Владелец nonce-аккаунта контролирует возможность аннулирования транзакций. Для дополнительного контроля и разделения обязанностей рассмотрите возможность отделения владельца nonce от подписантов транзакций
  • Любой, у кого есть сериализованные байты транзакции, может отправить её в сеть

Связанные ресурсы

Is this page helpful?

Управляется

© 2026 Фонд Solana.
Все права защищены.
Подключиться