Setiap transaksi Solana menyertakan blockhash terkini—referensi ke status jaringan terkini yang membuktikan transaksi dibuat "sekarang." Jaringan menolak transaksi apa pun dengan blockhash yang lebih lama dari ~150 blok (~60-90 detik), mencegah serangan replay dan pengiriman yang kedaluwarsa. Ini bekerja sempurna untuk pembayaran real-time. Namun, ini merusak alur kerja yang memerlukan jeda antara penandatanganan dan pengiriman, seperti:
| Skenario | Mengapa transaksi standar gagal |
|---|---|
| Operasi treasury | CFO di Tokyo menandatangani, Controller di NYC menyetujui—90 detik tidak cukup |
| Alur kerja kepatuhan | Transaksi memerlukan tinjauan hukum/kepatuhan sebelum eksekusi |
| Penandatanganan cold storage | Mesin air-gapped memerlukan transfer manual transaksi yang ditandatangani |
| Persiapan batch | Siapkan payroll atau pencairan selama jam kerja, eksekusi di malam hari |
| Koordinasi multi-sig | Beberapa approver di zona waktu berbeda |
| Pembayaran terjadwal | Jadwalkan pembayaran untuk dieksekusi di tanggal mendatang |
Dalam keuangan tradisional, cek yang ditandatangani tidak kedaluwarsa dalam 90 detik. Operasi blockchain tertentu juga seharusnya tidak demikian. Durable nonces mengatasi ini dengan mengganti blockhash terkini dengan nilai tersimpan yang persisten yang hanya berubah saat Anda menggunakannya—memberi Anda transaksi yang tetap valid hingga Anda siap mengirimkannya.
Cara kerjanya
Alih-alih blockhash terbaru (valid ~150 blok), Anda menggunakan akun nonce, sebuah akun khusus yang menyimpan nilai unik yang dapat digunakan sebagai pengganti blockhash. Setiap transaksi yang menggunakan nonce ini harus "memajukannya" sebagai instruksi pertama. Setiap nilai nonce hanya dapat digunakan untuk satu transaksi.
Akun nonce memerlukan biaya ~0.0015 SOL untuk pembebasan rent. Satu akun nonce = satu transaksi tertunda pada satu waktu. Untuk alur kerja paralel, buat beberapa akun nonce.
Membuat akun nonce
Membuat akun nonce memerlukan dua instruksi dalam satu transaksi:
- Buat akun menggunakan
getCreateAccountInstructiondari System Program - Inisialisasi sebagai nonce menggunakan
getInitializeNonceAccountInstruction
Generate Keypair
Buat keypair baru untuk digunakan sebagai alamat akun nonce dan hitung ruang serta rent yang diperlukan.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
Create Account Instruction
Buat akun yang dimiliki oleh System Program dengan lamport yang cukup untuk pembebasan 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});
Initialize Nonce Instruction
Inisialisasi akun sebagai akun nonce, tetapkan otoritas yang dapat memajukannya.
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});
Build Transaction
Bangun transaksi dengan kedua instruksi.
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));
Sign and Send
Tanda tangani dan kirim transaksi untuk membuat dan menginisialisasi akun nonce.
Membangun transaksi tertunda
Alih-alih blockhash terkini, gunakan blockhash dari akun nonce sebagai
lifetime transaksi.
Ambil nonce
Ambil data dari akun nonce. Gunakan blockhash dari akun nonce sebagai lifetime
transaksi.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
Buat instruksi transfer
Buat instruksi untuk pembayaran Anda. Contoh ini menunjukkan transfer token.
Bangun transaksi dengan durable nonce
Gunakan setTransactionMessageLifetimeUsingDurableNonce yang menetapkan nonce
sebagai blockhash dan secara otomatis menambahkan instruksi advance nonce di
awal.
Tanda tangani transaksi
Tanda tangani transaksi. Transaksi sekarang menggunakan durable nonce alih-alih blockhash standar.
Simpan atau kirim transaksi
Setelah menandatangani, encode transaksi untuk penyimpanan. Saat siap, kirim ke jaringan.
Encode untuk penyimpanan
Encode transaksi yang telah ditandatangani ke base64. Simpan nilai ini di database Anda.
Kirim transaksi
Kirim transaksi yang telah ditandatangani saat siap. Transaksi tetap valid hingga nonce dimajukan.
Demo
// 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// =============================================================================
Membatalkan transaksi tertunda
Setiap akun nonce blockhash hanya dapat digunakan sekali. Untuk membatalkan
transaksi tertunda atau mempersiapkan akun nonce untuk digunakan kembali,
majukan secara manual:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
Ini menghasilkan nilai nonce baru, membuat transaksi apa pun yang ditandatangani dengan nilai lama menjadi tidak valid secara permanen.
Alur kerja persetujuan multi-pihak
Deserialisasi transaksi untuk menambahkan tanda tangan tambahan, kemudian serialisasi lagi untuk penyimpanan atau pengiriman:
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);
Transaksi dapat diserialisasi, disimpan, dan diteruskan antar pemberi persetujuan. Setelah semua tanda tangan yang diperlukan terkumpul, kirim ke jaringan.
Pertimbangan produksi
Manajemen akun nonce:
- Buat kumpulan akun nonce untuk persiapan transaksi paralel
- Lacak nonce mana yang "sedang digunakan" (memiliki transaksi tertanda yang tertunda)
- Implementasikan daur ulang nonce setelah transaksi dikirim atau dibatalkan
Keamanan:
- Otoritas nonce mengontrol apakah transaksi dapat dibatalkan. Pertimbangkan untuk memisahkan otoritas nonce dari penandatangan transaksi untuk kontrol tambahan dan pemisahan tugas
- Siapa pun yang memiliki byte transaksi terserialisasi dapat mengirimkannya ke jaringan
Sumber daya terkait
Is this page helpful?