يصف هذا الدليل كيفية إضافة رمز SOL الأصلي لسولانا إلى بورصة العملات المشفرة الخاصة بك.
إعداد العقدة
نوصي بشدة بإعداد عقدتين على الأقل على أجهزة كمبيوتر عالية المواصفات أو مثيلات سحابية، والترقية إلى الإصدارات الأحدث فور توفرها، ومراقبة عمليات الخدمة باستخدام أداة المراقبة المدمجة.
يتيح لك هذا الإعداد:
- امتلاك بوابة تديرها بنفسك للاتصال بمجموعة سولانا الرئيسية للحصول على البيانات وإرسال معاملات السحب
- التحكم الكامل في مقدار بيانات الكتل التاريخية المحتفظ بها
- الحفاظ على توفر خدمتك حتى في حالة فشل إحدى العقد
تتطلب عقد سولانا قدرة حوسبة عالية نسبيًا للتعامل مع الكتل السريعة ومعدل المعاملات المرتفع. للاطلاع على المتطلبات التفصيلية، يرجى مراجعة توصيات الأجهزة.
لتشغيل عقدة API:
- تثبيت مجموعة أدوات سطر أوامر سولانا
- شغّل validator بالمعاملات التالية على الأقل:
solana-validator \--ledger <LEDGER_PATH> \--identity <VALIDATOR_IDENTITY_KEYPAIR> \--entrypoint <CLUSTER_ENTRYPOINT> \--expected-genesis-hash <EXPECTED_GENESIS_HASH> \--rpc-port 8899 \--no-voting \--enable-rpc-transaction-history \--limit-ledger-size \--known-validator <VALIDATOR_ADDRESS> \--only-known-rpc
خصّص --ledger لموقع تخزين السجل الذي تريده، و--rpc-port للمنفذ الذي تريد الكشف عنه.
معاملات --entrypoint و--expected-genesis-hash خاصة بالمجموعة التي تنضم إليها.
المعاملات الحالية للشبكة الرئيسية
تتيح لك معامل --limit-ledger-size تحديد عدد shreds السجل التي تحتفظ بها عقدتك على القرص. إذا لم تُدرج هذا المعامل، فسيحتفظ validator بكامل السجل حتى ينفد مساحة القرص. تحاول القيمة الافتراضية إبقاء استخدام القرص للسجل دون 500 جيجابايت. يمكن طلب مساحة أكثر أو أقل عن طريق إضافة وسيطة إلى --limit-ledger-size إذا رغبت في ذلك. تحقق من solana-validator --help للاطلاع على قيمة الحد الافتراضي المستخدمة بواسطة --limit-ledger-size. مزيد من المعلومات حول اختيار قيمة حد مخصصة متاحة هنا.
يمكن أن يحميك تحديد معامل واحد أو أكثر من --known-validator من الإقلاع من لقطة ضارة.
مزيد من المعلومات حول قيمة الإقلاع مع validators معروفين
معاملات اختيارية يُنصح بالنظر فيها:
--private-rpcيمنع نشر منفذ RPC الخاص بك للاستخدام من قِبل العقد الأخرى--rpc-bind-addressيتيح لك تحديد عنوان IP مختلف لربط منفذ RPC به
إعادة التشغيل التلقائية والمراقبة
نوصي بتهيئة كل عقدة من عقدك لإعادة التشغيل تلقائيًا عند الخروج، لضمان تفويت أقل قدر ممكن من البيانات. يُعدّ تشغيل برنامج سولانا كخدمة systemd خيارًا ممتازًا لذلك.
للمراقبة، نوفر solana-watchtower، الذي يمكنه مراقبة validator الخاص بك واكتشاف ما إذا كانت عملية solana-validator غير سليمة. يمكن تهيئته مباشرةً لإرسال تنبيهات عبر Slack أو Telegram أو Discord أو Twilio. لمزيد من التفاصيل، شغّل solana-watchtower --help.
solana-watchtower --validator-identity <YOUR VALIDATOR IDENTITY>
يمكنك العثور على مزيد من المعلومات حول أفضل ممارسات سولانا Watchtower هنا في الوثائق.
إعلانات إصدار البرامج الجديدة
نُصدر برامج جديدة بشكل متكرر (حوالي إصدار واحد في الأسبوع). أحيانًا تتضمن الإصدارات الأحدث تغييرات بروتوكول غير متوافقة، مما يستلزم تحديث البرنامج في الوقت المناسب لتجنب أخطاء في معالجة الكتل.
تُبلَّغ إعلاناتنا الرسمية للإصدارات بجميع أنواعها (العادية والأمنية) عبر قناة discord تُسمى #mb-announcement (mb اختصار لـ mainnet-beta).
كما هو الحال مع validators المشتركين في التحقق، نتوقع من أي validators تديرها البورصات أن تُحدَّث في أقرب وقت ممكن خلال يوم أو يومي عمل بعد إعلان الإصدار العادي. أما للإصدارات المتعلقة بالأمان، فقد تكون هناك حاجة لاتخاذ إجراءات أكثر إلحاحًا.
استمرارية السجل
بشكل افتراضي، ستقلع كل عقدة من عقدك من لقطة مقدَّمة من أحد validators المعروفين لديك. تعكس هذه اللقطة الحالة الراهنة للسلسلة، لكنها لا تحتوي على السجل التاريخي الكامل. إذا خرجت إحدى عقدك وأقلعت من لقطة جديدة، فقد يكون هناك فجوة في السجل على تلك العقدة. لمنع هذه المشكلة، أضف معامل --no-snapshot-fetch إلى أمر solana-validator الخاص بك لاستقبال بيانات السجل التاريخي بدلاً من لقطة.
لا تُمرّر معامل --no-snapshot-fetch عند الإقلاع الأولي لأنه لا يمكن إقلاع العقدة من كتلة النشأة مباشرةً. بدلاً من ذلك، أقلع أولاً من لقطة ثم أضف معامل --no-snapshot-fetch لعمليات إعادة التشغيل اللاحقة.
من المهم ملاحظة أن كمية السجل التاريخي المتاحة لعقدك من بقية الشبكة محدودة في أي وقت. وفي حالة تعرّض validators لفترات توقف طويلة بعد بدء التشغيل، قد لا تتمكن من اللحاق بالشبكة وستحتاج إلى تنزيل لقطة جديدة من validator معروف. وبذلك ستكون لدى validators فجوة في بيانات السجل التاريخي لا يمكن سدّها.
تقليل تعرّض منافذ validator
يتطلب validator أن تكون منافذ UDP و TCP المختلفة مفتوحة لحركة المرور الواردة من جميع validators سولانا الأخرى. على الرغم من أن هذا هو النمط الأكثر كفاءة والموصى به بشدة، إلا أنه يمكن تقييد validator ليتطلب فقط حركة المرور الواردة من validator سولانا واحد آخر.
أولاً أضف الوسيطة --restricted-repair-only-mode. سيؤدي هذا إلى تشغيل validator في وضع مقيّد حيث لن يتلقى دفعات من بقية validators، وبدلاً من ذلك سيحتاج إلى الاستعلام المستمر عن validators الأخرى للحصول على الكتل. سيُرسل validator حزم UDP فقط إلى validators الأخرى باستخدام منفذَي Gossip و_ServeR_ ("serve repair")، ولن يستقبل إلا حزم UDP على منفذَي Gossip و_Repair_ الخاصين به.
منفذ Gossip ثنائي الاتجاه ويتيح لـ validator الخاص بك البقاء على تواصل مع بقية المجموعة. يُرسل validator الخاص بك عبر ServeR لتقديم طلبات الإصلاح للحصول على كتل جديدة من بقية الشبكة، نظرًا لتعطيل Turbine الآن. سيتلقى validator الخاص بك بعد ذلك استجابات الإصلاح على منفذ Repair من validators الأخرى.
لتقييد validator بشكل أكبر ليطلب الكتل فقط من validator واحد أو أكثر، حدّد أولاً identity pubkey لذلك validator وأضف وسيطات --gossip-pull-validator PUBKEY --repair-validator PUBKEY لكل PUBKEY. سيؤدي هذا إلى جعل validator الخاص بك يستنزف موارد كل validator تضيفه، لذا يرجى القيام بذلك باعتدال وفقط بعد التشاور مع validator المستهدف.
يجب أن يتواصل validator الخاص بك الآن فقط مع validators المُدرجين صراحةً وفقط على منافذ Gossip و_Repair_ و_ServeR_.
إعداد حسابات الإيداع
لا تتطلب حسابات سولانا أي تهيئة على السلسلة؛ فهي تبدأ بالوجود فور احتوائها على بعض SOL. لإعداد حساب إيداع لبورصتك، ما عليك سوى إنشاء keypair سولانا باستخدام أي من أدوات المحفظة المتاحة لدينا.
نوصي باستخدام حساب إيداع فريد لكل مستخدم من مستخدميك.
يجب أن تكون حسابات سولانا معفاة من rent وذلك باحتوائها على ما يعادل سنتين من rent بعملة SOL. للعثور على الحد الأدنى للرصيد المعفى من rent لحسابات الإيداع الخاصة بك، استعلم عن نقطة النهاية getMinimumBalanceForRentExemption:
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id": 1,"method": "getMinimumBalanceForRentExemption","params": [0]}'
النتيجة
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }
الحسابات غير المتصلة
قد ترغب في الاحتفاظ بمفاتيح حساب تحصيل واحد أو أكثر في وضع غير متصل لمزيد من الأمان. في هذه الحالة، ستحتاج إلى نقل SOL إلى الحسابات الساخنة باستخدام الأساليب غير المتصلة الخاصة بنا.
الاستماع للإيداعات
عندما يرغب أحد المستخدمين في إيداع SOL في بورصتك، أرشده إلى إرسال تحويل إلى عنوان الإيداع المناسب.
الترحيل إلى المعاملات ذات الإصدار
عندما تبدأ شبكة Mainnet بمعالجة المعاملات ذات الإصدار، يجب على البورصات إجراء تغييرات. إذا لم تُجرَ أي تغييرات، فلن تعمل عملية اكتشاف الإيداعات بشكل صحيح لأن جلب معاملة ذات إصدار أو كتلة تحتوي على معاملات ذات إصدار سيُعيد خطأً.
-
{"maxSupportedTransactionVersion": 0}يجب إضافة معامل
maxSupportedTransactionVersionإلى طلباتgetBlockوgetTransactionلتجنب اضطراب عملية اكتشاف الإيداعات. أحدث إصدار للمعاملة هو0ويجب تحديده كقيمة أقصى لإصدار المعاملة المدعوم.
من المهم فهم أن المعاملات ذات الإصدار تسمح للمستخدمين بإنشاء معاملات تستخدم مجموعة أخرى من مفاتيح الحساب المُحمَّلة من جداول بحث العناوين على السلسلة.
-
{"encoding": "jsonParsed"}عند جلب الكتل والمعاملات، يُوصى الآن باستخدام ترميز
"jsonParsed"لأنه يتضمن جميع مفاتيح حساب المعاملة (بما فيها تلك من جداول البحث) في قائمة"accountKeys"الخاصة بالرسالة. هذا يجعل من السهل تتبع تغييرات الرصيد المفصّلة فيpreBalances/postBalancesوpreTokenBalances/postTokenBalances.إذا استُخدم ترميز
"json"بدلاً من ذلك، فقد تشير الإدخالات فيpreBalances/postBalancesوpreTokenBalances/postTokenBalancesإلى مفاتيح حساب غير موجودة في قائمة"accountKeys"وتحتاج إلى التحليل باستخدام إدخالات"loadedAddresses"في بيانات تعريف المعاملة.
الاستعلام عن الكتل
لتتبع جميع حسابات الإيداع في بورصتك، استعلم عن كل كتلة مؤكدة وافحصها بحثًا عن العناوين ذات الاهتمام، باستخدام خدمة JSON-RPC الخاصة بعقدة سولانا API لديك.
- لتحديد الكتل المتاحة، أرسل طلب
getBlocks، مُمررًا آخر كتلة قمت بمعالجتها بالفعل كمعامل start-slot:
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id": 1,"method": "getBlocks","params": [160017005, 160017015]}'
النتيجة
{"jsonrpc": "2.0","result": [160017005, 160017006, 160017007, 160017012, 160017013, 160017014, 160017015],"id": 1}
لا ينتج كل slot كتلةً، لذا قد تكون هناك فجوات في تسلسل الأعداد الصحيحة.
- لكل كتلة، اطلب محتوياتها من خلال طلب
getBlock:
نصائح جلب الكتل
{"rewards": false}
بشكل افتراضي، ستُعيد الكتل المجلوبة معلومات حول رسوم validator على كل كتلة ومكافآت التحصيص عند حدود epoch. إذا لم تكن بحاجة إلى هذه المعلومات، فعطّلها باستخدام معامل "rewards".
{"transactionDetails": "accounts"}
بشكل افتراضي، ستُعيد الكتل المجلوبة كميةً كبيرةً من معلومات المعاملات والبيانات الوصفية غير الضرورية لتتبع أرصدة الحسابات. اضبط معامل "transactionDetails" لتسريع عملية جلب الكتل.
curl https://api.devnet.solana.com -X POST -H 'Content-Type: application/json' -d '{"jsonrpc": "2.0","id": 1,"method": "getBlock","params": [166974442,{"encoding": "jsonParsed","maxSupportedTransactionVersion": 0,"transactionDetails": "accounts","rewards": false}]}'
النتيجة
{"jsonrpc": "2.0","result": {"blockHeight": 157201607,"blockTime": 1665070281,"blockhash": "HKhao674uvFc4wMK1Cm3UyuuGbKExdgPFjXQ5xtvsG3o","parentSlot": 166974441,"previousBlockhash": "98CNLU4rsYa2HDUyp7PubU4DhwYJJhSX9v6pvE7SWsAo","transactions": [... (omit){"meta": {"err": null,"fee": 5000,"postBalances": [1110663066,1,1040000000],"postTokenBalances": [],"preBalances": [1120668066,1,1030000000],"preTokenBalances": [],"status": {"Ok": null}},"transaction": {"accountKeys": [{"pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde","signer": true,"source": "transaction","writable": true},{"pubkey": "11111111111111111111111111111111","signer": false,"source": "transaction","writable": false},{"pubkey": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o","signer": false,"source": "lookupTable","writable": true}],"signatures": ["2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM"]},"version": 0},... (omit)]},"id": 1}
تتيح لك حقلا preBalances وpostBalances تتبع التغييرات في الرصيد لكل حساب دون الحاجة إلى تحليل المعاملة بأكملها. وتُدرج هذه الحقول الأرصدة الابتدائية والنهائية لكل حساب بوحدة lamport، مفهرسةً وفق قائمة accountKeys. فمثلاً، إذا كان عنوان الإيداع المعني هو G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o، فإن هذه المعاملة تمثل تحويلاً بقيمة 1040000000 - 1030000000 = 10,000,000 lamport = 0.01 SOL
إذا كنت بحاجة إلى مزيد من المعلومات حول نوع المعاملة أو تفاصيل أخرى، يمكنك طلب الكتلة من RPC بصيغة ثنائية، وتحليلها باستخدام Rust SDK أو Javascript SDK.
سجل العناوين
يمكنك أيضاً الاستعلام عن سجل المعاملات لعنوان محدد. غير أن هذه الطريقة عموماً ليست مجدية لتتبع جميع عناوين الإيداع عبر كافة slot، إلا أنها قد تكون مفيدة لفحص عدد قليل من الحسابات خلال فترة زمنية محددة.
- أرسل طلب
getSignaturesForAddressإلى عقدة الـ API:
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id": 1,"method": "getSignaturesForAddress","params": ["3M2b3tLji7rvscqrLAHMukYxDK2nB96Q9hwfV6QkdzBN",{"limit": 3}]}'
النتيجة
{"jsonrpc": "2.0","result": [{"blockTime": 1662064640,"confirmationStatus": "finalized","err": null,"memo": null,"signature": "3EDRvnD5TbbMS2mCusop6oyHLD8CgnjncaYQd5RXpgnjYUXRCYwiNPmXb6ZG5KdTK4zAaygEhfdLoP7TDzwKBVQp","slot": 148697216},{"blockTime": 1662064434,"confirmationStatus": "finalized","err": null,"memo": null,"signature": "4rPQ5wthgSP1kLdLqcRgQnkYkPAZqjv5vm59LijrQDSKuL2HLmZHoHjdSLDXXWFwWdaKXUuryRBGwEvSxn3TQckY","slot": 148696843},{"blockTime": 1662064341,"confirmationStatus": "finalized","err": null,"memo": null,"signature": "36Q383JMiqiobuPV9qBqy41xjMsVnQBm9rdZSdpbrLTGhSQDTGZJnocM4TQTVfUGfV2vEX9ZB3sex6wUBUWzjEvs","slot": 148696677}],"id": 1}
- للحصول على تفاصيل كل معاملة بناءً على التواقيع المُعادة، أرسل طلب
getTransaction:
curl https://api.devnet.solana.com -X POST -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","id":1,"method":"getTransaction","params":["2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM",{"encoding":"jsonParsed","maxSupportedTransactionVersion":0}]}'
النتيجة
{"jsonrpc": "2.0","result": {"blockTime": 1665070281,"meta": {"err": null,"fee": 5000,"innerInstructions": [],"logMessages": ["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances": [1110663066, 1, 1040000000],"postTokenBalances": [],"preBalances": [1120668066, 1, 1030000000],"preTokenBalances": [],"rewards": [],"status": {"Ok": null}},"slot": 166974442,"transaction": {"message": {"accountKeys": [{"pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde","signer": true,"source": "transaction","writable": true},{"pubkey": "11111111111111111111111111111111","signer": false,"source": "transaction","writable": false},{"pubkey": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o","signer": false,"source": "lookupTable","writable": true}],"addressTableLookups": [{"accountKey": "4syr5pBaboZy4cZyF6sys82uGD7jEvoAP2ZMaoich4fZ","readonlyIndexes": [],"writableIndexes": [3]}],"instructions": [{"parsed": {"info": {"destination": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o","lamports": 10000000,"source": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde"},"type": "transfer"},"program": "system","programId": "11111111111111111111111111111111"}],"recentBlockhash": "BhhivDNgoy4L5tLtHb1s3TP19uUXqKiy4FfUR34d93eT"},"signatures": ["2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM"]},"version": 0},"id": 1}
إرسال عمليات السحب
لتلبية طلب المستخدم بسحب SOL، يجب عليك إنشاء معاملة تحويل على سولانا وإرسالها إلى عقدة الـ API لتوجيهها إلى الكلستر الخاص بك.
متزامن
يتيح لك إرسال تحويل متزامن إلى كلستر سولانا التحقق بسهولة من نجاح التحويل واكتمال تأكيده من قِبل الكلستر.
تقدم أداة سطر الأوامر الخاصة بسولانا أمراً بسيطاً هو solana transfer لإنشاء معاملات التحويل وإرسالها وتأكيدها. بشكل افتراضي، ستنتظر هذه الطريقة وتتابع التقدم عبر stderr حتى يتم تأكيد المعاملة نهائياً من قِبل الكلستر. وفي حال فشل المعاملة، سيتم الإبلاغ عن أي أخطاء تتعلق بها.
solana transfer <USER_ADDRESS> <AMOUNT> --allow-unfunded-recipient --keypair <KEYPAIR> --url http://localhost:8899
يوفر Solana Javascript SDK نهجاً مماثلاً لبيئة JS. استخدم SystemProgram لبناء معاملة تحويل، وأرسلها باستخدام طريقة sendAndConfirmTransaction.
غير متزامن
لمزيد من المرونة، يمكنك إرسال تحويلات السحب بشكل غير متزامن. في هذه الحالات، تقع على عاتقك مسؤولية التحقق من نجاح المعاملة وتأكيدها النهائي من قِبل الكلستر.
ملاحظة: تحتوي كل معاملة على blockhash حديث للإشارة إلى صلاحيتها. ومن الضروري انتظار انتهاء صلاحية هذا الـ blockhash قبل إعادة محاولة تحويل سحب لم يبدُ أنه تم تأكيده أو اكتمل تأكيده النهائي من قِبل الكلستر. وإلا فقد تتعرض لخطر الإنفاق المزدوج. اطلع على مزيد من المعلومات حول انتهاء صلاحية الـ blockhash أدناه.
أولاً، احصل على blockhash حديث باستخدام نقطة النهاية
getFees أو أمر CLI:
solana fees --url http://localhost:8899
في أداة سطر الأوامر، مرر الوسيطة --no-wait لإرسال التحويل بشكل غير متزامن، وأضف الـ blockhash الحديث باستخدام الوسيطة --blockhash:
solana transfer <USER_ADDRESS> <AMOUNT> --no-wait --allow-unfunded-recipient --blockhash <RECENT_BLOCKHASH> --keypair <KEYPAIR> --url http://localhost:8899
يمكنك أيضاً بناء المعاملة وتوقيعها وتسلسلها يدوياً، ثم إرسالها إلى الكلستر باستخدام نقطة النهاية JSON-RPC
sendTransaction.
تأكيدات المعاملات والنهائية
احصل على حالة مجموعة من المعاملات باستخدام نقطة النهاية JSON-RPC
getSignatureStatuses.
يُشير حقل confirmations إلى عدد
الكتل المؤكدة التي مرت
منذ معالجة المعاملة. إذا كانت confirmations: null، فهذا يعني أن المعاملة
مكتملة التأكيد النهائي.
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"getSignatureStatuses","params":[["4cdd1oX7cfVALfr26tP52BZ6cSzrgnNGtYD7BFhm6FFeZV5sPTnRvg6NRn8yC6DbEikXcrNChBM5vVJnTgKhGhVu","5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]}'
النتيجة
{"jsonrpc": "2.0","result": {"context": {"slot": 82},"value": [{"slot": 72,"confirmations": 10,"err": null,"status": {"Ok": null}},{"slot": 48,"confirmations": null,"err": null,"status": {"Ok": null}}]},"id": 1}
انتهاء صلاحية الـ Blockhash
يمكنك التحقق من صلاحية blockhash معين عن طريق إرسال طلب
getFeeCalculatorForBlockhash
مع الـ blockhash كمعامل. إذا كانت قيمة الاستجابة null، فهذا يعني أن الـ blockhash قد انتهت صلاحيته، وأن معاملة السحب التي تستخدمه لن تنجح أبداً.
التحقق من صحة عناوين الحسابات التي يوفرها المستخدمون لعمليات السحب
نظراً لكون عمليات السحب غير قابلة للعكس، قد يكون من الممارسات الجيدة التحقق من صحة عنوان الحساب الذي يوفره المستخدم قبل تفويض عملية السحب، وذلك لتجنب الخسارة غير المقصودة لأموال المستخدم.
التحقق الأساسي
عناوين سولانا عبارة عن مصفوفة من 32 بايت، مُشفَّرة بأبجدية bitcoin base58. وينتج عن ذلك سلسلة نصية ASCII تطابق التعبير النمطي التالي:
[1-9A-HJ-NP-Za-km-z]{32,44}
هذا الفحص وحده غير كافٍ لأن عناوين سولانا لا تحتوي على مجموع تدقيق (checksum)، مما يعني عدم إمكانية اكتشاف الأخطاء المطبعية. للتحقق الإضافي من إدخال المستخدم، يمكن فك ترميز السلسلة والتأكد من أن طول مصفوفة البايت الناتجة يساوي 32. ومع ذلك، ثمة بعض العناوين التي يمكن فك ترميزها إلى 32 بايت على الرغم من وجود أخطاء مطبعية كحذف حرف واحد أو عكس ترتيب الأحرف أو تجاهل حالة الأحرف.
التحقق المتقدم
نظراً للهشاشة تجاه الأخطاء المطبعية الموضحة أعلاه، يُنصح بالاستعلام عن رصيد عناوين السحب المرشحة وتنبيه المستخدم لتأكيد نيته في حال اكتشاف رصيد غير صفري.
فحص صحة pubkey لمنحنى ed25519
عنوان الحساب العادي في سولانا هو سلسلة نصية مُشفَّرة بـ Base58 لمفتاح عام ed25519 بطول 256 بت. ليست جميع أنماط البتات مفاتيح عامة صالحة لمنحنى ed25519، لذا يمكن التحقق من أن عناوين الحسابات التي يوفرها المستخدمون هي على الأقل مفاتيح عامة صالحة لـ ed25519.
Java
إليك مثالاً بلغة Java للتحقق من صحة عنوان يوفره المستخدم بوصفه مفتاحاً عاماً صالحاً لـ ed25519:
يفترض نموذج الكود التالي أنك تستخدم Maven.
pom.xml:
<repositories>...<repository><id>spring</id><url>https://repo.spring.io/libs-release/</url></repository></repositories>...<dependencies>...<dependency><groupId>io.github.novacrypto</groupId><artifactId>Base58</artifactId><version>0.1.3</version></dependency><dependency><groupId>cafe.cryptography</groupId><artifactId>curve25519-elisabeth</artifactId><version>0.1.0</version></dependency><dependencies>
import io.github.novacrypto.base58.Base58;import cafe.cryptography.curve25519.CompressedEdwardsY;public class PubkeyValidator{public static boolean verifyPubkey(String userProvidedPubkey){try {return _verifyPubkeyInternal(userProvidedPubkey);} catch (Exception e) {return false;}}public static boolean _verifyPubkeyInternal(String maybePubkey) throws Exception{byte[] bytes = Base58.base58Decode(maybePubkey);return !(new CompressedEdwardsY(bytes)).decompress().isSmallOrder();}}
الحد الأدنى لمبالغ الإيداع والسحب
يجب أن يكون كل إيداع وسحب من SOL أكبر من أو يساوي الحد الأدنى للرصيد المعفى من الإيجار للحساب على عنوان المحفظة (حساب SOL أساسي لا يحتوي على بيانات)، وهو حالياً: 0.000890880 SOL
بالمثل، يجب أن يحتوي كل حساب إيداع على هذا الحد الأدنى من الرصيد على الأقل.
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id": 1,"method": "getMinimumBalanceForRentExemption","params": [0]}'
النتيجة
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }
رسوم الأولوية ووحدات الحساب
في فترات الطلب المرتفع، قد تنتهي صلاحية المعاملة قبل أن يقوم validator بتضمينها في كتلته، نظراً لاختياره معاملات أخرى ذات قيمة اقتصادية أعلى. قد تتأخر المعاملات الصالحة على سولانا أو تُسقط إذا لم يتم تطبيق رسوم الأولوية بشكل صحيح.
رسوم الأولوية هي رسوم إضافية يمكن إضافتها فوق رسوم المعاملة الأساسية لضمان تضمين المعاملة داخل الكتل في مثل هذه المواقف والمساعدة على ضمان وصولها.
تُضاف رسوم الأولوية إلى المعاملة عن طريق إضافة تعليمة Compute Budget خاصة تحدد رسوم الأولوية المطلوب دفعها.
ملاحظة مهمة
قد يؤدي الإخفاق في تطبيق هذه التعليمات إلى انقطاعات في الشبكة وإسقاط المعاملات. يُنصح بشدة بأن تستخدم كل بورصة تدعم سولانا رسوم الأولوية لتجنب الانقطاعات.
ما هي رسوم الأولوية؟
تُسعَّر رسوم الأولوية بالمايكرو-lamport لكل وحدة حساب (Compute Unit) (أي مبالغ صغيرة من SOL) تُضاف في بداية المعاملات لجعلها مجدية اقتصادياً لعقد validator لتضمينها في الكتل على الشبكة.
ما المبلغ المناسب لرسوم الأولوية؟
ينبغي أن تتضمن طريقة تحديد رسوم الأولوية الاستعلام عن رسوم الأولوية الأخيرة لتحديد رسوم من المرجح أن تكون مقبولة للشبكة. باستخدام طريقة RPC
getRecentPrioritizationFees،
يمكنك الاستعلام عن رسوم الأولوية المطلوبة لإدراج معاملة في كتلة حديثة.
ستتفاوت استراتيجية التسعير لرسوم الأولوية بناءً على حالة الاستخدام. لا توجد طريقة معيارية موحدة للقيام بذلك. إحدى الاستراتيجيات لتحديد رسوم الأولوية قد تتمثل في حساب معدل نجاح معاملاتك ثم رفع رسوم الأولوية استناداً إلى استعلام واجهة برمجة التطبيقات الخاصة برسوم المعاملات الأخيرة والتعديل وفقاً لذلك. سيكون تسعير رسوم الأولوية ديناميكياً بناءً على نشاط الشبكة والعروض المقدمة من المشاركين الآخرين، ولن يكون معروفاً إلا بعد وقوعه.
أحد التحديات عند استخدام استدعاء API الخاص بـ getRecentPrioritizationFees هو أنه قد يُعيد فقط أدنى رسوم لكل كتلة. وكثيراً ما تكون هذه القيمة صفراً، وهي ليست تقريباً مفيداً بالكامل لتحديد رسوم الأولوية المناسبة لتجنب الرفض من قِبل عقد validator.
يأخذ API الخاص بـ getRecentPrioritizationFees pubkeys الحسابات كمعاملات، ثم يُعيد أعلى الحد الأدنى لرسوم الأولوية لهذه الحسابات. وعند عدم تحديد أي حساب، سيُعيد الـ API أدنى رسوم للإدراج في الكتلة، وهي عادةً صفر (ما لم تكن الكتلة ممتلئة).
ينبغي على البورصات والتطبيقات الاستعلام من نقطة نهاية RPC بالحسابات التي ستقوم المعاملة بقفلها للكتابة. ستُعيد نقطة النهاية max(account_1_min_fee, account_2_min_fee, ... account_n_min_fee)، والتي ينبغي أن تكون نقطة الانطلاق للمستخدم لتحديد رسوم الأولوية لتلك المعاملة.
ثمة مناهج مختلفة لتحديد رسوم الأولوية، وتتوفر بعض واجهات برمجة التطبيقات من جهات خارجية لتحديد أفضل رسوم يمكن تطبيقها. نظراً للطبيعة الديناميكية للشبكة، لن تكون هناك طريقة "مثالية" لتسعير رسوم الأولوية، ويجب إجراء تحليل دقيق قبل اختيار المسار المناسب.
كيفية تطبيق رسوم الأولوية
تتضمن إضافة رسوم الأولوية إلى معاملة ما إضافة تعليمتين من تعليمات Compute Budget في بداية المعاملة المعطاة:
- إحداهما لتحديد سعر وحدة الحساب، و
- والأخرى لتحديد حد وحدة الحساب
يمكنك هنا أيضاً الاطلاع على دليل المطور المفصّل حول كيفية استخدام رسوم الأولوية الذي يتضمن مزيداً من المعلومات حول تطبيق رسوم الأولوية.
أنشئ تعليمة setComputeUnitPrice لإضافة رسوم أولوية فوق رسوم المعاملة الأساسية (5,000 Lamports).
// import { ComputeBudgetProgram } from "@solana/web3.js"ComputeBudgetProgram.setComputeUnitPrice({ microLamports: number });
ستُضرب القيمة المُعطاة بالمايكرو-lamport في ميزانية وحدة الحساب (CU) لتحديد رسوم الأولوية بالـ Lamports. فمثلاً، إذا كانت ميزانية CU لديك 1M وحدة حساب وأضفت 1 microLamport/CU، ستكون رسوم الأولوية 1 lamport (1M * 0.000001). وبذلك ستكون الرسوم الإجمالية 5001 lamport.
لتعيين ميزانية وحدة حساب جديدة للمعاملة، أنشئ تعليمة
setComputeUnitLimit
// import { ComputeBudgetProgram } from "@solana/web3.js"ComputeBudgetProgram.setComputeUnitLimit({ units: number });
ستحل قيمة units المُعطاة محل قيمة ميزانية الحساب الافتراضية لبيئة تشغيل سولانا.
تعيين أدنى قدر من وحدات الحساب المطلوبة للمعاملة
ينبغي للمعاملات طلب الحد الأدنى من وحدات الحساب (CU) المطلوبة للتنفيذ لتعظيم الإنتاجية وتقليل إجمالي الرسوم.
يمكنك معرفة وحدات الحساب التي تستهلكها المعاملة عن طريق إرسالها على كلستر سولانا مختلف، مثل devnet. فعلى سبيل المثال، يستهلك تحويل رمز بسيط 300 وحدة حساب.
// import { ... } from "@solana/web3.js"const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({// note: set this to be the lowest actual CU consumed by the transactionunits: 300});const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({microLamports: 1});const transaction = new Transaction().add(modifyComputeUnits).add(addPriorityFee).add(SystemProgram.transfer({fromPubkey: payer.publicKey,toPubkey: toAccount,lamports: 10000000}));
رسوم الأولوية والـ Nonces الدائمة
إذا كان إعدادك يستخدم معاملات Durable Nonce، فمن المهم تطبيق رسوم الأولوية بشكل صحيح بالتزامن مع Durable Transaction Nonces لضمان نجاح المعاملات. سيؤدي الإخفاق في ذلك إلى عدم التعرف على معاملات Durable Nonce المقصودة على هذا النحو.
إذا كنت تستخدم Durable Transaction Nonces، فيجب تحديد تعليمة AdvanceNonceAccount أولاً في قائمة التعليمات، حتى عند استخدام تعليمات ميزانية الحوسبة لتحديد رسوم الأولوية.
يمكنك العثور على مثال كود محدد يستخدم durable nonces ورسوم الأولوية معاً في دليل المطور هذا.
دعم معيار SPL Token
SPL Token هو معيار إنشاء وتبادل الرموز المُغلَّفة/الاصطناعية على بلوكتشين سولانا.
سير عمل SPL Token مشابه لسير عمل رموز SOL الأصلية، غير أن ثمة بعض الاختلافات التي سيتم مناقشتها في هذا القسم.
Token Mints
يتم الإعلان عن كل نوع من أنواع SPL Token عبر إنشاء حساب mint. يخزن هذا الحساب البيانات الوصفية التي تصف خصائص الرمز مثل العرض وعدد المنازل العشرية والصلاحيات المختلفة التي تتحكم في mint. يشير كل token account إلى الـ mint المرتبط به ولا يمكنه التفاعل إلا مع SPL Tokens من ذلك النوع.
تثبيت أداة spl-token في سطر الأوامر
يتم الاستعلام عن token accounts الخاصة بـ SPL Token وتعديلها باستخدام أداة سطر الأوامر spl-token. تعتمد الأمثلة المقدمة في هذا القسم على تثبيتها على النظام المحلي.
يتم توزيع spl-token من Rust
crates.io عبر أداة سطر أوامر Rust cargo.
يمكن تثبيت أحدث إصدار من cargo باستخدام أمر مختصر لمنصتك على rustup.rs. بمجرد تثبيت cargo، يمكن الحصول على spl-token بالأمر التالي:
cargo install spl-token-cli
يمكنك بعد ذلك التحقق من الإصدار المثبت للتأكد
spl-token --version
والذي يجب أن ينتج شيئاً مشابهاً لـ
spl-token-cli 2.0.1
إنشاء الحساب
تحمل token accounts الخاصة بـ SPL Token متطلبات إضافية لا تتطلبها حسابات System Program الأصلية:
- يجب إنشاء token accounts الخاصة بـ SPL Token قبل إيداع أي قدر من الرموز. يمكن إنشاء token accounts بشكل صريح باستخدام الأمر
spl-token create-account، أو بشكل ضمني من خلال الأمرspl-token transfer --fund-recipient .... - يجب أن تظل token accounts الخاصة بـ SPL Token معفاة من الإيجار طوال فترة وجودها، وبالتالي تستلزم إيداع كمية صغيرة من رموز SOL الأصلية عند إنشاء الحساب. بالنسبة لـ token accounts الخاصة بـ SPL Token، يبلغ هذا المبلغ 0.00203928 SOL (2,039,280 lamports).
سطر الأوامر
لإنشاء token account خاص بـ SPL Token بالخصائص التالية:
- مرتبط بالـ mint المحدد
- مملوك لـ keypair الخاص بالحساب الممول
spl-token create-account <TOKEN_MINT_ADDRESS>
مثال
spl-token create-account AkUFCWTXb3w9nY2n6SFJvBV6VwvFUCe4KBMCcgLsa2ir
ينتج مخرجاً مشابهاً لـ:
Creating account 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVSignature: 4JsqZEPra2eDTHtHpB4FMWSfk3UgcCVmkKkP7zESZeMrKmFFkDkNd91pKP3vPVVZZPiu5XxyJwS73Vi5WsZL88D7
أو لإنشاء token account خاص بـ SPL Token بـ keypair محدد:
solana-keygen new -o token-account.jsonspl-token create-account AkUFCWTXb3w9nY2n6SFJvBV6VwvFUCe4KBMCcgLsa2ir token-account.json
ينتج مخرجاً مشابهاً لـ:
Creating account 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVSignature: 4JsqZEPra2eDTHtHpB4FMWSfk3UgcCVmkKkP7zESZeMrKmFFkDkNd91pKP3vPVVZZPiu5XxyJwS73Vi5WsZL88D7
التحقق من رصيد الحساب
سطر الأوامر
spl-token balance <TOKEN_ACCOUNT_ADDRESS>
مثال
solana balance 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV
ينتج مخرجاً مشابهاً لـ:
0
تحويلات الرموز
الحساب المصدر للتحويل هو token account الفعلي الذي يحتوي على المبلغ.
أما عنوان المستلم فيمكن أن يكون حساب محفظة عادياً. إذا لم يكن associated token account للـ mint المحدد موجوداً بعد لتلك المحفظة، فسينشئه التحويل شريطة تقديم الوسيط --fund-recipient.
سطر الأوامر
spl-token transfer <SENDER_ACCOUNT_ADDRESS> <AMOUNT> <RECIPIENT_WALLET_ADDRESS> --fund-recipient
مثال
spl-token transfer 6B199xxzw3PkAm25hGJpjj3Wj3WNYNHzDAnt1tEqg5BN 1
ينتج مخرجاً مشابهاً لـ:
6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVTransfer 1 tokensSender: 6B199xxzw3PkAm25hGJpjj3Wj3WNYNHzDAnt1tEqg5BNRecipient: 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVSignature: 3R6tsog17QM8KfzbcbdP4aoMfwgo6hBggJDVy7dZPVmH2xbCWjEj31JKD53NzMrf25ChFjY7Uv2dfCDq4mGFFyAj
الإيداع
نظراً لأن كل زوج (محفظة، mint) يتطلب حساباً منفصلاً على السلسلة، يُنصح باشتقاق عناوين هذه الحسابات من محافظ إيداع SOL باستخدام نظام
Associated Token Account
(ATA)، وبأن يُقبل فقط الإيداع من عناوين ATA.
يجب أن تتبع مراقبة معاملات الإيداع طريقة استطلاع الكتل الموضحة أعلاه. يجب فحص كل كتلة جديدة بحثاً عن المعاملات الناجحة التي تتضمن عناوين token accounts الخاصة بالمستخدم والبورصة.
يجب استخدام حقلَي preTokenBalances وpostTokenBalances من بيانات المعاملة الوصفية لتحديد التغيير الفعلي في الرصيد. تتضمن هذه الحقول mint الرمز، ومالك token account (عنوان المحفظة)، وأرصدة token accounts قبل المعاملة وبعدها.
إذا تم إنشاء token account كجزء من معاملة (كما هو الحال عند استلام الرموز لأول مرة)، فلن يظهر في مصفوفة preTokenBalances لأنه لم يكن موجوداً قبل المعاملة. في هذا السيناريو، يجب اعتبار الرصيد الأولي صفراً عند حساب مبالغ الإيداع. سيظهر الحساب المُنشأ حديثاً فقط في مصفوفة postTokenBalances برصيده النهائي بعد اكتمال المعاملة.
مثال 1: تحويل رمز واحد
يوضح تفصيل المعاملة أدناه مثالاً على معاملة تتضمن تعليمة تحويل رمز واحد.
تُحوِّل المعاملة 100 وحدة أساسية من الرمز (غير مُعدَّلة لعدد المنازل العشرية للـ mint) وتتضمن الحسابات التالية:
- المُرسِل (المالك):
4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw - token account الخاص بالمُرسِل:
6zhjktfYBRUp7fgXLoWU7GFFrCuZa9iQkYTQzDzuvsAS - token account الخاص بالمستلم:
G5nNekUhhWFqJAiCMpKHootZ5Bfa7MXwuQ5vvKcvuxKM - معرّف Token Extension Program:
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
لاحظ أن حساب المستلم (المالك) وmint account غير مطلوبَين في تعليمة تحويل الرمز. للرجوع إليهما، فهما مدرجان هنا إذ تُضمَّن عناوينهما في بيانات المعاملة الوصفية المُحلَّلة.
- المستلم (المالك):
8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n - الـ Mint:
Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2
{"blockTime": "1741240211","meta": {"computeUnitsConsumed": "1551","err": null,"fee": "5000","innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]","Program log: Instruction: Transfer","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1551 of 200000 compute units","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success"],"postBalances": ["994375240", "2074080", "2074080", "1141440"],"postTokenBalances": [{"accountIndex": 1,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "0","decimals": 2,"uiAmount": null,"uiAmountString": "0"}},{"accountIndex": 2,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}}],"preBalances": ["994380240", "2074080", "2074080", "1141440"],"preTokenBalances": [{"accountIndex": 1,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}},{"accountIndex": 2,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "0","decimals": 2,"uiAmount": null,"uiAmountString": "0"}}],"rewards": [],"status": {"Ok": null}},"slot": "3916","transaction": {"message": {"accountKeys": ["4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw","6zhjktfYBRUp7fgXLoWU7GFFrCuZa9iQkYTQzDzuvsAS","G5nNekUhhWFqJAiCMpKHootZ5Bfa7MXwuQ5vvKcvuxKM","TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"],"addressTableLookups": [],"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"instructions": [{"accounts": [1, 2, 0],"data": "3WBgs5fm8oDy","programIdIndex": 3,"stackHeight": null}],"recentBlockhash": "8soh8j2dkEniZW6Jpx9cJaWtnvrGoGUqpbaUVwUkX5R3"},"signatures": ["3vr6Gj3GnBQmsZW1TtBJ3hvfFMi3h9BxLs2oaZkV41LRWeGWPVmeo16JTN8MdP3ypU5VgWAziYUjybhyZoisryQ6"]},"version": "0"}
مثال 2: إنشاء token account وتحويل
يوضح تفصيل المعاملة أدناه مثالاً على معاملة يتم فيها إنشاء token account الخاص بالمستلم في نفس المعاملة التي يتم فيها تحويل الرمز.
لاحظ أن مصفوفة preTokenBalances لا تتضمن token account الخاص بالمستلم لأنه لم يكن موجوداً قبل المعاملة. يظهر token account الخاص بالمستلم فقط في مصفوفة postTokenBalances برصيده النهائي بعد اكتمال المعاملة.
{"blockTime": "1740541705","meta": {"computeUnitsConsumed": "17416","err": null,"fee": "5000","innerInstructions": [{"index": 0,"instructions": [{"accounts": [4],"data": "84eT","programIdIndex": 7,"stackHeight": 2},{"accounts": [0, 1],"data": "11119ExAoTptm6xKUTUcw2V69MKmyEdDmRins3j3bK43o9nHeiYUtSiaT9pc292PhNQvxj","programIdIndex": 3,"stackHeight": 2},{"accounts": [1],"data": "P","programIdIndex": 7,"stackHeight": 2},{"accounts": [1, 4],"data": "6b8ZSccu4ezujyhGG8KNmg75iCWbQRyjxeSfi38u8ED8N","programIdIndex": 7,"stackHeight": 2}]}],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [1]","Program log: Create","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]","Program log: Instruction: GetAccountDataSize","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 928 of 394613 compute units","Program return: TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb qgAAAAAAAAA=","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program log: Initialize the associated token account","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]","Program log: Instruction: InitializeImmutableOwner","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 487 of 388755 compute units","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [2]","Program log: Instruction: InitializeAccount3","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1440 of 385879 compute units","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 15865 of 400000 compute units","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]","Program log: Instruction: Transfer","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1551 of 384135 compute units","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success"],"postBalances": ["994375240","2074080","2074080","1","1461600","731913600","0","1141440"],"postTokenBalances": [{"accountIndex": 1,"mint": "3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK","owner": "EzrmgRNGN9duiDAk3ABSC8eKhd1b2EUFwXVrYZDJw4hQ","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}},{"accountIndex": 2,"mint": "3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK","owner": "CbJNxBnU9ZWnQq12aVHhbze9nubbDVDV5rYVEqz9qFaS","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "0","decimals": 2,"uiAmount": null,"uiAmountString": "0"}}],"preBalances": ["996454320","0","2074080","1","1461600","731913600","0","1141440"],"preTokenBalances": [{"accountIndex": 2,"mint": "3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK","owner": "CbJNxBnU9ZWnQq12aVHhbze9nubbDVDV5rYVEqz9qFaS","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}}],"rewards": [],"status": {"Ok": null}},"slot": "81051","transaction": {"message": {"accountKeys": ["CbJNxBnU9ZWnQq12aVHhbze9nubbDVDV5rYVEqz9qFaS","571u96hRRmxbRCTmp5oqC5WpJfvZhPaSXEbihLVCR5wQ","8y8KjtZN9tyGeAeKwr8doSpbBVVgfsZMtMjCGUDH7mmU","11111111111111111111111111111111","3RPRXBsdwyHhs2UnTWXoHp6Frwv4eWEbA55qCzbs9nxK","ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL","EzrmgRNGN9duiDAk3ABSC8eKhd1b2EUFwXVrYZDJw4hQ","TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"],"addressTableLookups": [],"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 5,"numRequiredSignatures": 1},"instructions": [{"accounts": [0, 1, 6, 4, 3, 7],"data": "1","programIdIndex": 5,"stackHeight": null},{"accounts": [2, 1, 0],"data": "3WBgs5fm8oDy","programIdIndex": 7,"stackHeight": null}],"recentBlockhash": "77QC38Q2hKFYZzUXk8JWmsAqGNKhw4k2Lm2XVUme9uqP"},"signatures": ["4kuGhMeZxBHgEtej4Uv4n2arhe3jqT2GdTDPFri4JLFXYgcAtbeeXdBdzvG98HENe1tZSZqyFkm3SEvB6CfCMaM9"]},"version": "0"}
مثال 3: تغيير مالك token account
يوضح تفصيل المعاملة أدناه مثالاً على معاملة يتم فيها تغيير حقل owner الخاص بـ token account.
يُثبَّط بشدة قبول الإيداعات بالسماح للمودِعين بنقل ملكية token accounts (عبر تغيير حقل owner).
إذا اخترت دعم هذا كأسلوب إيداع، فيجب عليك التحقق من أن حقل owner الجديد في postTokenBalances يتطابق مع عنوان محفظة تتحكم فيه بورصتك وتمتلك مفتاحها الخاص.
إذا قام مودِع بتغيير owner لـ token account إلى عنوان ليس محفظة (كعنوان token account آخر)، فقد تصبح الأموال غير قابلة للوصول بشكل دائم.
{"blockTime": "1740598556","meta": {"computeUnitsConsumed": "1167","err": null,"fee": "5000","innerInstructions": [],"loadedAddresses": {"readonly": [],"writable": []},"logMessages": ["Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb invoke [1]","Program log: Instruction: SetAuthority","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb consumed 1167 of 200000 compute units","Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb success"],"postBalances": ["996479120", "2039280", "1141440"],"postTokenBalances": [{"accountIndex": 1,"mint": "ELRBdV4gcuxqYb6jHkV4ySJ7dwYx9344cgFNzUSww1ra","owner": "A9FK8XxT2Hfefz8H3vQJHLwvbibGQJWErBsqMumgUYeP","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}}],"preBalances": ["996484120", "2039280", "1141440"],"preTokenBalances": [{"accountIndex": 1,"mint": "ELRBdV4gcuxqYb6jHkV4ySJ7dwYx9344cgFNzUSww1ra","owner": "DLvpDgEABKfEaRDz5Qh9tSrJhuZzsiiZMcYXmvdek1zV","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}}],"rewards": [],"status": {"Ok": null}},"slot": "137902","transaction": {"message": {"accountKeys": ["DLvpDgEABKfEaRDz5Qh9tSrJhuZzsiiZMcYXmvdek1zV","5Qj4uNGuAEBdryPg8k2UTewpnNfYAc9Ux9fCcDrNAjGs","TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb"],"addressTableLookups": [],"header": {"numReadonlySignedAccounts": 0,"numReadonlyUnsignedAccounts": 1,"numRequiredSignatures": 1},"instructions": [{"accounts": [1, 0],"data": "bmb6sys4wqZErNeiV7hrM4vQVHF8AVBhi3XeR5TgbQM68MH","programIdIndex": 2,"stackHeight": null}],"recentBlockhash": "CvTdX9MSYkqFMkALUHeGPMQN5yBeUdJptRdce2qMEkPr"},"signatures": ["3rHUaKMh4KDDfdaATAL4J5WDEV7oFm7ykkNaMf1Eo5EwvDUjLE6dWsbxNDmyENrhb2w5gE4KqRxZ3ZwQxuM18SVR"]},"version": "0"}
حساب إيداعات الرموز
لتتبع إيداعات الرموز بدقة، يجب مقارنة حقلَي preTokenBalances وpostTokenBalance في بيانات المعاملة الوصفية. تُظهر هذه الحقول أرصدة الرموز ومالك token account قبل المعاملة وبعدها، مما يتيح لك حساب المقدار الدقيق للرموز المُحوَّلة. يضمن هذا الأسلوب التقاط التغييرات الفعلية في الرصيد.
- إذا ظل حقل
ownerفي كلٍّ منpreTokenBalancesوpostTokenBalancesدون تغيير، احسب الفرق بين حقلَيamount. - إذا تغيرت ملكية token account (اختلاف حقل
ownerبينpreTokenBalancesوpostTokenBalances)، وتطابق المالك الجديد فيpostTokenBalanceمع عنوانownerالمتوقع لبورصتك، فاعتبر الرصيد الكامل المُظهَر في حقلamountضمنpostTokenBalancesهو المبلغ المُودَع.
"meta": {// --snip--"postBalances": ["994375240", "2074080", "2074080", "1141440"],"postTokenBalances": [{"accountIndex": 1,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "0","decimals": 2,"uiAmount": null,"uiAmountString": "0"}},{"accountIndex": 2,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}}],"preBalances": ["994380240", "2074080", "2074080", "1141440"],"preTokenBalances": [{"accountIndex": 1,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "4fvXFPXSL9i7VbiRzoizuW4bhn1dMvRgQzQ6VevssYxw","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "100","decimals": 2,"uiAmount": "1","uiAmountString": "1"}},{"accountIndex": 2,"mint": "Fx1JZFeYbCxLrMv7422YSxpr7YzcsAgpU1MkjZTyCKi2","owner": "8fjS2shNWY8xniiEMLNk1Aek4MAu8Qp2LCXJckVwTD4n","programId": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb","uiTokenAmount": {"amount": "0","decimals": 2,"uiAmount": null,"uiAmountString": "0"}}],// --snip--}
السحب
يجب أن يكون عنوان السحب الذي يقدمه المستخدم هو عنوان محفظة SOL الخاصة به.
قبل تنفيذ تحويل السحب، يجب على البورصة التحقق من العنوان كما هو موضح أعلاه. علاوة على ذلك، يجب أن يكون هذا العنوان مملوكًا لـ System Program وألا يحتوي على بيانات حساب. إذا كان العنوان لا يحتوي على رصيد SOL، يجب الحصول على تأكيد المستخدم قبل المتابعة في عملية السحب. يجب رفض جميع عناوين السحب الأخرى.
من عنوان السحب، يتم اشتقاق Associated Token Account (ATA) للـ mint الصحيح، ويُصدر التحويل إلى ذلك الحساب عبر تعليمة TransferChecked. لاحظ أنه من المحتمل ألا يكون عنوان ATA موجودًا بعد، وفي هذه الحالة يجب على البورصة تمويل الحساب نيابةً عن المستخدم. بالنسبة لحسابات SPL Token، سيتطلب تمويل حساب السحب 0.00203928 SOL (2,039,280 lamport).
قالب أمر spl-token transfer لعملية السحب:
spl-token transfer --fund-recipient <exchange token account> <withdrawal amount> <withdrawal address>
اعتبارات أخرى
صلاحية التجميد
لأسباب تتعلق بالامتثال التنظيمي، قد تختار جهة إصدار رمز SPL Token اختياريًا الاحتفاظ بـ "Freeze Authority" على جميع الحسابات المُنشأة المرتبطة بـ mint الخاصة بها. يتيح لها ذلك تجميد الأصول في أي حساب بإرادتها، مما يجعل الحساب غير قابل للاستخدام حتى يتم إلغاء تجميده. إذا كانت هذه الميزة مُفعَّلة، فسيتم تسجيل pubkey لجهة التجميد في حساب mint الخاص بـ SPL Token.
الدعم الأساسي لمعيار SPL Token-2022 (Token Extensions)
SPL Token-2022 هو أحدث معيار لإنشاء وتبادل الرموز المُغلَّفة/الاصطناعية على سلسلة بلوكشين سولانا.
المعروف أيضًا بـ "Token Extensions"، يحتوي هذا المعيار على العديد من الميزات الجديدة التي يمكن لمنشئي الرموز وأصحاب الحسابات تفعيلها اختياريًا. تشمل هذه الميزات التحويلات السرية، والرسوم على التحويل، وإغلاق الـ mints، والبيانات الوصفية، والمفوضين الدائمين، والملكية غير القابلة للتغيير، وغير ذلك الكثير. يرجى مراجعة دليل الامتدادات لمزيد من المعلومات.
إذا كانت بورصتك تدعم SPL Token، فلن يكون هناك الكثير من العمل الإضافي المطلوب لدعم SPL Token-2022:
- تعمل أداة CLI بسلاسة مع كلا البرنامجين بدءًا من الإصدار 3.0.0.
- تتضمن
preTokenBalancesوpostTokenBalancesأرصدة SPL Token-2022 - يُفهرس RPC حسابات SPL Token-2022، ولكن يجب الاستعلام عنها بشكل منفصل باستخدام معرّف البرنامج
TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
يعمل Associated Token Account بالطريقة نفسها، ويحسب بشكل صحيح مبلغ الإيداع المطلوب من SOL للحساب الجديد.
بسبب الامتدادات، قد تكون الحسابات أكبر من 165 بايت، وبالتالي قد تتطلب أكثر من 0.00203928 SOL للتمويل.
على سبيل المثال، يتضمن برنامج Associated Token Account دائمًا امتداد "immutable owner"، لذا تأخذ الحسابات 170 بايت كحد أدنى، مما يستلزم 0.00207408 SOL.
اعتبارات خاصة بالامتدادات
يوضح القسم السابق الدعم الأساسي لـ SPL Token-2022. نظرًا لأن الامتدادات تُعدِّل سلوك الرموز، قد تحتاج البورصات إلى تغيير طريقة تعاملها مع الرموز.
يمكن عرض جميع الامتدادات على mint أو token account:
spl-token display <account address>
رسوم التحويل
يمكن تهيئة رمز برسوم تحويل، حيث يتم احتجاز جزء من الرموز المحوَّلة في الوجهة لتحصيلها مستقبلًا.
إذا كانت بورصتك تحوِّل هذه الرموز، فكن حذرًا من أنها قد لا تصل بالكامل إلى الوجهة بسبب المبلغ المحتجز.
يمكن تحديد الرسوم المتوقعة أثناء التحويل لتجنب أي مفاجآت:
spl-token transfer --expected-fee <fee amount> --fund-recipient <exchange token account> <withdrawal amount> <withdrawal address>
صلاحية إغلاق الـ Mint
بفضل هذا الامتداد، يمكن لمنشئ الرمز إغلاق الـ mint، شريطة أن يكون المعروض من الرموز صفرًا.
عند إغلاق الـ mint، قد لا تزال هناك token accounts فارغة موجودة، ولن تكون مرتبطة بعد الآن بـ mint صالح.
يمكن إغلاق هذه token accounts بأمان:
spl-token close --address <account address>
التحويل السري
يمكن تهيئة الـ mints للتحويلات السرية، بحيث تكون مبالغ الرموز مشفَّرة، في حين لا يزال أصحاب الحسابات معلنين.
يمكن للبورصات تهيئة token accounts لإرسال واستقبال التحويلات السرية لإخفاء مبالغ المستخدمين. لا يُشترط تفعيل التحويلات السرية على token accounts، لذا يمكن للبورصات إلزام المستخدمين بإرسال الرموز بشكل غير سري.
لتفعيل التحويلات السرية، يجب تهيئة الحساب لذلك:
spl-token configure-confidential-transfer-account --address <account address>
وللتحويل:
spl-token transfer --confidential <exchange token account> <withdrawal amount> <withdrawal address>
أثناء التحويل السري، لن تُظهر حقلا preTokenBalance وpostTokenBalance أي تغيير. لكنشف حسابات الإيداع، يجب فكّ تشفير الرصيد الجديد لسحب الرموز:
spl-token apply-pending-balance --address <account address>spl-token withdraw-confidential-tokens --address <account address> <amount or ALL>
حالة الحساب الافتراضية
يمكن تهيئة الـ mints بحالة حساب افتراضية، بحيث تكون جميع token accounts الجديدة مجمَّدة بشكل افتراضي. قد يطلب منشئو هذه الرموز من المستخدمين المرور بعملية منفصلة لإلغاء تجميد الحساب.
غير قابل للتحويل
بعض الرموز غير قابلة للتحويل، غير أنه لا يزال يمكن حرقها وإغلاق حسابها.
المفوَّض الدائم
يمكن لمنشئي الرموز تعيين مفوَّض دائم لجميع رموزهم. يجوز للمفوَّض الدائم تحويل الرموز أو حرقها من أي حساب، مما قد يؤدي إلى سرقة الأموال.
هذا متطلب قانوني للعملات المستقرة في بعض الولايات القضائية، أو قد يُستخدم في مخططات استرداد الرموز.
كن حذرًا من أن هذه الرموز قد يتم تحويلها دون علم بورصتك.
خطاف التحويل
يمكن تهيئة الرموز ببرنامج إضافي يجب استدعاؤه أثناء التحويلات، للتحقق من صحة التحويل أو تنفيذ أي منطق آخر.
نظرًا لأن بيئة تشغيل سولانا تتطلب تمرير جميع الحسابات بشكل صريح إلى البرنامج، وخطافات التحويل تستلزم حسابات إضافية، تحتاج البورصة إلى إنشاء تعليمات التحويل بشكل مختلف لهذه الرموز.
تُضيف أداة CLI ومنشئو التعليمات مثل createTransferCheckedWithTransferHookInstruction الحسابات الإضافية تلقائيًا، ولكن يمكن أيضًا تحديد الحسابات الإضافية بشكل صريح:
spl-token transfer --transfer-hook-account <pubkey:role> --transfer-hook-account <pubkey:role> ...
المذكرة المطلوبة عند التحويل
يمكن للمستخدمين تهيئة token accounts الخاصة بهم لاشتراط مذكرة عند التحويل.
قد تحتاج البورصات إلى إضافة تعليمة مذكرة قبل تحويل الرموز إلى المستخدمين، أو قد تطلب من المستخدمين إضافة تعليمة مذكرة قبل الإرسال إلى البورصة:
spl-token transfer --with-memo <memo text> <exchange token account> <withdrawal amount> <withdrawal address>
اختبار التكامل
تأكد من اختبار سير عملك الكامل على شبكتَي devnet وtestnet الخاصتين بسولانا clusters قبل الانتقال إلى الإنتاج على mainnet. شبكة devnet هي الأكثر انفتاحًا ومرونةً، وهي مثالية للتطوير الأولي، في حين تقدم testnet تهيئة cluster أكثر واقعية. تدعم كلتا الشبكتين devnet وtestnet نظام الصنبور، قم بتشغيل solana airdrop 1 للحصول على بعض SOL الخاص بـ devnet أو testnet للتطوير والاختبار.
Is this page helpful?