PagosPagos avanzados

Ejecución diferida

Cada transacción de Solana incluye un blockhash reciente, una referencia a un estado reciente de la red que demuestra que la transacción se creó "ahora". La red rechaza cualquier transacción con un blockhash anterior a ~150 bloques (~60-90 segundos), previniendo ataques de repetición y envíos obsoletos. Esto funciona perfectamente para pagos en tiempo real. Pero rompe los flujos de trabajo que necesitan un intervalo entre la firma y el envío, tales como:

EscenarioPor qué fallan las transacciones estándar
Operaciones de tesoreríaEl CFO en Tokio firma, el controlador en NYC aprueba: 90 segundos no son suficientes
Flujos de cumplimientoLas transacciones necesitan revisión legal/de cumplimiento antes de la ejecución
Firma en almacenamiento fríoLas máquinas aisladas requieren transferencia manual de transacciones firmadas
Preparación por lotesPreparar nómina o desembolsos en horario laboral, ejecutar durante la noche
Coordinación multi-firmaMúltiples aprobadores en diferentes zonas horarias
Pagos programadosProgramar pagos para ejecutarse en una fecha futura

En las finanzas tradicionales, un cheque firmado no expira en 90 segundos. Ciertas operaciones de blockchain tampoco deberían hacerlo. Los nonces duraderos resuelven esto reemplazando el blockhash reciente con un valor almacenado y persistente que solo avanza cuando lo usas, dándote transacciones que permanecen válidas hasta que estés listo para enviarlas.

Cómo funciona

En lugar de un blockhash reciente (válido ~150 bloques), usas una cuenta nonce, una cuenta especial que almacena un valor único que puede usarse en lugar de un blockhash. Cada transacción que use este nonce debe "avanzarlo" como primera instrucción. Cada valor de nonce solo puede usarse para una transacción.

Durable Nonce
Standard Blockhash

La cuenta nonce cuesta ~0.0015 SOL para exención de rent. Una cuenta nonce = una transacción pendiente a la vez. Para flujos de trabajo paralelos, crea múltiples cuentas nonce.

Crear una cuenta nonce

Crear una cuenta nonce requiere dos instrucciones en una sola transacción:

  1. Crear la cuenta usando getCreateAccountInstruction del System Program
  2. Inicializarla como nonce usando getInitializeNonceAccountInstruction

Generar keypair

Genera un nuevo keypair para usar como dirección de la cuenta nonce y calcula el espacio requerido y el rent.

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

Instrucción de crear cuenta

Crea la cuenta propiedad del System Program con suficientes lamports para la exención 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
});

Instrucción de inicializar nonce

Inicializa la cuenta como cuenta nonce, estableciendo la autoridad que puede avanzarla.

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 transacción

Construye una transacción con ambas instrucciones.

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

Firmar y enviar

Firma y envía la transacción para crear e inicializar la cuenta nonce.

Generar keypair

Genera un nuevo keypair para usar como dirección de la cuenta nonce y calcula el espacio requerido y el rent.

Instrucción de crear cuenta

Crea la cuenta propiedad del System Program con suficientes lamports para la exención de rent.

Instrucción de inicializar nonce

Inicializa la cuenta como cuenta nonce, estableciendo la autoridad que puede avanzarla.

Construir transacción

Construye una transacción con ambas instrucciones.

Firmar y enviar

Firma y envía la transacción para crear e inicializar la cuenta nonce.

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

Construir una transacción diferida

En lugar de un blockhash reciente, usa el blockhash de la cuenta nonce como tiempo de vida de la transacción.

Obtener el nonce

Obtén los datos de la cuenta nonce. Usa el blockhash de la cuenta nonce como tiempo de vida de la transacción.

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

Crear instrucción de transferencia

Crea la instrucción para tu pago. Este ejemplo muestra una transferencia de tokens.

Construir transacción con nonce duradero

Usa setTransactionMessageLifetimeUsingDurableNonce que establece el nonce como blockhash y antepone automáticamente la instrucción de avance del nonce.

Firmar transacción

Firma la transacción. Ahora usa el nonce duradero en lugar de un blockhash estándar.

Obtener el nonce

Obtén los datos de la cuenta nonce. Usa el blockhash de la cuenta nonce como tiempo de vida de la transacción.

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

Crear instrucción de transferencia

Crea la instrucción para tu pago. Este ejemplo muestra una transferencia de tokens.

Construir transacción con nonce duradero

Usa setTransactionMessageLifetimeUsingDurableNonce que establece el nonce como blockhash y antepone automáticamente la instrucción de avance del nonce.

Firmar transacción

Firma la transacción. Ahora usa el nonce duradero en lugar de un blockhash estándar.

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

Almacenar o enviar transacción

Después de firmar, codifica la transacción para almacenarla. Cuando esté lista, envíala a la red.

Codificar para almacenamiento

Codifica la transacción firmada a base64. Almacena este valor en tu base de datos.

Enviar transacción

Envía la transacción firmada cuando esté lista. La transacción permanece válida hasta que el nonce sea avanzado.

Codificar para almacenamiento

Codifica la transacción firmada a base64. Almacena este valor en tu base de datos.

Enviar transacción

Envía la transacción firmada cuando esté lista. La transacción permanece válida hasta que el nonce sea avanzado.

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.

Invalidar una transacción pendiente

Cada cuenta nonce blockhash solo puede usarse una vez. Para invalidar una transacción pendiente o preparar la cuenta nonce para reutilizarla, avánzala manualmente:

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

Esto genera un nuevo valor de nonce, haciendo que cualquier transacción firmada con el valor antiguo sea permanentemente inválida.

Flujo de trabajo de aprobación multipartita

Deserializa la transacción para agregar firmas adicionales, luego serializa nuevamente para almacenarla o enviarla:

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

La transacción puede ser serializada, almacenada y pasada entre aprobadores. Una vez que se recopilen todas las firmas requeridas, envíala a la red.

Consideraciones de producción

Gestión de cuentas nonce:

  • Crear un grupo de cuentas nonce para la preparación paralela de transacciones
  • Rastrear qué nonces están "en uso" (tienen transacciones firmadas pendientes)
  • Implementar el reciclaje de nonces después de que las transacciones se envíen o abandonen

Seguridad:

  • La autoridad del nonce controla si las transacciones pueden invalidarse. Considera separar la autoridad del nonce de los firmantes de transacciones para un control adicional y separación de funciones
  • Cualquiera con los bytes de transacción serializados puede enviarla a la red

Recursos relacionados

Is this page helpful?

Tabla de Contenidos

Editar Página

Gestionado por

© 2026 Fundación Solana.
Todos los derechos reservados.
Conéctate