تخطٍ إلى المحتوى الرئيسي

إرشادات تفصيلية لعقد ERC-721 الخاص بـ فايبر

Vyper
erc-721
Python
المستوى المبتدئ
أوري بوميرانتز
1 أبريل 2021
19 دقيقة قراءة

مقدمة

يُستخدم معيار ERC-721 للاحتفاظ بملكية الرموز غير القابلة للاستبدال (إن إف تي). تتصرف رموز ERC-20 كسلعة، لأنه لا يوجد فرق بين الرموز الفردية. على النقيض من ذلك، صُممت رموز ERC-721 للأصول المتشابهة ولكن غير المتطابقة، مثل رسوم القطط المتحركة (opens in a new tab) المختلفة أو صكوك ملكية العقارات المختلفة.

في هذه المقالة، سنحلل عقد ERC-721 الخاص بـ Ryuya Nakamura (opens in a new tab). هذا العقد مكتوب بلغة فايبر (opens in a new tab)، وهي لغة عقود شبيهة بلغة بايثون مصممة لجعل كتابة نصوص برمجية غير آمنة أصعب مما هي عليه في لغة سوليديتي.

العقد

1# @dev تنفيذ معيار الرمز غير القابل للاستبدال ERC-721.
2# @author Ryuya Nakamura (@nrryuya)
3# مُعدَّل من: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy

