18 min de lecture
Aperçu
Les nonces durables constituent un outil pratique permettant d'éviter l'expiration des transactions. Dans ce guide, nous allons vous montrer comment utiliser les nonces durables pour signer et envoyer des transactions hors ligne sur Solana sans craindre que votre transaction n'expire.
Ce que vous ferez
Dans ce guide, vous allez :
- Créer un compte « nonce » durable
- Créer et sérialiser une transaction à l'aide du nonce durable
- Signer la transaction dans un environnement hors ligne simulé
- Envoyer la transaction signée au réseau Solana
- Testez plusieurs scénarios pour voir comment fonctionnent les nonces
Ce dont vous aurez besoin
- Connaissances de base sur les principes fondamentaux de Solana
- Expérience avec les transactions Solana de base
- Dernière version de Solana CLI installée
| Dépendance | Version |
|---|---|
| node.js | 18.12.1 |
| tsc | 5.0.2 |
| ts-node | 10.9.1 |
| solana-cli | 1.14.16 |
| @solana/web3.js | 1.74.0 |
| bs58 | 5.0.0 |
Qu'est-ce qu'un « nonce » ?
Un « nonce » est un nombre qui n'est utilisé qu'une seule fois. Dans le contexte de Solana, un « nonce » est un nombre utilisé pour empêcher les attaques par rejeu. Une attaque par rejeu consiste à intercepter une transaction et à la renvoyer sur le réseau.
Les transactions Solana typiques incluent un hachage de bloc récent dans les données de la transaction, afin que le moteur d'exécution puisse vérifier que celle-ci est unique. Pour limiter la quantité d'historique que le moteur d'exécution doit vérifier, Solana ne prend en compte que les 150 derniers blocs. Cela signifie que la deuxième transaction échouera si deux transactions identiques sont envoyées à moins de 150 blocs l'une de l'autre. Cela signifie également que les transactions périmées (datant de plus de 150 blocs) échoueront.
Malheureusement, si vous envoyez des transactions hors ligne (ou si vous êtes soumis à d’autres contraintes particulièrement chronophages), vous risquez de rencontrer des problèmes liés à l’expiration des transactions. C’est là que les nonces durables entrent en jeu. Solana vous permet de créer un type de compte spécial, un « compte nonce ». Vous pouvez considérer ce compte comme votre propre file d’attente privée de hachages de blocs. Vous pouvez générer de nouveaux identifiants uniques, passer à l’identifiant suivant, voire transférer le contrôle de la file d’attente à quelqu’un d’autre. Ce compte contient une valeur unique, ou « nonce ». Vous pouvez inclure ce nonce à la place du hachage de bloc récent lors de la création d’une transaction. Pour prévenir les attaques par rejeu, le nonce est modifié à chaque fois en appelant advanceNonceAccount dans la première instruction de la transaction. Les transactions qui tentent d'utiliser un compte « nonce » sans que la valeur du nonce n'augmente échoueront. Voici un exemple :
// The nonceAdvance method is on the SystemProgram class and returns a TransactionInstruction (like SystemProgram.transfer)
const advanceIx = SystemProgram.nonceAdvance({
authorizedPubkey: nonceAuthKeypair.publicKey,
noncePubkey: nonceKeypair.publicKey
})
const transferIx = SystemProgram.transfer({
fromPubkey: senderKeypair.publicKey,
toPubkey: destination.publicKey,
lamports: TRANSFER_AMOUNT,
});
const sampleTx = new Transaction();
// Add the nonce advance instruction to the transaction first
sampleTx.add(advanceIx, transferIx);
Voici ce qu'offrent les « Durable Nonces » de Solana : un moyen de préparer une transaction à l'avance avec un identifiant unique qui ne sera pas rejeté au motif qu'il est trop ancien. Cela permet de programmer des transactions pour une exécution ultérieure tout en prévenant la fraude et en préservant l'ordre dans la file d'attente des transactions.
Créer un script de nonce durable
Configurer votre environnement
Créons un nouveau projet Node.js et installons la bibliothèque Solana-Web3.js. Dans votre terminal, saisissez les commandes suivantes dans l'ordre :
mkdir offline-tx && cd offline-tx && echo > app.ts
npm init -y # or yarn init -y
npm install @solana/web3.js@1 bs58 # or yarn add @solana/web3.js@1 bs58
Ouvrez le app.ts fichier dans votre éditeur préféré et ajoutez les importations suivantes :
import { Connection, Keypair, LAMPORTS_PER_SOL, NonceAccount, NONCE_ACCOUNT_LENGTH, SystemProgram, Transaction, TransactionSignature, TransactionConfirmationStatus, SignatureStatus } from "@solana/web3.js";
import { encode, decode } from 'bs58';
import fs from 'fs';
Nous importons les dépendances nécessaires depuis @solana/web3.js, bs58 (un module JavaScript permettant d'effectuer l'encodage et le décodage en base 58), et fs (pour nous permettre de lire et d'écrire des fichiers dans le répertoire de notre projet).
Définissons quelques constantes que nous utiliserons tout au long de ce guide. Ajoutez le code suivant à votre app.ts fichier situé sous vos importations :
const RPC_URL = 'http://127.0.0.1:8899';
const TRANSFER_AMOUNT = LAMPORTS_PER_SOL * 0,01;
const nonceAuthKeypair = Keypair.générer();
const nonceKeypair = Keypair.générer();
const senderKeypair = Keypair.générer();
const connexion = new Connection(RPC_URL);
Voyons en détail ce que chacun de ces éléments signifie :
RPC_URL- l'URL du cluster Solana local par défaut (si vous préférez utiliser le devnet ou le mainnet, il vous suffit de remplacer l'URL de connexion par celle de votre Point de terminaison RPC Quicknode)MONTANT_DU_VERSEMENT- le nombre de SOL que nous allons transférer dans notre exemple de transactionpaire de clés d'authentification nonce- la paire de clés du compte de l'autorité de noncepaire de clés nonce- la paire de clés du compte « nonce »paire de clés de l'expéditeur- la paire de clés du compte de l'expéditeurconnexion- la connexion à un cluster Solana local
Enfin, créez une fonction asynchrone nommée principal et ajoutez le code suivant :
async function main() {
const { useNonce, waitTime } = parseCommandLineArgs();
console.log(`Attempting to send a transaction using a ${useNonce ? "nonce" : "recent blockhash"}. Waiting ${waitTime}ms before signing to simulate an offline transaction.`)
try {
// Step 1 - Fund the nonce authority account
await fundAccounts([nonceAuthKeypair, senderKeypair]);
// Step 2 - Create the nonce account
await createNonce();
// Step 3 - Create a transaction
await createTx(useNonce);
// Step 4 - Sign the transaction offline
await signOffline(waitTime, useNonce);
// Step 5 - Execute the transaction
await executeTx();
} catch (error) {
console.error(error);
}
}
Nous allons présenter les étapes nécessaires pour créer un nonce, générer une transaction, la signer hors ligne et l'exécuter. Nous préciserons les détails de chaque étape au fur et à mesure. Nous utiliserons également des arguments de ligne de commande pour permettre de tester certains scénarios plus loin dans ce guide. Nous utiliserons un booléen, useNonce, et un temps d'attente en ms pour nous aider à tester notre signature hors ligne.
Créer des fonctions d'aide
Créons quelques fonctions qui nous aideront à automatiser les tâches répétitives.
Récupérer les informations relatives au nonce
Pour récupérer les informations relatives au nonce, nous devrons obtenir les informations du compte associé à ce nonce. Pour ce faire, nous utiliserons la getAccountInfo méthode issue de la Connexion classe. Nous devons également décoder les données du compte à l'aide de la fromAccountData méthode issue de la Compte temporaire classe. Ajoutez le code suivant à votre app.ts fichier :
async function fetchNonceInfo() {
const accountInfo = await connection.getAccountInfo(nonceKeypair.publicKey);
if (!accountInfo) throw new Error("No account info found");
const nonceAccount = NonceAccount.fromAccountData(accountInfo.data);
console.log(" Auth:", nonceAccount.authorizedPubkey.toBase58());
console.log(" Nonce:", nonceAccount.nonce);
return nonceAccount;
}
Analyser les arguments de la ligne de commande
Nous utiliserons des arguments de ligne de commande pour mettre en place certains tests de scénarios plus loin dans ce guide. Nous utiliserons un booléen, useNonce, et un temps d'attente dans ms pour nous aider à tester notre signature hors ligne. Ajoutez le code suivant à votre app.ts fichier :
function parseCommandLineArgs() {
let useNonce = false;
let waitTime = 120000;
for (let i = 2; i < process.argv.length; i++) {
if (process.argv[i] === '-useNonce') {
useNonce = true;
} else if (process.argv[i] === '-waitTime') {
if (i + 1 < process.argv.length) {
waitTime = parseInt(process.argv[i + 1]);
i++;
} else {
console.error('Error: The -waitTime flag requires an argument');
process.exit(1);
}
} else {
console.error(`Error: Unknown argument '${process.argv[i]}'`);
process.exit(1);
}
}
return { useNonce, waitTime };
}
Cette fonction analysera les arguments de ligne de commande (« -useNonce » et « -waitTime ») à l'aide d'un boucle « for » qui parcourt chaque argument de ligne de commande à l'aide de process.argv. La fonction renverra le useNonce et temps d'attente valeurs.
Encodage et enregistrement d'une transaction
Créons une fonction qui encode et enregistre notre transaction sérialisée dans un fichier. Ajoutez le code suivant à votre app.ts fichier :
async function encodeAndWriteTransaction(tx: Transaction, filename: string, requireAllSignatures = true) {
const serialisedTx = encode(tx.serialize({ requireAllSignatures }));
fs.writeFileSync(filename, serialisedTx);
console.log(` Tx written to ${filename}`);
return serialisedTx;
}
Nous utilisons le encoder méthode issue de la bs58 bibliothèque que nous avons importée précédemment pour encoder la transaction sérialisée. Nous utilisons ensuite la writeFileSync méthode issue de la fs bibliothèque permettant d'écrire la transaction encodée dans un fichier. Nous disposons d'un paramètre facultatif, requireAllSignatures dont la valeur par défaut est vrai. Cela impliquera que toutes les signatures doivent figurer dans la transaction. Notre transaction partielle et non signée ne comportera pas toutes les signatures ; nous allons donc définir ce paramètre sur false lorsque nous appelons cette fonction.
Lire et décoder une transaction
Créons une fonction « counter » pour lire et décoder notre transaction sérialisée à partir d'un fichier. Ajoutez le code suivant à votre app.ts fichier :
async function readAndDecodeTransaction(filename: string): Promise<Transaction> {
const transactionData = fs.readFileSync(filename, 'utf-8');
const decodedData = decode(transactionData);
const transaction = Transaction.from(decodedData);
return transaction;
}
Nous utilisons le fs library to read the transaction data from the file. We then use the décoder méthode issue de la bs58 bibliothèque. Enfin, nous utilisons la from méthode issue de la Transaction classe permettant de créer un objet de transaction à partir des données décodées.
Confirmer la transaction
Commençons par définir une fonction d'aide permettant de vérifier que les transactions ont bien été traitées par le cluster. Ajoutez le code suivant à votre app.ts fichier :
async function confirmTransaction(
connection: Connection,
signature: TransactionSignature,
desiredConfirmationStatus: TransactionConfirmationStatus = 'confirmed',
timeout: number = 30000,
pollInterval: number = 1000,
searchTransactionHistory: boolean = false
): Promise<SignatureStatus> {
const start = Date.now();
while (Date.now() - start < timeout) {
const { value: statuses } = await connection.getSignatureStatuses([signature], { searchTransactionHistory });
if (!statuses || statuses.length === 0) {
throw new Error('Failed to get signature status');
}
const status = statuses[0];
if (status === null) {
await new Promise(resolve => setTimeout(resolve, pollInterval));
continue;
}
if (status.err) {
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
}
if (status.confirmationStatus && status.confirmationStatus === desiredConfirmationStatus) {
return status;
}
if (status.confirmationStatus === 'finalized') {
return status;
}
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
}
Cette fonction interroge le réseau Solana pour connaître l'état de la transaction jusqu'à ce que celle-ci soit confirmée ou que le délai d'expiration soit atteint. Nous avons défini des valeurs par défaut pour le délai d'expiration et l'intervalle d'interrogation, mais vous pouvez les modifier selon vos besoins.
Bravo ! Maintenant, développons chacune des étapes que nous avons décrites dans notre principal fonction.
Étape 1 - Approvisionner les comptes des payeurs
Nous aurons besoin d'un peu de SOL de test pour mettre en œuvre nos transactions. Créons une fonction appelée comptes de fonds et ajoutez le code suivant :
async function fundAccounts(accountsToFund: Keypair[]) {
console.log("---Step 1---Funding accounts");
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
const airdropPromises = accountsToFund.map(account => {
return connection.requestAirdrop(account.publicKey, LAMPORTS_PER_SOL);
});
const airDropSignatures = await Promise.all(airdropPromises).catch(error => {
console.error("Failed to request airdrops: ", error);
throw error;
});
const airdropConfirmations = airDropSignatures.map(signature => {
return confirmTransaction(connection, signature, 'finalized');
});
await Promise.all(airdropConfirmations).catch(error => {
console.error("Failed to confirm airdrops: ", error);
throw error;
});
}
Il suffit de passer un tableau de Paires de clés et demander un airdrop de 1 SOL pour chacun d'entre eux à l'aide de la demander un airdrop méthode. Nous attendons la confirmation des airdrops (finalisé) sur le réseau afin de garantir que nos fonds soient disponibles pour les étapes suivantes.
Étape 2 - Créer le compte « nonce »
Nous devons créer un compte « nonce » avant de créer une transaction utilisant un nonce. Créons une fonction appelée createNonce et ajoutez le code suivant :
async function createNonce() {
console.log("---Step 2---Creating nonce account");
const newNonceTx = new Transaction();
const rent = await connection.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH);
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
newNonceTx.feePayer = nonceAuthKeypair.publicKey;
newNonceTx.recentBlockhash = blockhash;
newNonceTx.lastValidBlockHeight = lastValidBlockHeight;
newNonceTx.add(
SystemProgram.createAccount({
fromPubkey: nonceAuthKeypair.publicKey,
newAccountPubkey: nonceKeypair.publicKey,
lamports: rent,
space: NONCE_ACCOUNT_LENGTH,
programId: SystemProgram.programId,
}),
SystemProgram.nonceInitialize({
noncePubkey: nonceKeypair.publicKey,
authorizedPubkey: nonceAuthKeypair.publicKey,
})
);
newNonceTx.sign(nonceKeypair, nonceAuthKeypair);
try {
const signature = await connection.sendRawTransaction(newNonceTx.serialize());
await confirmTransaction(connection, signature, 'finalized');
console.log(" Nonce Acct Created: ", signature);
} catch (error) {
console.error("Failed to create nonce account: ", error);
throw error;
}
}
Nous créons une nouvelle transaction, newNonceTx, et en ajoutant deux instructions. La première consiste à créer un nouveau compte à l'aide de la SystemProgram.createAccount méthode. Nous utilisons la paire de clés d'authentification nonce à verser pour approvisionner le nouveau compte, paire de clés nonce. Nous utilisons également le SystemProgram.nonceInitialize méthode permettant d'initialiser ce compte en tant que nonce. Nous utilisons la paire de clés d'authentification nonce car le clé publique autorisée et le paire de clés nonce car le noncePubkey. Nous signons ensuite la transaction avec le paire de clés nonce et le paire de clés d'authentification nonce et l'envoyer au réseau.
Si la transaction aboutit, nous disposerons alors d'un compte « nonce » pouvant être utilisé lors des transactions ultérieures avec notre paire de clés d'authentification nonce en tant qu'autorité.
Étape 3 - Créer une transaction
Nous sommes désormais prêts à créer une transaction que nous utiliserons pour notre signature hors ligne. Créons une fonction nommée createTx et ajoutez le code suivant :
async function createTx(useNonce = false) {
console.log("---Step 3---Creating transaction");
const destination = Keypair.generate();
const transferIx = SystemProgram.transfer({
fromPubkey: senderKeypair.publicKey,
toPubkey: destination.publicKey,
lamports: TRANSFER_AMOUNT,
});
const advanceIx = SystemProgram.nonceAdvance({
authorizedPubkey: nonceAuthKeypair.publicKey,
noncePubkey: nonceKeypair.publicKey
})
const sampleTx = new Transaction();
if (useNonce) {
sampleTx.add(advanceIx, transferIx);
const nonceAccount = await fetchNonceInfo();
sampleTx.recentBlockhash = nonceAccount.nonce;
}
else {
sampleTx.add(transferIx);
sampleTx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
}
sampleTx.feePayer = senderKeypair.publicKey;
const serialisedTx = encodeAndWriteTransaction(sampleTx, './unsigned.json', false);
return serialisedTx;
}
Notre fonction accepte un paramètre, useNonce, que nous utiliserons pour déterminer si nous souhaitons utiliser un nonce dans notre transaction (ou un hash de bloc récent dans le cas contraire). La transaction transférera MONTANT_DU_VERSEMENT tiré de la paire de clés de l'expéditeur vers un nouveau compte généré aléatoirement, destination. La fonction renverra la transaction sérialisée afin que nous puissions l'utiliser pour notre signature hors ligne. Analysons un peu cette fonction :
- Nous sommes en train de créer un nouveau
destinationcompte à l'aide duKeypair.generate()méthode. - Nous sommes en train de créer un
transferIxinstruction utilisant leSystemProgram.transferméthode. Cette instruction permettra de transférerMONTANT_DU_VERSEMENTtiré de lapaire de clés de l'expéditeurà ladestinationcompte. - Nous sommes en train de créer un
advanceIxinstruction utilisant leSystemProgram.nonceAdvanceméthode. Cette instruction fera progresser le compteur de nonce à l'aide de lapaire de clés d'authentification nonceen tant qu'autorité. - Nous sommes en train de créer un nouveau
sampleTxtransaction. - Si vous vous en souvenez, lorsque l'on utilise des comptes « Nonce », il faut incrémenter la valeur « nonce » au moment de les utiliser. Si
useNoncesi c'est vrai, on ajoute leadvanceIxinstruction relative à la transaction avant latransferIxinstruction. Nous définissons également lerecentBlockhashau nonce du compte nonce. - Si
useNoncesi elle est fausse, on se contente de mettre letransferIxinstruction relative à la transaction et définir lerecentBlockhashau dernier hachage de bloc du réseau. - Nous définissons le
payeurqu'il s'agisse du même compte effectuant le transfert de SOL (paire de clés de l'expéditeur)
Étape 4 - Signer la transaction hors ligne
Au lieu de déconnecter notre connexion Internet ou d'utiliser un dispositif de stockage à froid, nous simulerons une transaction hors ligne en ajoutant un certain temps de traitement (temps d'attente) à notre processus de signature. Créons une fonction appelée signOffline et ajoutez le code suivant :
async function signOffline(waitTime = 120000, useNonce = false): Promise<string> {
console.log("---Step 4---Signing transaction offline");
await new Promise((resolve) => setTimeout(resolve, waitTime));
const unsignedTx = await readAndDecodeTransaction('./unsigned.json');
if (useNonce) unsignedTx.sign(nonceAuthKeypair, senderKeypair);
else unsignedTx.sign(senderKeypair);
const serialisedTx = encodeAndWriteTransaction(unsignedTx, './signed.json');
return serialisedTx;
}
Comme vous pouvez le voir, nous transmettons un paramètre facultatif, temps d'attente, qui est appelée via un setTimeout fonction. Cela simulera le temps nécessaire pour transférer la transaction vers un périphérique de stockage à froid, la signer, puis la retransférer. Nous avons intégré cette fonction en tant que paramètre afin que vous puissiez ajuster la durée et voir comment un compte « nonce » peut être utilisé pour signer une transaction hors ligne.
Nous lisons ensuite la transaction non signée à partir du fichier que nous avons créé à l'étape précédente, puis nous la signons avec le paire de clés d'authentification nonce (si useNonce est vrai) et le paire de clés de l'expéditeur, puis en enregistrant la transaction signée dans un fichier nommé signed.json. Nous récupérons la transaction sérialisée afin de pouvoir l'utiliser à l'étape suivante.
Étape 5 - Envoyer la transaction signée
Nous devrions désormais disposer d'une transaction signée enregistrée dans un fichier nommé signed.json. Créons une fonction appelée envoyerTransactionSignée qui décode la transaction et l'envoie au réseau :
async function executeTx() {
console.log("---Step 5---Executing transaction");
const signedTx = await readAndDecodeTransaction('./signed.json');
const sig = await connection.sendRawTransaction(signedTx.serialize());
console.log(" Tx sent: ", sig);
}
Notre fonction d'aide nous facilite la tâche ! Il suffit de lire la transaction signée dans le fichier, de la sérialiser, puis de l'envoyer sur le réseau.
Bravo ! Testons ça. Ajoute le code suivant à la fin de ton fichier :
Accueil();
Cela va appeler notre principal fonction lorsque nous exécutons notre programme.
Lancer le programme
Bravo pour ce que vous avez fait jusqu'à présent. Il ne nous reste plus qu'à exécuter notre programme. Si vous utilisez un réseau local comme nous l'avons fait plus haut, vous devrez ouvrir deux terminaux. Dans le premier terminal, exécutez la commande suivante :
solana-test-validator
Cela devrait lancer votre cluster Solana local.
Si vous vous en souvenez, notre programme attend deux paramètres, useNonce et temps d'attente. Effectuons trois simulations pour voir comment fonctionne le compte « nonce » :
| Simulation | useNonce | temps d'attente | Résultats attendus |
|---|---|---|---|
| 1. Simuler une transaction type à l'aide d'un hachage de bloc récent | false | 0 | Succès |
| 2. Simuler une transaction hors ligne (différée) à l'aide d'un hachage de bloc | false | 120000 | Échec |
| 3. Simuler une transaction hors ligne (différée) à l'aide d'un compte « nonce » | vrai | 120000 | Succès |
Simulation n° 1 – Transaction type
Dans le deuxième terminal, lancez votre première simulation : une transaction « en ligne » classique qui utilise un hash de bloc récent (pour cela, il suffit de définir temps d'attente à 0 et pas en utilisant le useNonce (paramètre) :
ts-node app -waitTime 0 # useNonce is false by default
Il s'agit d'une transaction classique, et nous devrions obtenir un résultat positif.
Simulation 2 - Transaction hors ligne à l'aide d'un hachage de bloc
Dans le deuxième terminal, lancez votre deuxième simulation pour simuler une transaction hors ligne en utilisant un hash de bloc récent. Comme ce hash de bloc aura expiré avant que la transaction ne soit traitée, nous nous attendons à ce qu'une erreur apparaisse. Exécutez la commande suivante :
ts-node app -waitTime 120000 # useNonce is false by default
Avez-vous remarqué une erreur (par exemple, Hachage du bloc introuvable) ? Si oui, parfait ! C'est exactement ce à quoi nous nous attendions. Sinon, essayez d'augmenter la temps d'attente et en relançant la commande.
Simulation n° 3 - Transaction hors ligne à l'aide d'un compte « nonce »
Dans le deuxième terminal, lancez votre troisième simulation pour simuler une transaction hors ligne à l'aide d'un compte « nonce ». Comme ce compte « nonce » n'expirera pas, nous nous attendons à obtenir un résultat positif. Exécutez la commande suivante :
ts-node app -useNonce -waitTime 120000
Comme nous avons utilisé un compte « nonce », nous ne devrions pas rencontrer de problème lié à l'expiration du hash du bloc. Nous devrions obtenir un résultat positif comme celui-ci :
Attempting to send a transaction using a nonce. Waiting 12000ms before signing to simulate an offline transaction.
---Step 1---Funding accounts
---Step 2---Creating nonce account
Nonce Acct Created: 3XzR...USMq
---Step 3---Creating transaction
Auth: EVxuoBFLQ8KpTLChkgW4RBU5pdxvCLUndntUsNW1cSyQ
Nonce: H4rwV9cdhcwNk4jSUxRTgrnSnkLYCTU7c8Vc3ZS6RNRQ
Tx written to ./unsigned.json
---Step 4---Signing transaction offline
Tx written to ./signed.json
---Step 5---Executing transaction
Tx sent: 5Ep3sPV1r1hr73kKQG8Q2xD4B9erPEzW7Hitxwe8iwBooXFq8iz4WC6YzrRE6VBUL8arZHqmYKBF52QrPKbRgmRK
Bravo 🎉 ! Vous avez réussi à créer un compte « nonce » et à l'utiliser pour effectuer une transaction hors ligne.
« Nonce avancé »
Vous avez réussi à créer un compte « nonce » et à l'utiliser pour effectuer une transaction hors ligne. Vous êtes prêt à passer à l'étape suivante de votre parcours avec Solana.
Comment comptez-vous intégrer les nonces et les transactions hors ligne dans votre flux de travail ? Nous serions ravis de discuter de ce que vous préparez ! Écrivez-nous sur Discord ou suivez-nous sur Twitter pour rester au courant des dernières actualités !
Nous ❤️ les commentaires !
Faites-nous savoir si vous avez des commentaires ou des suggestions de nouveaux sujets. Nous serions ravis de vous entendre.
