PaiementsPaiements avancés

Exécution différée

Chaque transaction Solana inclut un blockhash récent — une référence à un état réseau récent qui prouve que la transaction a été créée "maintenant". Le réseau rejette toute transaction avec un blockhash plus ancien que ~150 blocs (~60-90 secondes), empêchant les attaques par rejeu et les soumissions obsolètes. Cela fonctionne parfaitement pour les paiements en temps réel. Mais cela casse les flux de travail qui nécessitent un délai entre la signature et la soumission, tels que :

ScénarioPourquoi les transactions standard échouent
Opérations de trésorerieLe directeur financier à Tokyo signe, le contrôleur à New York approuve — 90 secondes ne suffisent pas
Flux de conformitéLes transactions nécessitent un examen juridique/de conformité avant l'exécution
Signature en stockage à froidLes machines isolées nécessitent un transfert manuel des transactions signées
Préparation par lotsPréparer la paie ou les décaissements pendant les heures de bureau, exécuter la nuit
Coordination multi-signaturesPlusieurs approbateurs dans différents fuseaux horaires
Paiements programmésProgrammer des paiements à exécuter à une date future

Dans la finance traditionnelle, un chèque signé n'expire pas en 90 secondes. Certaines opérations blockchain ne devraient pas non plus. Les nonces durables résolvent ce problème en remplaçant le blockhash récent par une valeur stockée et persistante qui n'avance que lorsque vous l'utilisez — vous donnant des transactions qui restent valides jusqu'à ce que vous soyez prêt à les soumettre.

Comment ça fonctionne

Au lieu d'un blockhash récent (valide ~150 blocs), vous utilisez un compte nonce, un compte spécial qui stocke une valeur unique pouvant être utilisée à la place d'un blockhash. Chaque transaction utilisant ce nonce doit l'« avancer » comme première instruction. Chaque valeur nonce ne peut être utilisée que pour une seule transaction.

Durable Nonce
Standard Blockhash

Le compte nonce coûte ~0,0015 SOL pour l'exemption de loyer. Un compte nonce = une transaction en attente à la fois. Pour les flux de travail parallèles, créez plusieurs comptes nonce.

Créer un compte nonce

La création d'un compte nonce nécessite deux instructions dans une seule transaction :

  1. Créer le compte en utilisant getCreateAccountInstruction du System Program
  2. L'initialiser comme nonce en utilisant getInitializeNonceAccountInstruction

Générer une paire de clés

Générez une nouvelle paire de clés à utiliser comme adresse du compte nonce et calculez l'espace requis et le loyer.

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

Instruction de création de compte

Créez le compte détenu par le System Program avec suffisamment de lamports pour l'exemption de loyer.

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

Instruction d'initialisation du nonce

Initialisez le compte comme compte nonce, en définissant l'autorité qui peut l'avancer.

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

Construire la transaction

Construisez une transaction avec les deux instructions.

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

Signer et envoyer

Signez et envoyez la transaction pour créer et initialiser le compte nonce.

Générer une paire de clés

Générez une nouvelle paire de clés à utiliser comme adresse du compte nonce et calculez l'espace requis et le loyer.

Instruction de création de compte

Créez le compte détenu par le System Program avec suffisamment de lamports pour l'exemption de loyer.

Instruction d'initialisation du nonce

Initialisez le compte comme compte nonce, en définissant l'autorité qui peut l'avancer.

Construire la transaction

Construisez une transaction avec les deux instructions.

Signer et envoyer

Signez et envoyez la transaction pour créer et initialiser le compte nonce.

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

Construire une transaction différée

Au lieu d'un blockhash récent, utilisez le blockhash du compte nonce comme durée de vie de la transaction.

Récupérer le nonce

Récupérez les données du compte nonce. Utilisez le blockhash du compte nonce comme durée de vie de la transaction.

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

Créer l'instruction de transfert

Créez l'instruction pour votre paiement. Cet exemple montre un transfert de token.

Construire la transaction avec un nonce durable

Utilisez setTransactionMessageLifetimeUsingDurableNonce qui définit le nonce comme blockhash et ajoute automatiquement l'instruction d'avancement du nonce au début.

Signer la transaction

Signez la transaction. Elle utilise maintenant le nonce durable au lieu d'un blockhash standard.

Récupérer le nonce

Récupérez les données du compte nonce. Utilisez le blockhash du compte nonce comme durée de vie de la transaction.

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

Créer l'instruction de transfert

Créez l'instruction pour votre paiement. Cet exemple montre un transfert de token.

Construire la transaction avec un nonce durable

Utilisez setTransactionMessageLifetimeUsingDurableNonce qui définit le nonce comme blockhash et ajoute automatiquement l'instruction d'avancement du nonce au début.

Signer la transaction

Signez la transaction. Elle utilise maintenant le nonce durable au lieu d'un blockhash standard.

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

Stocker ou envoyer la transaction

Après signature, encodez la transaction pour le stockage. Lorsque vous êtes prêt, envoyez-la au réseau.

Encoder pour le stockage

Encodez la transaction signée en base64. Stockez cette valeur dans votre base de données.

Envoyer la transaction

Envoyez la transaction signée lorsque vous êtes prêt. La transaction reste valide jusqu'à ce que le nonce soit avancé.

Encoder pour le stockage

Encodez la transaction signée en base64. Stockez cette valeur dans votre base de données.

Envoyer la transaction

Envoyez la transaction signée lorsque vous êtes prêt. La transaction reste valide jusqu'à ce que le nonce soit avancé.

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

Démo

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.

Invalider une transaction en attente

Chaque compte nonce blockhash ne peut être utilisé qu'une seule fois. Pour invalider une transaction en attente ou préparer le compte nonce pour une réutilisation, avancez-le manuellement :

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

Cela génère une nouvelle valeur de nonce, rendant toute transaction signée avec l'ancienne valeur définitivement invalide.

Flux de travail d'approbation multi-parties

Désérialisez la transaction pour ajouter des signatures supplémentaires, puis sérialisez à nouveau pour le stockage ou la soumission :

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 transaction peut être sérialisée, stockée et transmise entre les approbateurs. Une fois toutes les signatures requises collectées, soumettez-la au réseau.

Considérations pour la production

Gestion des comptes nonce :

  • Créer un pool de comptes nonce pour la préparation parallèle des transactions
  • Suivre quels nonces sont « en cours d'utilisation » (ont des transactions signées en attente)
  • Implémenter le recyclage des nonces après la soumission ou l'abandon des transactions

Sécurité :

  • L'autorité du nonce contrôle si les transactions peuvent être invalidées. Envisagez de séparer l'autorité du nonce des signataires de transactions pour un contrôle supplémentaire et une séparation des responsabilités
  • N'importe qui disposant des octets de transaction sérialisés peut la soumettre au réseau

Ressources associées

Is this page helpful?

Table des matières

Modifier la page

Géré par

© 2026 Fondation Solana.
Tous droits réservés.
Restez connecté