تبدأ التعليقات في لغة فايبر، كما في بايثون، برمز الهاش (#) وتستمر حتى نهاية السطر. تُستخدم التعليقات التي تتضمن @<keyword> بواسطة نات سبيك (opens in a new tab) لإنتاج توثيق يمكن للبشر قراءته.

1from vyper.interfaces import ERC721
2
3implements: ERC721

واجهة ERC-721 مدمجة في لغة فايبر. يمكنك رؤية تعريف النص البرمجي هنا (opens in a new tab). تعريف الواجهة مكتوب بلغة بايثون، وليس فايبر، لأن الواجهات لا تُستخدم فقط داخل البلوكتشين، ولكن أيضًا عند إرسال معاملة إلى البلوكتشين من عميل خارجي، والذي قد يكون مكتوبًا بلغة بايثون.

السطر الأول يستورد الواجهة، والثاني يحدد أننا نقوم بتنفيذها هنا.

واجهة ERC721Receiver

1# واجهة للعقد الذي تستدعيه safeTransferFrom()
2interface ERC721Receiver:
3 def onERC721Received(

يدعم ERC-721 نوعين من التحويل:

  • transferFrom، التي تتيح للمرسل تحديد أي عنوان وجهة وتضع مسؤولية التحويل على المرسل. هذا يعني أنه يمكنك التحويل إلى عنوان غير صالح، وفي هذه الحالة يُفقد الرمز غير القابل للاستبدال (إن إف تي) إلى الأبد.
  • safeTransferFrom، التي تتحقق مما إذا كان عنوان الوجهة عقدًا. إذا كان الأمر كذلك، يسأل عقد ERC-721 العقد المُستقبِل ما إذا كان يريد استلام الرمز غير القابل للاستبدال (إن إف تي).

للإجابة على طلبات safeTransferFrom، يجب على العقد المستقبِل تنفيذ ERC721Receiver.

1 _operator: address,
2 _from: address,

عنوان _from هو المالك الحالي للرمز. عنوان _operator هو الذي طلب التحويل (قد لا يكونان متماثلين، بسبب المخصصات).

1 _tokenId: uint256,

معرفات رموز ERC-721 هي 256 بت. عادةً ما يتم إنشاؤها عن طريق تجزئة (هاش) وصف لما يمثله الرمز.

1 _data: Bytes[1024]

يمكن أن يحتوي الطلب على ما يصل إلى 1024 بايت من بيانات المستخدم.

1 ) -> bytes32: view

لمنع الحالات التي يقبل فيها العقد عن طريق الخطأ عملية تحويل، فإن القيمة المرجعة ليست قيمة منطقية (boolean)، ولكنها 256 بت بقيمة محددة.

هذه الدالة هي view، مما يعني أنها تستطيع قراءة حالة البلوكتشين، ولكن لا يمكنها تعديلها.

الأحداث

يتم إصدار الأحداث (opens in a new tab) لإبلاغ المستخدمين والخوادم خارج البلوكتشين بالأحداث. لاحظ أن محتوى الأحداث غير متاح للعقود على البلوكتشين.

1# @dev يتم إصداره عند تغيير ملكية أي رمز غير قابل للاستبدال (NFT) بأي آلية. يتم إصدار هذا الحدث عند إنشاء رموز NFT (`from` == 0) وتدميرها (`to` == 0). استثناء: أثناء إنشاء العقد، يمكن إنشاء وتعيين أي عدد من رموز NFT دون إصدار حدث Transfer. في وقت أي تحويل، تتم إعادة تعيين العنوان المعتمد لهذا الرمز (إن وجد) إلى لا شيء.
2# @param _from مرسل الرمز غير القابل للاستبدال (NFT) (إذا كان العنوان هو العنوان الصفري فهذا يشير إلى إنشاء الرمز).
3# @param _to مستلم الرمز غير القابل للاستبدال (NFT) (إذا كان العنوان هو العنوان الصفري فهذا يشير إلى تدمير الرمز).
4# @param _tokenId الرمز غير القابل للاستبدال (NFT) الذي تم تحويله.
5event Transfer:
6 sender: indexed(address)
7 receiver: indexed(address)
8 tokenId: indexed(uint256)

هذا مشابه لحدث التحويل الخاص بمعيار ERC-20، باستثناء أننا نبلغ عن tokenId بدلاً من مبلغ. لا أحد يمتلك العنوان الصفري، لذلك حسب العرف نستخدمه للإبلاغ عن إنشاء وتدمير الرموز.

1# @dev يصدر هذا عند تغيير العنوان المعتمد لرمز غير قابل للاستبدال (NFT) أو إعادة تأكيده. يشير العنوان الصفري إلى عدم وجود عنوان معتمد. عندما يصدر حدث Transfer، يشير هذا أيضًا إلى أن العنوان المعتمد لهذا الرمز (إن وجد) تتم إعادة تعيينه إلى لا شيء.
2# @param _owner مالك الرمز غير القابل للاستبدال (NFT).
3# @param _approved العنوان الذي نوافق عليه.
4# @param _tokenId الرمز غير القابل للاستبدال (NFT) الذي نوافق عليه.
5event Approval:
6 owner: indexed(address)
7 approved: indexed(address)
8 tokenId: indexed(uint256)

الموافقة في ERC-721 مشابهة للمخصصات في ERC-20. يُسمح لعنوان معين بتحويل رمز معين. وهذا يعطي آلية للعقود للاستجابة عندما تقبل رمزًا. لا يمكن للعقود الاستماع للأحداث، لذلك إذا قمت فقط بتحويل الرمز إليها، فإنها لا "تعرف" عنه. بهذه الطريقة، يقدم المالك أولاً موافقة ثم يرسل طلبًا إلى العقد: "لقد وافقت على قيامك بتحويل الرمز X، من فضلك افعل ...".

هذا خيار تصميم لجعل معيار ERC-721 مشابهًا لمعيار ERC-20. لأن رموز ERC-721 غير قابلة للاستبدال، يمكن للعقد أيضًا تحديد أنه حصل على رمز معين من خلال النظر في ملكية الرمز.

1# @dev يصدر هذا الحدث عند تمكين أو تعطيل مشغل لمالك. يمكن للمشغل إدارة جميع رموز NFT الخاصة بالمالك.
2# @param _owner مالك الرمز غير القابل للاستبدال (NFT).
3# @param _operator العنوان الذي نقوم بتعيين حقوق المشغل له.
4# @param _approved حالة حقوق المشغل (true إذا تم منح حقوق المشغل و false إذا تم إلغاؤها).
5event ApprovalForAll:
6 owner: indexed(address)
7 operator: indexed(address)
8 approved: bool

من المفيد أحيانًا وجود مشغل يمكنه إدارة جميع رموز حساب من نوع معين (تلك التي يديرها عقد معين)، على غرار التوكيل الرسمي. على سبيل المثال، قد أرغب في منح مثل هذه السلطة لعقد يتحقق مما إذا كنت لم أتصل به لمدة ستة أشهر، وإذا كان الأمر كذلك، فإنه يوزع أصولي على ورثتي (إذا طلب أحدهم ذلك، فلا يمكن للعقود فعل أي شيء دون أن يتم استدعاؤها من خلال معاملة). في ERC-20، يمكننا فقط منح مخصصات عالية لعقد ميراث، ولكن هذا لا يعمل مع ERC-721 لأن الرموز ليست قابلة للاستبدال. هذا هو المعادل.

تخبرنا قيمة approved ما إذا كان الحدث للموافقة، أو لسحب الموافقة.

متغيرات الحالة

تحتوي هذه المتغيرات على الحالة الحالية للرموز: أيها متاح ومن يمتلكها. معظمها عبارة عن كائنات HashMap، وهي تعيينات أحادية الاتجاه موجودة بين نوعين (opens in a new tab).

1# @dev تعيين من معرف الرمز غير القابل للاستبدال (NFT) إلى العنوان الذي يمتلكه.
2idToOwner: HashMap[uint256, address]
3
4# @dev تعيين من معرف الرمز غير القابل للاستبدال (NFT) إلى العنوان المعتمد.
5idToApprovals: HashMap[uint256, address]

يتم تمثيل هويات المستخدمين والعقود في إيثريوم بعناوين 160 بت. يقوم هذان المتغيران بالتعيين من معرّفات الرموز إلى مالكيها وأولئك المعتمدين لتحويلها (بحد أقصى واحد لكل منهما). في إيثريوم، تكون البيانات غير المهيأة دائمًا صفرًا، لذلك إذا لم يكن هناك مالك أو محوّل معتمد، فإن قيمة هذا الرمز تكون صفرًا.

1# @dev تعيين من عنوان المالك إلى عدد رموزه.
2ownerToNFTokenCount: HashMap[address, uint256]

يحمل هذا المتغير عدد الرموز لكل مالك. لا يوجد تعيين من المالكين إلى الرموز، لذا فإن الطريقة الوحيدة لتحديد الرموز التي يمتلكها مالك معين هي الرجوع إلى سجل أحداث البلوكتشين ورؤية أحداث Transfer المناسبة. يمكننا استخدام هذا المتغير لمعرفة متى يكون لدينا كل رموز إن إف تي ولا نحتاج إلى النظر أبعد من ذلك في الوقت.

لاحظ أن هذه الخوارزمية تعمل فقط مع واجهات المستخدم والخوادم الخارجية. لا يمكن للنص البرمجي الذي يعمل على البلوكتشين نفسه قراءة الأحداث الماضية.

1# @dev تعيين من عنوان المالك إلى تعيين عناوين المشغلين.
2ownerToOperators: HashMap[address, HashMap[address, bool]]

قد يكون للحساب أكثر من مشغل واحد. HashMap بسيط غير كافٍ لتتبعها، لأن كل مفتاح يؤدي إلى قيمة واحدة. بدلاً من ذلك، يمكنك استخدام HashMap[address, bool] كقيمة. بشكل افتراضي، تكون القيمة لكل عنوان هي False، مما يعني أنه ليس مشغلاً. يمكنك تعيين القيم على True حسب الحاجة.

1# @dev عنوان الساك، الذي يمكنه سك رمز
2minter: address

يجب إنشاء الرموز الجديدة بطريقة ما. في هذا العقد، هناك كيان واحد مسموح له بالقيام بذلك، وهو minter. من المحتمل أن يكون هذا كافيًا للعبة، على سبيل المثال. لأغراض أخرى، قد يكون من الضروري إنشاء منطق أعمال أكثر تعقيدًا.

1# @dev تعيين من معرّف الواجهة إلى قيمة منطقية حول ما إذا كانت مدعومة أم لا
2supportedInterfaces: HashMap[bytes32, bool]
3
4# @dev معرّف واجهة ERC165 الخاص بـ ERC165
5ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
6
7# @dev معرّف واجهة ERC165 الخاص بـ ERC721
8ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd

يحدد ERC-165 (opens in a new tab) آلية للعقد للكشف عن كيفية تواصل التطبيقات معه، وإلى أي من معايير ERC يتوافق. في هذه الحالة، يتوافق العقد مع ERC-165 و ERC-721.

الدوال

هذه هي الدوال التي تنفذ بالفعل ERC-721.

الدالة الإنشائية

1@external
2def __init__():

في فايبر، كما هو الحال في بايثون، تُسمى الدالة الإنشائية __init__.

1 """
2 @dev الدالة الإنشائية للعقد.
3 """

في بايثون، وفي فايبر، يمكنك أيضًا إنشاء تعليق عن طريق تحديد سلسلة نصية متعددة الأسطر (تبدأ وتنتهي بـ """)، وعدم استخدامها بأي طريقة. يمكن أن تتضمن هذه التعليقات أيضًا نات سبيك (opens in a new tab).

1 self.supportedInterfaces[ERC165_INTERFACE_ID] = True
2 self.supportedInterfaces[ERC721_INTERFACE_ID] = True
3 self.minter = msg.sender

للوصول إلى متغيرات الحالة، استخدم self.<variable name> (مرة أخرى، كما هو الحال في بايثون).

دوال العرض

هذه هي الدوال التي لا تعدل حالة البلوكتشين، وبالتالي يمكن تنفيذها مجانًا إذا تم استدعاؤها خارجيًا. إذا تم استدعاء دوال العرض بواسطة عقد، فلا يزال يتعين تنفيذها على كل عقدة وبالتالي تكلف غازًا.

1@view
2@external

هذه الكلمات المفتاحية التي تسبق تعريف الدالة والتي تبدأ بعلامة @ (@) تسمى الديكورات. إنها تحدد الظروف التي يمكن فيها استدعاء الدالة.

  • @view تحدد أن هذه الدالة هي دالة عرض.
  • @external تحدد أنه يمكن استدعاء هذه الدالة المعينة عن طريق المعاملات ومن خلال العقود الأخرى.
1def supportsInterface(_interfaceID: bytes32) -> bool:

على عكس بايثون، فإن فايبر هي لغة ذات أنواع ثابتة (opens in a new tab). لا يمكنك الإعلان عن متغير، أو معلمة دالة، دون تحديد نوع البيانات (opens in a new tab). في هذه الحالة، معلمة الإدخال هي bytes32، وهي قيمة 256 بت (256 بت هو حجم الكلمة الأصلي لـآلة إيثريوم الافتراضية). الناتج هو قيمة منطقية. حسب العرف، تبدأ أسماء معلمات الدالة بشرطة سفلية (_).

1 """
2 @dev يتم تحديد تعريف الواجهة في ERC-165.
3 @param _interfaceID معرّف الواجهة
4 """
5 return self.supportedInterfaces[_interfaceID]

إرجاع القيمة من self.supportedInterfaces HashMap، والتي يتم تعيينها في الدالة الإنشائية (__init__).

1### دوال العرض ###
2

هذه هي دوال العرض التي توفر معلومات حول الرموز للمستخدمين والعقود الأخرى.

1@view
2@external
3def balanceOf(_owner: address) -> uint256:
4 """
5 @dev ترجع عدد رموز NFT المملوكة لـ `_owner`.
6 تطلق خطأ إذا كان `_owner` هو العنوان الصفري. تعتبر رموز NFT المخصصة للعنوان الصفري غير صالحة.
7 @param _owner العنوان المراد الاستعلام عن رصيده.
8 """
9 assert _owner != ZERO_ADDRESS
إظهار الكل

يؤكد (opens in a new tab) هذا السطر أن _owner ليس صفرًا. إذا كان الأمر كذلك، يحدث خطأ ويتم إرجاع العملية.

1 return self.ownerToNFTokenCount[_owner]
2
3@view
4@external
5def ownerOf(_tokenId: uint256) -> address:
6 """
7 @dev ترجع عنوان مالك الرمز غير القابل للاستبدال (NFT).
8 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا.
9 @param _tokenId المعرف لرمز NFT.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا
13 assert owner != ZERO_ADDRESS
14 return owner
إظهار الكل

في آلة إيثريوم الافتراضية (EVM)، أي مساحة تخزين لا تحتوي على قيمة مخزنة فيها تكون صفرًا. إذا لم يكن هناك رمز في _tokenId، فإن قيمة self.idToOwner[_tokenId] هي صفر. في هذه الحالة، ترجع الدالة.

1@view
2@external
3def getApproved(_tokenId: uint256) -> address:
4 """
5 @dev الحصول على العنوان المعتمد لرمز NFT واحد.
6 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا.
7 @param _tokenId معرّف الرمز غير القابل للاستبدال (NFT) المراد الاستعلام عن موافقته.
8 """
9 # تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا
10 assert self.idToOwner[_tokenId] != ZERO_ADDRESS
11 return self.idToApprovals[_tokenId]
إظهار الكل

لاحظ أن getApproved يمكن أن ترجع صفرًا. إذا كان الرمز صالحًا، فإنه يرجع self.idToApprovals[_tokenId]. إذا لم يكن هناك موافِق، فإن هذه القيمة تكون صفرًا.

1@view
2@external
3def isApprovedForAll(_owner: address, _operator: address) -> bool:
4 """
5 @dev تتحقق مما إذا كان `_operator` مشغلًا معتمدًا لـ `_owner`.
6 @param _owner العنوان الذي يمتلك رموز NFT.
7 @param _operator العنوان الذي يعمل نيابة عن المالك.
8 """
9 return (self.ownerToOperators[_owner])[_operator]
إظهار الكل

تتحقق هذه الدالة مما إذا كان مسموحًا لـ _operator بإدارة جميع رموز _owner في هذا العقد. نظرًا لأنه يمكن أن يكون هناك العديد من المشغلين، فهذا HashMap من مستويين.

الدوال المساعدة للتحويل

تنفذ هذه الدوال العمليات التي هي جزء من تحويل أو إدارة الرموز.

1
2### الدوال المساعدة لدالة التحويل ###
3
4@view
5@internal

هذا الديكور، @internal، يعني أن الدالة يمكن الوصول إليها فقط من دوال أخرى داخل نفس العقد. حسب العرف، تبدأ أسماء هذه الدوال أيضًا بشرطة سفلية (_).

1def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
2 """
3 @dev ترجع ما إذا كان المُنفِق المحدد يمكنه تحويل معرّف رمز معين
4 @param spender عنوان المُنفِق للاستعلام عنه
5 @param tokenId معرّف الرمز uint256 المراد تحويله
6 @return bool ما إذا كان msg.sender معتمدًا لمعرّف الرمز المحدد،
7 أو كان مشغلًا للمالك، أو مالك الرمز
8 """
9 owner: address = self.idToOwner[_tokenId]
10 spenderIsOwner: bool = owner == _spender
11 spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
12 spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
13 return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
إظهار الكل

هناك ثلاث طرق يمكن من خلالها السماح لعنوان ما بتحويل رمز:

  1. العنوان هو مالك الرمز
  2. العنوان معتمد لإنفاق هذا الرمز
  3. العنوان هو مشغل لمالك الرمز

يمكن أن تكون الدالة أعلاه دالة عرض لأنها لا تغير الحالة. لتقليل تكاليف التشغيل، أي دالة يمكن أن تكون دالة عرض يجب أن تكون دالة عرض.

1@internal
2def _addTokenTo(_to: address, _tokenId: uint256):
3 """
4 @dev إضافة رمز NFT إلى عنوان معين
5 تطلق خطأ إذا كان `_tokenId` مملوكًا لشخص ما.
6 """
7 # تطلق خطأ إذا كان `_tokenId` مملوكًا لشخص ما
8 assert self.idToOwner[_tokenId] == ZERO_ADDRESS
9 # تغيير المالك
10 self.idToOwner[_tokenId] = _to
11 # تغيير تتبع العدد
12 self.ownerToNFTokenCount[_to] += 1
13
14@internal
15def _removeTokenFrom(_from: address, _tokenId: uint256):
16 """
17 @dev إزالة رمز NFT من عنوان معين
18 تطلق خطأ إذا لم يكن `_from` هو المالك الحالي.
19 """
20 # تطلق خطأ إذا لم يكن `_from` هو المالك الحالي
21 assert self.idToOwner[_tokenId] == _from
22 # تغيير المالك
23 self.idToOwner[_tokenId] = ZERO_ADDRESS
24 # تغيير تتبع العدد
25 self.ownerToNFTokenCount[_from] -= 1
إظهار الكل

عندما تكون هناك مشكلة في التحويل، نقوم بإرجاع الاستدعاء.

1@internal
2def _clearApproval(_owner: address, _tokenId: uint256):
3 """
4 @dev مسح موافقة على عنوان معين
5 تطلق خطأ إذا لم يكن `_owner` هو المالك الحالي.
6 """
7 # تطلق خطأ إذا لم يكن `_owner` هو المالك الحالي
8 assert self.idToOwner[_tokenId] == _owner
9 if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
10 # إعادة تعيين الموافقات
11 self.idToApprovals[_tokenId] = ZERO_ADDRESS
إظهار الكل

غيّر القيمة فقط إذا لزم الأمر. متغيرات الحالة موجودة في التخزين. الكتابة إلى التخزين هي إحدى أغلى العمليات التي تقوم بها آلة إيثريوم الافتراضية (EVM) (من حيث الغاز). لذلك، من الجيد تقليلها، حتى كتابة القيمة الحالية لها تكلفة عالية.

1@internal
2def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
3 """
4 @dev تنفيذ تحويل رمز NFT.
5 تطلق خطأ ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا معتمدًا، أو العنوان المعتمد لهذا الرمز NFT. (ملاحظة: `msg.sender` غير مسموح به في الدالة الخاصة لذا مرر `_sender`.)
6 تطلق خطأ إذا كان `_to` هو العنوان الصفري.
7 تطلق خطأ إذا لم يكن `_from` هو المالك الحالي.
8 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا.
9 """
إظهار الكل

لدينا هذه الدالة الداخلية لأن هناك طريقتين لتحويل الرموز (عادية وآمنة)، ولكننا نريد موقعًا واحدًا فقط في النص البرمجي حيث نقوم بذلك لتسهيل المراجعة.

1 # تحقق من المتطلبات
2 assert self._isApprovedOrOwner(_sender, _tokenId)
3 # تطلق خطأ إذا كان `_to` هو العنوان الصفري
4 assert _to != ZERO_ADDRESS
5 # مسح الموافقة. تطلق خطأ إذا لم يكن `_from` هو المالك الحالي
6 self._clearApproval(_from, _tokenId)
7 # إزالة الرمز غير القابل للاستبدال (NFT). تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا
8 self._removeTokenFrom(_from, _tokenId)
9 # إضافة الرمز غير القابل للاستبدال (NFT)
10 self._addTokenTo(_to, _tokenId)
11 # تسجيل التحويل
12 log Transfer(_from, _to, _tokenId)
إظهار الكل

لإصدار حدث في فايبر، استخدم عبارة log (انظر هنا لمزيد من التفاصيل (opens in a new tab)).

دوال التحويل

1
2### دوال التحويل ###
3
4@external
5def transferFrom(_from: address, _to: address, _tokenId: uint256):
6 """
7 @dev تطلق خطأ ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا معتمدًا، أو العنوان المعتمد لهذا الرمز NFT.
8 تطلق خطأ إذا لم يكن `_from` هو المالك الحالي.
9 تطلق خطأ إذا كان `_to` هو العنوان الصفري.
10 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا.
11 @notice المتصل هو المسؤول عن التأكد من أن `_to` قادر على استلام رموز NFT وإلا فقد تُفقد بشكل دائم.
12 @param _from المالك الحالي لرمز NFT.
13 @param _to المالك الجديد.
14 @param _tokenId الرمز غير القابل للاستبدال (NFT) المراد تحويله.
15 """
16 self._transferFrom(_from, _to, _tokenId, msg.sender)
إظهار الكل

تتيح لك هذه الدالة التحويل إلى عنوان عشوائي. ما لم يكن العنوان مستخدمًا، أو عقدًا يعرف كيفية تحويل الرموز، فإن أي رمز تقوم بتحويله سيبقى عالقًا في ذلك العنوان وعديم الفائدة.

1@external
2def safeTransferFrom(
3 _from: address,
4 _to: address,
5 _tokenId: uint256,
6 _data: Bytes[1024]=b""
7 ):
8 """
9 @dev تحويل ملكية رمز NFT من عنوان إلى آخر.
10 تطلق خطأ ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا معتمدًا، أو العنوان المعتمد لهذا الرمز NFT.
11 تطلق خطأ إذا لم يكن `_from` هو المالك الحالي.
12 تطلق خطأ إذا كان `_to` هو العنوان الصفري.
13 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا.
14 إذا كان `_to` عقدًا ذكيًا، فإنه يستدعي `onERC721Received` على `_to` ويطلق خطأ إذا لم تكن القيمة المرجعة هي `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
15 ملاحظة: يتم تمثيل bytes4 بـ bytes32 مع حشو
16 @param _from المالك الحالي لرمز NFT.
17 @param _to المالك الجديد.
18 @param _tokenId الرمز غير القابل للاستبدال (NFT) المراد تحويله.
19 @param _data بيانات إضافية بدون تنسيق محدد، يتم إرسالها في استدعاء إلى `_to`.
20 """
21 self._transferFrom(_from, _to, _tokenId, msg.sender)
إظهار الكل

لا بأس من إجراء التحويل أولاً لأنه إذا كانت هناك مشكلة فسنقوم بإرجاع العملية على أي حال، لذلك سيتم إلغاء كل ما تم في الاستدعاء.

1 if _to.is_contract: # تحقق مما إذا كان `_to` عنوان عقد

تحقق أولاً لمعرفة ما إذا كان العنوان عقدًا (إذا كان لديه نص برمجي). إذا لم يكن كذلك، افترض أنه عنوان مستخدم وأن المستخدم سيكون قادرًا على استخدام الرمز أو تحويله. لكن لا تدع ذلك يخدعك ويوهمك بشعور زائف بالأمان. يمكنك أن تفقد الرموز، حتى مع safeTransferFrom، إذا قمت بتحويلها إلى عنوان لا يعرف أحد مفتاحه الخاص.

1 returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)

استدعِ العقد المستهدف لمعرفة ما إذا كان يمكنه استلام رموز ERC-721.

1 # تطلق خطأ إذا كانت وجهة التحويل عقدًا لا ينفذ 'onERC721Received'
2 assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)

