PagamentosPagamentos avançados

Execução diferida

Cada transação Solana inclui um blockhash recente—uma referência a um estado recente da rede que prova que a transação foi criada "agora". A rede rejeita qualquer transação com um blockhash mais antigo que ~150 blocos (~60-90 segundos), prevenindo ataques de repetição e submissões obsoletas. Isto funciona perfeitamente para pagamentos em tempo real. Mas quebra fluxos de trabalho que precisam de um intervalo entre assinatura e submissão, tais como:

CenárioPor que transações padrão falham
Operações de tesourariaCFO em Tóquio assina, Controller em NYC aprova—90 segundos não são suficientes
Fluxos de conformidadeTransações precisam de revisão legal/conformidade antes da execução
Assinatura em armazenamento frioMáquinas isoladas requerem transferência manual de transações assinadas
Preparação em lotePreparar folha de pagamento ou desembolsos durante horário comercial, executar à noite
Coordenação multi-assinaturaMúltiplos aprovadores em fusos horários diferentes
Pagamentos agendadosAgendar pagamentos para serem executados numa data futura

Nas finanças tradicionais, um cheque assinado não expira em 90 segundos. Certas operações blockchain também não deveriam. Nonces duráveis resolvem isto substituindo o blockhash recente por um valor armazenado e persistente que só avança quando você o usa—dando-lhe transações que permanecem válidas até você estar pronto para submeter.

Como funciona

Em vez de um blockhash recente (válido por ~150 blocos), utiliza uma conta nonce, uma conta especial que armazena um valor único que pode ser usado no lugar de um blockhash. Cada transação que usa este nonce deve "avançá-lo" como primeira instrução. Cada valor nonce só pode ser usado para uma transação.

Durable Nonce
Standard Blockhash

A conta nonce custa ~0,0015 SOL para isenção de rent. Uma conta nonce = uma transação pendente de cada vez. Para fluxos de trabalho paralelos, crie múltiplas contas nonce.

Criar uma conta nonce

Criar uma conta nonce requer duas instruções numa única transação:

  1. Criar a conta usando getCreateAccountInstruction do System Program
  2. Inicializá-la como nonce usando getInitializeNonceAccountInstruction

Gerar keypair

Gere um novo keypair para usar como endereço da conta nonce e calcule o espaço necessário e o rent.

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

Instrução de criação de conta

Crie a conta pertencente ao System Program com lamports suficientes para isenção de 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
});

Instrução de inicialização de nonce

Inicialize a conta como uma conta nonce, definindo a autoridade que pode avançá-la.

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

Construir transação

Construa uma transação com ambas as instruções.

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

Assinar e enviar

Assine e envie a transação para criar e inicializar a conta nonce.

Gerar keypair

Gere um novo keypair para usar como endereço da conta nonce e calcule o espaço necessário e o rent.

Instrução de criação de conta

Crie a conta pertencente ao System Program com lamports suficientes para isenção de rent.

Instrução de inicialização de nonce

Inicialize a conta como uma conta nonce, definindo a autoridade que pode avançá-la.

Construir transação

Construa uma transação com ambas as instruções.

Assinar e enviar

Assine e envie a transação para criar e inicializar a conta nonce.

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

Construir uma transação diferida

Em vez de um blockhash recente, use o blockhash da conta nonce como tempo de vida da transação.

Buscar o nonce

Busque os dados da conta nonce. Use o blockhash da conta nonce como tempo de vida da transação.

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

Criar instrução de transferência

Crie a instrução para o seu pagamento. Este exemplo mostra uma transferência de token.

Construir transação com nonce durável

Use setTransactionMessageLifetimeUsingDurableNonce que define o nonce como blockhash e automaticamente adiciona a instrução de avanço do nonce no início.

Assinar transação

Assine a transação. Ela agora usa o nonce durável em vez de um blockhash padrão.

Buscar o nonce

Busque os dados da conta nonce. Use o blockhash da conta nonce como tempo de vida da transação.

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

Criar instrução de transferência

Crie a instrução para o seu pagamento. Este exemplo mostra uma transferência de token.

Construir transação com nonce durável

Use setTransactionMessageLifetimeUsingDurableNonce que define o nonce como blockhash e automaticamente adiciona a instrução de avanço do nonce no início.

Assinar transação

Assine a transação. Ela agora usa o nonce durável em vez de um blockhash padrão.

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

Armazenar ou enviar transação

Após assinar, codifique a transação para armazenamento. Quando estiver pronto, envie-a para a rede.

Codificar para armazenamento

Codifique a transação assinada para base64. Armazene este valor na sua base de dados.

Enviar transação

Envie a transação assinada quando estiver pronto. A transação permanece válida até que o nonce seja avançado.

Codificar para armazenamento

Codifique a transação assinada para base64. Armazene este valor na sua base de dados.

Enviar transação

Envie a transação assinada quando estiver pronto. A transação permanece válida até que o nonce seja avançado.

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

Demonstração

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.

Invalidar uma transação pendente

Cada conta nonce blockhash só pode ser usada uma vez. Para invalidar uma transação pendente ou preparar a conta nonce para reutilização, avance-a manualmente:

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

Isto gera um novo valor de nonce, tornando qualquer transação assinada com o valor antigo permanentemente inválida.

Fluxo de trabalho de aprovação multipartidária

Desserialize a transação para adicionar assinaturas adicionais, depois serialize novamente para armazenamento ou submissão:

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

A transação pode ser serializada, armazenada e passada entre aprovadores. Assim que todas as assinaturas necessárias forem recolhidas, submeta à rede.

Considerações de produção

Gestão de contas nonce:

  • Criar um conjunto de contas nonce para preparação paralela de transações
  • Rastrear quais nonces estão "em uso" (têm transações assinadas pendentes)
  • Implementar reciclagem de nonce após as transações serem submetidas ou abandonadas

Segurança:

  • A autoridade nonce controla se as transações podem ser invalidadas. Considere separar a autoridade nonce dos signatários de transações para controlo adicional e separação de funções
  • Qualquer pessoa com os bytes da transação serializada pode submetê-la à rede

Recursos relacionados

Is this page helpful?

Índice

Editar Página

Gerenciado por

© 2026 Fundação Solana.
Todos os direitos reservados.
Conecte-se
  • Blog