تتضمن كل معاملة على سولانا قيمة تجزئة كتلة حديثة—وهي إشارة إلى حالة شبكة حديثة تثبت أن المعاملة تم إنشاؤها "الآن". ترفض الشبكة أي معاملة تحتوي على قيمة تجزئة كتلة أقدم من ~150 كتلة (~60-90 ثانية)، مما يمنع هجمات إعادة التشغيل والإرسالات القديمة. يعمل هذا بشكل مثالي للمدفوعات في الوقت الفعلي. لكنه يعطل سير العمل الذي يحتاج إلى فجوة بين التوقيع والإرسال، مثل:
| السيناريو | لماذا تفشل المعاملات القياسية |
|---|---|
| عمليات الخزينة | المدير المالي في طوكيو يوقّع، المراقب في نيويورك يوافق—90 ثانية ليست كافية |
| سير عمل الامتثال | تحتاج المعاملات إلى مراجعة قانونية/امتثال قبل التنفيذ |
| التوقيع من التخزين البارد | تتطلب الأجهزة المعزولة عن الشبكة نقلًا يدويًا للمعاملات الموقعة |
| إعداد الدفعات | إعداد كشوف الرواتب أو الصرفيات خلال ساعات العمل، وتنفيذها ليلاً |
| تنسيق التوقيع المتعدد | عدة موافقين عبر مناطق زمنية مختلفة |
| المدفوعات المجدولة | جدولة المدفوعات لتنفيذها في تاريخ مستقبلي |
في التمويل التقليدي، لا تنتهي صلاحية الشيك الموقع في 90 ثانية. بعض عمليات البلوكشين لا ينبغي أن تنتهي صلاحيتها أيضًا. القيم الفريدة الدائمة تحل هذه المشكلة عن طريق استبدال قيمة تجزئة الكتلة الحديثة بقيمة مخزنة ومستمرة لا تتقدم إلا عند استخدامها—مما يمنحك معاملات تظل صالحة حتى تصبح جاهزًا لإرسالها.
كيف يعمل
بدلاً من blockhash حديث (صالح لحوالي 150 كتلة)، تستخدم حساب nonce، حساب خاص يخزن قيمة فريدة يمكن استخدامها بدلاً من blockhash. يجب على كل معاملة تستخدم هذا nonce "تقديمه" كأول تعليمة. يمكن استخدام كل قيمة nonce لمعاملة واحدة فقط.
يكلف حساب nonce حوالي 0.0015 SOL للإعفاء من الإيجار. حساب nonce واحد = معاملة معلقة واحدة في كل مرة. لسير العمل المتوازي، قم بإنشاء عدة حسابات nonce.
إنشاء حساب Nonce
يتطلب إنشاء حساب nonce تعليمتين في معاملة واحدة:
- إنشاء الحساب باستخدام
getCreateAccountInstructionمن System Program - تهيئته كـ nonce باستخدام
getInitializeNonceAccountInstruction
توليد Keypair
قم بتوليد keypair جديد لاستخدامه كعنوان حساب nonce واحسب المساحة المطلوبة والإيجار.
const nonceKeypair = await generateKeyPairSigner();const nonceSpace = BigInt(getNonceSize());const nonceRent = await rpc.getMinimumBalanceForRentExemption(nonceSpace).send();
تعليمة إنشاء الحساب
قم بإنشاء الحساب المملوك من قبل System Program مع ما يكفي من lamports للإعفاء من الإيجار.
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});
تعليمة تهيئة Nonce
قم بتهيئة الحساب كحساب nonce، مع تعيين الصلاحية التي يمكنها تقديمه.
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 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));
التوقيع والإرسال
قم بتوقيع وإرسال المعاملة لإنشاء وتهيئة حساب nonce.
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));const signedCreateNonceTx =await signTransactionMessageWithSigners(createNonceTx);await sendAndConfirmTransactionFactory({ rpc, rpcSubscriptions })(signedCreateNonceTx,{ commitment: "confirmed" });
بناء معاملة مؤجلة
بدلاً من تجزئة كتلة حديثة، استخدم blockhash الخاص بحساب nonce كعمر افتراضي
للمعاملة.
جلب قيمة nonce
اجلب البيانات من حساب nonce. استخدم blockhash من حساب nonce كعمر افتراضي
للمعاملة.
{version: 1,state: 1,authority: 'HgjaL8artMtmntaQDVM2UBk3gppsYYERS4PkUhiaLZD1',blockhash: '5U7seXqfgZx1uh5DFhdH1vyBhr7XGRrKxBAnJJTbbUa',lamportsPerSignature: 5000n}
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);
إنشاء تعليمة التحويل
أنشئ التعليمة الخاصة بالدفع. يوضح هذا المثال تحويل رمز مميز.
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);const transferInstruction = getTransferInstruction({source: senderAta,destination: recipientAta,authority: sender.address,amount: 250_000n});
بناء معاملة باستخدام nonce دائم
استخدم setTransactionMessageLifetimeUsingDurableNonce الذي يعين nonce كتجزئة
كتلة ويضيف تلقائياً تعليمة تقديم nonce في البداية.
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);const transferInstruction = getTransferInstruction({source: senderAta,destination: recipientAta,authority: sender.address,amount: 250_000n});const transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) =>setTransactionMessageLifetimeUsingDurableNonce({nonce: nonceData.blockhash as Nonce,nonceAccountAddress: nonceKeypair.address,nonceAuthorityAddress: nonceData.authority},tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));
توقيع المعاملة
قم بتوقيع المعاملة. تستخدم الآن nonce الدائم بدلاً من تجزئة كتلة قياسية.
const { data: nonceData } = await fetchNonce(rpc, nonceKeypair.address);const transferInstruction = getTransferInstruction({source: senderAta,destination: recipientAta,authority: sender.address,amount: 250_000n});const transactionMessage = pipe(createTransactionMessage({ version: 0 }),(tx) => setTransactionMessageFeePayerSigner(sender, tx),(tx) =>setTransactionMessageLifetimeUsingDurableNonce({nonce: nonceData.blockhash as Nonce,nonceAccountAddress: nonceKeypair.address,nonceAuthorityAddress: nonceData.authority},tx),(tx) => appendTransactionMessageInstructions([transferInstruction], tx));const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);
تخزين أو إرسال المعاملة
بعد التوقيع، قم بترميز المعاملة للتخزين. عندما تكون جاهزًا، أرسلها إلى الشبكة.
الترميز للتخزين
قم بترميز المعاملة الموقعة إلى base64. قم بتخزين هذه القيمة في قاعدة البيانات الخاصة بك.
const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);const base64EncodedTransaction =getBase64EncodedWireTransaction(signedTransaction);// Store base64EncodedTransaction in your database
إرسال المعاملة
أرسل المعاملة الموقعة عندما تكون جاهزًا. تظل المعاملة صالحة حتى يتم تقديم nonce.
const signedTransaction =await signTransactionMessageWithSigners(transactionMessage);const base64EncodedTransaction =getBase64EncodedWireTransaction(signedTransaction);// When ready to execute (could be days later):await rpc.sendTransaction(base64EncodedTransaction, { encoding: "base64" }).send();
عرض توضيحي
// 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// =============================================================================
إبطال معاملة معلقة
كل حساب nonce blockhash يمكن استخدامه مرة واحدة فقط. لإبطال معاملة معلقة أو
إعداد حساب nonce لإعادة الاستخدام، قم بتقديمه يدويًا:
import { getAdvanceNonceAccountInstruction } from "@solana-program/system";// Submit this instruction (with a regular blockhash) to invalidate any pending transactiongetAdvanceNonceAccountInstruction({nonceAccount: nonceAddress,nonceAuthority});
هذا ينشئ قيمة nonce جديدة، مما يجعل أي معاملة موقعة بالقيمة القديمة غير صالحة بشكل دائم.
سير عمل الموافقة متعدد الأطراف
قم بإلغاء تسلسل المعاملة لإضافة توقيعات إضافية، ثم قم بالتسلسل مرة أخرى للتخزين أو التقديم:
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);
يمكن تسلسل المعاملة وتخزينها وتمريرها بين الموافقين. بمجرد جمع جميع التوقيعات المطلوبة، قم بالتقديم إلى الشبكة.
اعتبارات الإنتاج
إدارة حسابات Nonce:
- إنشاء مجموعة من حسابات nonce للتحضير المتوازي للمعاملات
- تتبع حسابات nonce "قيد الاستخدام" (التي لديها معاملات موقعة معلقة)
- تنفيذ إعادة تدوير nonce بعد إرسال المعاملات أو التخلي عنها
الأمان:
- تتحكم صلاحية nonce في إمكانية إبطال المعاملات. يُنصح بفصل صلاحية nonce عن الموقعين على المعاملات للحصول على تحكم إضافي وفصل المهام
- يمكن لأي شخص لديه بايتات المعاملة المتسلسلة إرسالها إلى الشبكة
الموارد ذات الصلة
Is this page helpful?