إذا كانت الوجهة عقدًا، ولكنها لا تقبل رموز ERC-721 (أو قررت عدم قبول هذا التحويل بالذات)، فسيتم إرجاع العملية.

1@external
2def approve(_approved: address, _tokenId: uint256):
3 """
4 @dev تعيين أو إعادة تأكيد العنوان المعتمد لرمز NFT. يشير العنوان الصفري إلى عدم وجود عنوان معتمد.
5 تطلق خطأ ما لم يكن `msg.sender` هو مالك الرمز NFT الحالي، أو مشغلًا معتمدًا للمالك الحالي.
6 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا. (ملاحظة: هذا ليس مكتوبًا في EIP)
7 تطلق خطأ إذا كان `_approved` هو المالك الحالي. (ملاحظة: هذا ليس مكتوبًا في EIP)
8 @param _approved العنوان المراد اعتماده لمعرّف الرمز NFT المحدد.
9 @param _tokenId معرّف الرمز المراد اعتماده.
10 """
11 owner: address = self.idToOwner[_tokenId]
12 # تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا
13 assert owner != ZERO_ADDRESS
14 # تطلق خطأ إذا كان `_approved` هو المالك الحالي
15 assert _approved != owner
إظهار الكل

حسب العرف، إذا كنت لا تريد أن يكون لديك موافِق، فأنت تعين العنوان الصفري، وليس نفسك.

