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ário | Por que transações padrão falham |
|---|---|
| Operações de tesouraria | CFO em Tóquio assina, Controller em NYC aprova—90 segundos não são suficientes |
| Fluxos de conformidade | Transações precisam de revisão legal/conformidade antes da execução |
| Assinatura em armazenamento frio | Máquinas isoladas requerem transferência manual de transações assinadas |
| Preparação em lote | Preparar folha de pagamento ou desembolsos durante horário comercial, executar à noite |
| Coordenação multi-assinatura | Múltiplos aprovadores em fusos horários diferentes |
| Pagamentos agendados | Agendar 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.
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:
- Criar a conta usando
getCreateAccountInstructiondo System Program - 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.
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.
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.
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.
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.
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.
{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.
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.
Demonstração
// 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// =============================================================================
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 transactiongetAdvanceNonceAccountInstruction({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 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);
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?