1 # تحقق من المتطلبات
2 senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
3 senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
4 assert (senderIsOwner or senderIsApprovedForAll)

لتعيين موافقة، يمكنك إما أن تكون المالك، أو مشغلًا معتمدًا من قبل المالك.

1 # تعيين الموافقة
2 self.idToApprovals[_tokenId] = _approved
3 log Approval(owner, _approved, _tokenId)
4
5@external
6def setApprovalForAll(_operator: address, _approved: bool):
7 """
8 @dev تمكين أو تعطيل الموافقة لطرف ثالث ("مشغل") لإدارة جميع أصول `msg.sender`.
9 كما أنه يصدر حدث ApprovalForAll.
10 يطلق خطأ إذا كان `_operator` هو `msg.sender`. (ملاحظة: هذا ليس مكتوبًا في EIP)
11 @notice يعمل هذا حتى لو لم يكن المرسل يمتلك أي رموز في ذلك الوقت.
12 @param _operator العنوان المراد إضافته إلى مجموعة المشغلين المعتمدين.
13 @param _approved True إذا تمت الموافقة على المشغلين، وfalse لإلغاء الموافقة.
14 """
15 # يطلق خطأ إذا كان `_operator` هو `msg.sender`
16 assert _operator != msg.sender
17 self.ownerToOperators[msg.sender][_operator] = _approved
18 log ApprovalForAll(msg.sender, _operator, _approved)
إظهار الكل

سك الرموز الجديدة وتدمير الرموز الحالية

الحساب الذي أنشأ العقد هو minter، المستخدم الخارق المعتمد لسك رموز إن إف تي جديدة. ومع ذلك، حتى هو غير مسموح له بحرق الرموز الحالية. فقط المالك، أو كيان معتمد من المالك، يمكنه القيام بذلك.

1### دوال السك والحرق ###
2
3@external
4def mint(_to: address, _tokenId: uint256) -> bool:

ترجع هذه الدالة دائمًا True، لأنه إذا فشلت العملية فسيتم إرجاعها.

1 """
2 @dev دالة لسك الرموز
3 تطلق خطأ إذا لم يكن `msg.sender` هو الساك.
4 تطلق خطأ إذا كان `_to` هو العنوان الصفري.
5 تطلق خطأ إذا كان `_tokenId` مملوكًا لشخص ما.
6 @param _to العنوان الذي سيستقبل الرموز المسكوكة.
7 @param _tokenId معرّف الرمز المراد سكه.
8 @return قيمة منطقية تشير إلى ما إذا كانت العملية ناجحة.
9 """
10 # تطلق خطأ إذا لم يكن `msg.sender` هو الساك
11 assert msg.sender == self.minter
إظهار الكل

فقط الساك (الحساب الذي أنشأ عقد ERC-721) يمكنه سك رموز جديدة. قد تكون هذه مشكلة في المستقبل إذا أردنا تغيير هوية الساك. في عقد الإنتاج، قد ترغب على الأرجح في وجود دالة تسمح للساك بنقل امتيازات السك إلى شخص آخر.

1 # تطلق خطأ إذا كان `_to` هو العنوان الصفري
2 assert _to != ZERO_ADDRESS
3 # إضافة الرمز غير القابل للاستبدال (NFT). تطلق خطأ إذا كان `_tokenId` مملوكًا لشخص ما
4 self._addTokenTo(_to, _tokenId)
5 log Transfer(ZERO_ADDRESS, _to, _tokenId)
6 return True

حسب العرف، يعتبر سك الرموز الجديدة بمثابة تحويل من العنوان الصفري.

1
2@external
3def burn(_tokenId: uint256):
4 """
5 @dev يحرق رمز ERC721 محدد.
6 تطلق خطأ ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا معتمدًا، أو العنوان المعتمد لهذا الرمز NFT.
7 تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا.
8 @param _tokenId معرّف uint256 لرمز ERC721 المراد حرقه.
9 """
10 # تحقق من المتطلبات
11 assert self._isApprovedOrOwner(msg.sender, _tokenId)
12 owner: address = self.idToOwner[_tokenId]
13 # تطلق خطأ إذا لم يكن `_tokenId` رمز NFT صالحًا
14 assert owner != ZERO_ADDRESS
15 self._clearApproval(owner, _tokenId)
16 self._removeTokenFrom(owner, _tokenId)
17 log Transfer(owner, ZERO_ADDRESS, _tokenId)
إظهار الكل

أي شخص مسموح له بتحويل رمز مسموح له بحرقه. في حين يبدو الحرق مكافئًا للتحويل إلى العنوان الصفري، إلا أن العنوان الصفري لا يتلقى الرمز فعليًا. هذا يسمح لنا بتحرير كل مساحة التخزين التي تم استخدامها للرمز، مما يمكن أن يقلل من تكلفة الغاز للمعاملة.

استخدام هذا العقد

على عكس سوليديتي، لا تملك فايبر ميزة الوراثة. هذا خيار تصميم متعمد لجعل النص البرمجي أوضح وبالتالي أسهل في التأمين. لذلك لإنشاء عقد فايبر ERC-721 خاص بك، خذ هذا العقد (opens in a new tab) وقم بتعديله لتنفيذ منطق الأعمال الذي تريده.

الخلاصة

للمراجعة، إليك بعض أهم الأفكار في هذا العقد:

  • لاستلام رموز ERC-721 مع تحويل آمن، يجب على العقود تنفيذ واجهة ERC721Receiver.
  • حتى إذا كنت تستخدم التحويل الآمن، فلا يزال من الممكن أن تعلق الرموز إذا أرسلتها إلى عنوان لا يُعرف مفتاحه الخاص.
  • عندما تكون هناك مشكلة في عملية ما، فمن الجيد إرجاع الاستدعاء، بدلاً من مجرد إرجاع قيمة فشل.
  • توجد رموز ERC-721 عندما يكون لها مالك.
  • هناك ثلاث طرق لتكون معتمدًا لتحويل رمز إن إف تي. يمكنك أن تكون المالك، أو معتمدًا لرمز معين، أو أن تكون مشغلًا لجميع رموز المالك.
  • الأحداث الماضية مرئية فقط خارج البلوكتشين. لا يمكن للنص البرمجي الذي يعمل داخل البلوكتشين عرضها.

اذهب الآن ونفذ عقود فايبر آمنة.

انظر هنا لمزيد من أعمالي (opens in a new tab).

آخر تحديث للصفحة: 22 أغسطس 2025

هل كانت تعليمات الاستخدام هذه مفيدة؟