---
title: "جولة تفصيلية في عقد ⁦ERC-721⁩ بلغة ⁦Vyper⁩"
description: "عقد ⁦ERC-721⁩ الخاص بـ ريويا ناكامورا وكيفية عمله"
author: "أوري بوميرانتس"
lang: ar
tags: ["Vyper", "erc-721", "Python"]
skill: beginner
breadcrumb: "⁦Vyper ERC-721⁩"
published: 2021-04-01
---

## مقدمة {#introduction}

يُستخدم معيار [<span dir="ltr">ERC-721</span>](/developers/docs/standards/tokens/erc-721/) للاحتفاظ بملكية الرموز المميزة غير القابلة للاستبدال (<span dir="ltr">NFT</span>).
تتصرف الرموز المميزة [<span dir="ltr">ERC-20</span>](/developers/docs/standards/tokens/erc-20/) كسلعة، لأنه لا يوجد فرق بين الرموز المميزة الفردية.
على النقيض من ذلك، تم تصميم الرموز المميزة <span dir="ltr">ERC-721</span> للأصول المتشابهة ولكنها غير متطابقة، مثل [الرسوم المتحركة المختلفة للقطط](https://www.cryptokitties.co/)
أو سندات الملكية لقطع عقارية مختلفة.

في هذه المقالة سنقوم بتحليل [عقد <span dir="ltr">ERC-721</span> الخاص بـ ريويا ناكامورا](https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy).
هذا العقد مكتوب بلغة [Vyper](https://vyper.readthedocs.io/en/latest/index.html)، وهي لغة عقود تشبه لغة Python مصممة لجعل
كتابة التعليمات البرمجية غير الآمنة أكثر صعوبة مما هي عليه في لغة Solidity.

## العقد {#contract}

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

تبدأ التعليقات في لغة Vyper، كما هو الحال في لغة Python، بعلامة التجزئة (`ethereum.ercs`) وتستمر حتى نهاية السطر. تُستخدم التعليقات التي تتضمن
`@<keyword>` بواسطة [NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html) لإنتاج وثائق قابلة للقراءة من قبل البشر.

```python
from vyper.interfaces import ERC721

implements: ERC721
```

واجهة <span dir="ltr">ERC-721</span> مدمجة في لغة Vyper.
[يمكنك رؤية تعريف التعليمات البرمجية هنا](https://github.com/vyperlang/vyper/blob/master/vyper/builtin_interfaces/ERC721.py).
تعريف الواجهة مكتوب بلغة Python، بدلاً من Vyper، لأن الواجهات لا تُستخدم فقط داخل
سلسلة الكتل، ولكن أيضًا عند إرسال معاملة إلى سلسلة الكتل من عميل خارجي، والذي قد يكون مكتوبًا بلغة
Python.

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

```python
#pragma version >0.3.10
```

```python
#pragma version >0.3.10
```
### واجهة <span dir="ltr">ERC721Receiver</span>

```python
# واجهة العقد الذي يتم استدعاؤه بواسطة safeTransferFrom()
interface ERC721Receiver:
    def onERC721Received(
```

يدعم <span dir="ltr">ERC-721</span> نوعين من التحويل:

- `transferFrom`، والذي يتيح للمرسل تحديد أي عنوان وجهة ويضع مسؤولية التحويل على المرسل. هذا يعني أنه يمكنك التحويل إلى عنوان غير صالح، وفي هذه الحالة يُفقد الرمز المميز غير القابل للاستبدال (<span dir="ltr">NFT</span>) إلى الأبد.
- `safeTransferFrom`، والذي يتحقق مما إذا كان عنوان الوجهة عبارة عن عقد. إذا كان الأمر كذلك، يسأل عقد <span dir="ltr">ERC-721</span> العقد المتلقي عما إذا كان يريد استلام الرمز المميز غير القابل للاستبدال (<span dir="ltr">NFT</span>).

للرد على طلبات `safeTransferFrom`، يجب أن ينفذ العقد المتلقي `ERC721Receiver`.

```python
            _operator: address,
            _from: address,
```

العنوان `_from` هو المالك الحالي للرمز المميز. العنوان `_operator` هو العنوان الذي طلب التحويل (قد لا يكون هذان العنوانان متطابقين، بسبب السماحيات). حسب العرف، تبدأ معظم معلمات الدوال في هذا العقد بشرطة سفلية (`_`).

```python
            _tokenId: uint256,
```

تتكون معرفات رموز <span dir="ltr">ERC-721</span> المميزة من 256 بت. عادةً ما يتم إنشاؤها عن طريق عملية التجزئة لوصف ما يمثله الرمز المميز.

```python
            _data: Bytes[1024]
```

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

```python
        ) -> bytes4: nonpayable
```

لمنع الحالات التي يقبل فيها العقد تحويلاً عن طريق الخطأ، فإن القيمة المرجعة ليست قيمة منطقية (boolean)، بل قيمة محددة مكونة من أربعة بايت، وهي محدد الدالة (function selector) لـ `onERC721Received`. الدالة هي `nonpayable` لأن العقد المتلقي قد يغير حالته الخاصة عندما يقبل رمزًا مميزًا.
### الأحداث

يتم إصدار [الأحداث](/developers/docs/smart-contracts/anatomy/#events-and-logs) لإعلام المستخدمين والخوادم خارج سلسلة الكتل بالأحداث. لاحظ أن محتوى الأحداث غير متاح للعقود الموجودة على سلسلة الكتل. يتم تعريف أحداث <span dir="ltr">ERC-721</span> الثلاثة بواسطة واجهة `IERC721` التي استوردناها، لذلك لا يعلن هذا العقد عنها بنفسه؛ بل يصدرها باستخدام `log IERC721.<Event>(...)`، كما سنرى في دوال التحويل أدناه.

يُبلغ `Transfer` (`sender`، `receiver`، `token_id`) عن تغيير في ملكية رمز مميز غير قابل للاستبدال (<span dir="ltr">NFT</span>). هذا مشابه لحدث التحويل في <span dir="ltr">ERC-20</span>، باستثناء أننا نبلغ عن `token_id` بدلاً من مبلغ. لا أحد يمتلك العنوان الصفري، لذلك حسب العرف نستخدمه للإبلاغ عن إنشاء وتدمير الرموز المميزة. الاستثناء الوحيد هو إنشاء العقد، والذي يمكن خلاله إنشاء وتعيين أي عدد من الرموز المميزة غير القابلة للاستبدال (<span dir="ltr">NFTs</span>) دون إصدار `Transfer`.

تشبه الموافقة في <span dir="ltr">ERC-721</span> السماحية في <span dir="ltr">ERC-20</span>: يُسمح لعنوان معين بتحويل رمز مميز معين، ويتم إصدار `Approval` (`owner`، `approved`، `token_id`) كلما تم تعيين هذا العنوان المعتمد أو إعادة تأكيده. يوفر هذا آلية للعقود للرد عندما تقبل رمزًا مميزًا. لا يمكن للعقود الاستماع إلى الأحداث، لذلك إذا قمت بمجرد تحويل الرمز المميز إليها فإنها لا "تعرف" بذلك. بهذه الطريقة يقدم المالك أولاً موافقة ثم يرسل طلبًا إلى العقد: "لقد وافقت لك على تحويل الرمز المميز X، يرجى القيام بـ ...". هذا خيار تصميمي لجعل معيار <span dir="ltr">ERC-721</span> مشابهًا لمعيار <span dir="ltr">ERC-20</span>. نظرًا لأن رموز <span dir="ltr">ERC-721</span> المميزة غير قابلة للاستبدال، يمكن للعقد أيضًا تحديد أنه حصل على رمز مميز معين من خلال النظر في ملكية الرمز المميز.

أخيرًا، يتم إصدار `ApprovalForAll` (`owner`، `operator`، `approved`) عند تمكين أو تعطيل _مشغل_ (operator) لمالك. من المفيد أحيانًا أن يكون لديك مشغل يمكنه إدارة جميع الرموز المميزة لحساب من نوع معين (تلك التي يديرها عقد معين)، على غرار التوكيل الرسمي. على سبيل المثال، قد أرغب في إعطاء مثل هذه الصلاحية لعقد يتحقق مما إذا كنت لم أتصل به لمدة ستة أشهر، وإذا كان الأمر كذلك يوزع أصولي على ورثتي (إذا طلب أحدهم ذلك، لا يمكن للعقود أن تفعل أي شيء دون أن يتم استدعاؤها بواسطة معاملة). في <span dir="ltr">ERC-20</span> يمكننا فقط إعطاء سماحية عالية لعقد الميراث، ولكن هذا لا ينجح مع <span dir="ltr">ERC-721</span> لأن الرموز المميزة غير قابلة للاستبدال. هذا هو المعادل لذلك. تخبرنا القيمة `approved` ما إذا كان الحدث مخصصًا لموافقة، أو لسحب موافقة.
### متغيرات الحالة

تحتوي هذه المتغيرات على الحالة الحالية للرموز المميزة: أي منها متاح ومن يمتلكها. معظم هذه المتغيرات عبارة عن كائنات `HashMap`، وهي [تعيينات أحادية الاتجاه موجودة بين نوعين](https://vyper.readthedocs.io/en/latest/types.html#mappings).

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

# @dev تعيين من معرف الرمز المميز غير القابل للاستبدال (NFT) إلى العنوان المعتمد.
idToApprovals: HashMap[uint256, address]
```

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

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

يحتفظ هذا المتغير بعدد الرموز المميزة لكل مالك. لا يوجد تعيين من المالكين إلى الرموز المميزة، لذا فإن الطريقة الوحيدة لتحديد الرموز المميزة التي يمتلكها مالك معين هي الرجوع إلى سجل أحداث سلسلة الكتل ورؤية أحداث `Transfer` المناسبة. يمكننا استخدام هذا المتغير لمعرفة متى يكون لدينا جميع الرموز المميزة غير القابلة للاستبدال (<span dir="ltr">NFTs</span>) ولا نحتاج إلى البحث أكثر في الماضي.

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

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

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

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

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

```python
# @dev قائمة ثابتة بمعرفات واجهة ERC165 المدعومة
SUPPORTED_INTERFACES: constant(bytes4[2]) = [
    # معرف واجهة ERC165 لـ ERC165
    0x01ffc9a7,
    # معرف واجهة ERC165 لـ ERC721
    0x80ac58cd,
]
```

يحدد [<span dir="ltr">ERC-165</span>](https://eips.ethereum.org/EIPS/eip-165) آلية للعقد للكشف عن كيفية تواصل التطبيقات معه، وإلى أي معايير <span dir="ltr">ERC</span> يتوافق. `SUPPORTED_INTERFACES` هي قائمة ثابتة بمعرفي الواجهة المكونين من أربعة بايت واللذين يتوافق معهما هذا العقد: <span dir="ltr">ERC-165</span> نفسه و<span dir="ltr">ERC-721</span>.
### الدوال {#functions}

هذه هي الدوال التي تنفذ <span dir="ltr">ERC-721</span> فعليًا.

#### المُنشئ

```python
@deploy
def __init__():
```

في لغة Vyper، كما هو الحال في لغة Python، تُسمى دالة المُنشئ `__init__`. يتم تمييزها بالزخرفة (decoration) `@deploy`، مما يعني أنها تعمل مرة واحدة، عند نشر العقد.

```python
    """
    @dev مُنشئ العقد.
    """
```

في لغة Python، وفي لغة Vyper، يمكنك أيضًا إنشاء تعليق عن طريق تحديد سلسلة نصية متعددة الأسطر (والتي تبدأ وتنتهي بـ `"""`)، وعدم استخدامها بأي شكل من الأشكال. يمكن أن تتضمن هذه التعليقات أيضًا [NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html).

```python
    self.minter = msg.sender
```

للوصول إلى متغيرات الحالة، تستخدم `self.<variable name>` (مرة أخرى، كما هو الحال في لغة Python). يسجل المُنشئ الحساب الذي قام بنشر العقد كـ `minter` (الساك).
#### دوال العرض

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

```python
@view
@external
```

تُسمى هذه الكلمات الرئيسية التي تسبق تعريف الدالة والتي تبدأ بعلامة "at" (`@`) _الزخارف_ (decorations). وهي تحدد الظروف التي يمكن فيها استدعاء الدالة.

- يحدد `@view` أن هذه الدالة هي دالة عرض.
- يحدد `@external` أنه يمكن استدعاء هذه الدالة المحددة بواسطة المعاملات والعقود الأخرى.

```python
def supportsInterface(interface_id: bytes4) -> bool:
```

على النقيض من لغة Python، فإن لغة Vyper هي [لغة ذات كتابة ثابتة (static typed)](https://wikipedia.org/wiki/Type_system#Static_type_checking). لا يمكنك الإعلان عن متغير، أو معلمة دالة، دون تحديد [نوع البيانات](https://vyper.readthedocs.io/en/latest/types.html). في هذه الحالة، معلمة الإدخال هي `bytes4`، وهي قيمة مكونة من أربعة بايت، والمخرجات عبارة عن قيمة منطقية.

```python
    """
    @dev تم تحديد تعريف الواجهة في ERC-165.
    @param interface_id معرف الواجهة
    """
    return interface_id in SUPPORTED_INTERFACES
```

تُرجع `True` إذا كان `interface_id` أحد معرفات الواجهة في قائمة `SUPPORTED_INTERFACES`.

```python
### دوال العرض ###
```

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

```python
@view
@external
def balanceOf(_owner: address) -> uint256:
    """
    @dev تُرجع عدد الرموز المميزة غير القابلة للاستبدال (NFTs) المملوكة لـ `_owner`.
         تطرح خطأ إذا كان `_owner` هو العنوان الصفري. تعتبر الرموز المميزة غير القابلة للاستبدال المعينة للعنوان الصفري غير صالحة.
    @param _owner العنوان المراد الاستعلام عن رصيده.
    """
    assert _owner != empty(address)
```

هذا السطر [يؤكد (asserts)](https://vyper.readthedocs.io/en/latest/statements.html#assert) أن `_owner` ليس العنوان الصفري، والمكتوب كـ `empty(address)`. إذا كان كذلك، فهناك خطأ ويتم التراجع عن العملية.

```python
    return self.ownerToNFTokenCount[_owner]

@view
@external
def ownerOf(_tokenId: uint256) -> address:
    """
    @dev تُرجع عنوان مالك الرمز المميز غير القابل للاستبدال (NFT).
         تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
    @param _tokenId معرف الرمز المميز غير القابل للاستبدال.
    """
    owner: address = self.idToOwner[_tokenId]
    # تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
    assert owner != empty(address)
    return owner
```

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

```python
@view
@external
def getApproved(_tokenId: uint256) -> address:
    """
    @dev الحصول على العنوان المعتمد لرمز مميز غير قابل للاستبدال (NFT) واحد.
         تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
    @param _tokenId معرف الرمز المميز غير القابل للاستبدال المراد الاستعلام عن الموافقة الخاصة به.
    """
    # تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
    assert self.idToOwner[_tokenId] != empty(address)
    return self.idToApprovals[_tokenId]
```

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

```python
@view
@external
def isApprovedForAll(_owner: address, _operator: address) -> bool:
    """
    @dev تتحقق مما إذا كان `_operator` مشغلاً معتمدًا لـ `_owner`.
    @param _owner العنوان الذي يمتلك الرموز المميزة غير القابلة للاستبدال.
    @param _operator العنوان الذي يتصرف نيابة عن المالك.
    """
    return (self.ownerToOperators[_owner])[_operator]
```

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

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

```python

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

@view
@internal
```

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

```python
def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool:
    """
    @dev تُرجع ما إذا كان المنفق المحدد يمكنه تحويل معرف رمز مميز محدد
    @param spender عنوان المنفق المراد الاستعلام عنه
    @param tokenId uint256 معرف الرمز المميز المراد تحويله
    @return قيمة منطقية (bool) تشير إلى ما إذا كان msg.sender معتمدًا لمعرف الرمز المميز المحدد،
        أو مشغلاً للمالك، أو مالك الرمز المميز
    """
    owner: address = self.idToOwner[_tokenId]
    spenderIsOwner: bool = owner == _spender
    spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId]
    spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender]
    return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll
```

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

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

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

```python
@internal
def _addTokenTo(_to: address, _tokenId: uint256):
    """
    @dev إضافة رمز مميز غير قابل للاستبدال (NFT) إلى عنوان محدد
         تطرح خطأ إذا كان `_tokenId` مملوكًا لشخص ما.
    """
    # تطرح خطأ إذا كان `_tokenId` مملوكًا لشخص ما
    assert self.idToOwner[_tokenId] == empty(address)
    # تغيير المالك
    self.idToOwner[_tokenId] = _to
    # تغيير تتبع العدد
    self.ownerToNFTokenCount[_to] += 1


@internal
def _removeTokenFrom(_from: address, _tokenId: uint256):
    """
    @dev إزالة رمز مميز غير قابل للاستبدال (NFT) من عنوان محدد
         تطرح خطأ إذا لم يكن `_from` هو المالك الحالي.
    """
    # تطرح خطأ إذا لم يكن `_from` هو المالك الحالي
    assert self.idToOwner[_tokenId] == _from
    # تغيير المالك
    self.idToOwner[_tokenId] = empty(address)
    # تغيير تتبع العدد
    self.ownerToNFTokenCount[_from] -= 1
```

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

```python
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
    """
    @dev مسح موافقة لعنوان محدد
         تطرح خطأ إذا لم يكن `_owner` هو المالك الحالي.
    """
    # تطرح خطأ إذا لم يكن `_owner` هو المالك الحالي
    assert self.idToOwner[_tokenId] == _owner
    if self.idToApprovals[_tokenId] != empty(address):
        # إعادة تعيين الموافقات
        self.idToApprovals[_tokenId] = empty(address)
```

قم بتغيير القيمة فقط إذا لزم الأمر. تعيش متغيرات الحالة في مساحة التخزين. تعد الكتابة في مساحة التخزين واحدة من أكثر العمليات تكلفة التي تقوم بها آلة إيثيريوم الافتراضية (EVM) (من حيث [الغاز](/developers/docs/gas/)). لذلك، من الجيد تقليلها، فحتى كتابة القيمة الحالية لها تكلفة عالية.

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

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

```python
    # التحقق من المتطلبات
    assert self._isApprovedOrOwner(_sender, _tokenId)
    # تطرح خطأ إذا كان `_to` هو العنوان الصفري
    assert _to != empty(address)
    # مسح الموافقة. تطرح خطأ إذا لم يكن `_from` هو المالك الحالي
    self._clearApproval(_from, _tokenId)
    # إزالة الرمز المميز غير القابل للاستبدال. تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
    self._removeTokenFrom(_from, _tokenId)
    # إضافة الرمز المميز غير القابل للاستبدال
    self._addTokenTo(_to, _tokenId)
    # تسجيل التحويل
    log IERC721.Transfer(sender=_from, receiver=_to, token_id=_tokenId)
```

لإصدار حدث في لغة Vyper، تستخدم عبارة `log` ([انظر هنا لمزيد من التفاصيل](https://vyper.readthedocs.io/en/latest/event-logging.html#event-logging)). نظرًا لأن الأحداث تنتمي إلى الواجهة المستوردة، فإننا نشير إليها باسم `IERC721.Transfer` ونمرر حقولها بواسطة الكلمة الرئيسية.
#### دوال التحويل

```python

### دوال التحويل ###

@external
@payable
def transferFrom(_from: address, _to: address, _tokenId: uint256):
    """
    @dev تطرح خطأ ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلاً مصرحًا له، أو العنوان
         المعتمد لهذا الرمز المميز غير القابل للاستبدال.
         تطرح خطأ إذا لم يكن `_from` هو المالك الحالي.
         تطرح خطأ إذا كان `_to` هو العنوان الصفري.
         تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
    @notice المتصل مسؤول عن التأكد من أن `_to` قادر على تلقي الرموز المميزة غير القابلة للاستبدال وإلا
            فقد تُفقد بشكل دائم.
    @param _from المالك الحالي للرمز المميز غير القابل للاستبدال.
    @param _to المالك الجديد.
    @param _tokenId الرمز المميز غير القابل للاستبدال المراد تحويله.
    """
    self._transferFrom(_from, _to, _tokenId, msg.sender)
```

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

الزخرفة `@payable` موجودة هنا لأن واجهة `IERC721` تعلن عن `transferFrom` و`safeTransferFrom` و`approve` كدوال قابلة للدفع (payable)، لذا يجب أن يتطابق العقد الذي ينفذ الواجهة مع هذه التوقيعات.

```python
@external
@payable
def safeTransferFrom(
        _from: address,
        _to: address,
        _tokenId: uint256,
        _data: Bytes[1024]=b""
    ):
    """
    @dev تحويل ملكية رمز مميز غير قابل للاستبدال (NFT) من عنوان إلى عنوان آخر.
         تطرح خطأ ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلاً مصرحًا له، أو
         العنوان المعتمد لهذا الرمز المميز غير القابل للاستبدال.
         تطرح خطأ إذا لم يكن `_from` هو المالك الحالي.
         تطرح خطأ إذا كان `_to` هو العنوان الصفري.
         تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
         إذا كان `_to` عقدًا ذكيًا، فإنها تستدعي `onERC721Received` على `_to` وتطرح خطأ إذا
         لم تكن القيمة المرجعة هي `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
    @param _from المالك الحالي للرمز المميز غير القابل للاستبدال.
    @param _to المالك الجديد.
    @param _tokenId الرمز المميز غير القابل للاستبدال المراد تحويله.
    @param _data بيانات إضافية بدون تنسيق محدد، يتم إرسالها في الاستدعاء إلى `_to`.
    """
    self._transferFrom(_from, _to, _tokenId, msg.sender)
```

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

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

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

```python
        returnValue: bytes4 = extcall ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
```

استدعِ العقد المستهدف لمعرفة ما إذا كان يمكنه تلقي رموز <span dir="ltr">ERC-721</span> المميزة. يتطلب الإصدار <span dir="ltr">0.4</span> من لغة Vyper تمييز الاستدعاءات للعقود الأخرى، لذلك يتم إضافة البادئة `extcall` إلى الاستدعاء.

```python
        # تطرح خطأ إذا كانت وجهة التحويل عبارة عن عقد لا ينفذ 'onERC721Received'
        assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4)
```

إذا كانت الوجهة عبارة عن عقد، ولكنه لا يقبل رموز <span dir="ltr">ERC-721</span> المميزة (أو قرر عدم قبول هذا التحويل المحدد)، فتراجع.

```python
@external
@payable
def approve(_approved: address, _tokenId: uint256):
    """
    @dev تعيين أو إعادة تأكيد العنوان المعتمد لرمز مميز غير قابل للاستبدال (NFT). يشير العنوان الصفري إلى عدم وجود عنوان معتمد.
         تطرح خطأ ما لم يكن `msg.sender` هو المالك الحالي للرمز المميز غير القابل للاستبدال، أو مشغلاً مصرحًا له للمالك الحالي.
         تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا. (ملاحظة: هذا غير مكتوب في EIP)
         تطرح خطأ إذا كان `_approved` هو المالك الحالي. (ملاحظة: هذا غير مكتوب في EIP)
    @param _approved العنوان المراد اعتماده لمعرف الرمز المميز غير القابل للاستبدال المحدد.
    @param _tokenId معرف الرمز المميز المراد اعتماده.
    """
    owner: address = self.idToOwner[_tokenId]
    # تطرح خطأ إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
    assert owner != empty(address)
    # تطرح خطأ إذا كان `_approved` هو المالك الحالي
    assert _approved != owner
```

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

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

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

```python
    # تعيين الموافقة
    self.idToApprovals[_tokenId] = _approved
    log IERC721.Approval(owner=owner, approved=_approved, token_id=_tokenId)


@external
def setApprovalForAll(_operator: address, _approved: bool):
    """
    @dev تمكين أو تعطيل الموافقة لطرف ثالث ("مشغل") لإدارة جميع
         أصول `msg.sender`. كما أنها تصدر حدث ApprovalForAll.
         تطرح خطأ إذا كان `_operator` هو `msg.sender`. (ملاحظة: هذا غير مكتوب في EIP)
    @notice يعمل هذا حتى إذا لم يكن المرسل يمتلك أي رموز مميزة في ذلك الوقت.
    @param _operator العنوان المراد إضافته إلى مجموعة المشغلين المصرح لهم.
    @param _approved True إذا كان المشغل معتمدًا، و false لإلغاء الموافقة.
    """
    # تطرح خطأ إذا كان `_operator` هو `msg.sender`
    assert _operator != msg.sender
    self.ownerToOperators[msg.sender][_operator] = _approved
    log IERC721.ApprovalForAll(owner=msg.sender, operator=_operator, approved=_approved)
```
#### سك رموز مميزة جديدة وتدمير الرموز الحالية {#mint-burn}

الحساب الذي أنشأ العقد هو `minter`، وهو المستخدم المتميز المخول لسك
رموز مميزة غير قابلة للاستبدال (<span dir="ltr">NFTs</span>) جديدة. ومع ذلك، حتى هو غير مسموح له بحرق الرموز المميزة الحالية. فقط المالك، أو كيان
مفوض من قبل المالك، يمكنه القيام بذلك.

```python
### دوال السك والحرق ###

@external
def mint(_to: address, _tokenId: uint256) -> bool:
```

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

```python
    """
    @dev دالة لسك الرموز المميزة
         يتراجع إذا لم يكن `msg.sender` هو الساك.
         يتراجع إذا كان `_to` هو العنوان الصفري.
         يتراجع إذا كان `_tokenId` مملوكًا لشخص ما.
    @param _to العنوان الذي سيتلقى الرموز المميزة المسكوكة.
    @param _tokenId معرف الرمز المميز المراد سكه.
    @return قيمة منطقية تشير إلى ما إذا كانت العملية ناجحة.
    """
    # يتراجع إذا لم يكن `msg.sender` هو الساك
    assert msg.sender == self.minter
```

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

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

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

```python

@external
def burn(_tokenId: uint256):
    """
    @dev يحرق رمز ERC-721 مميز محدد.
         يتراجع ما لم يكن `msg.sender` هو المالك الحالي، أو مشغلًا مصرحًا له، أو العنوان
         المعتمد لهذا الرمز المميز غير القابل للاستبدال.
         يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا.
    @param _tokenId uint256 معرف رمز ERC-721 المميز المراد حرقه.
    """
    # التحقق من المتطلبات
    assert self._isApprovedOrOwner(msg.sender, _tokenId)
    owner: address = self.idToOwner[_tokenId]
    # يتراجع إذا لم يكن `_tokenId` رمزًا مميزًا غير قابل للاستبدال صالحًا
    assert owner != ZERO_ADDRESS
    self._clearApproval(owner, _tokenId)
    self._removeTokenFrom(owner, _tokenId)
    log Transfer(owner, ZERO_ADDRESS, _tokenId)
```

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

## استخدام هذا العقد {#using-contract}

على النقيض من لغة Solidity، لا تحتوي لغة Vyper على الوراثة (inheritance). هذا خيار تصميمي متعمد لجعل
التعليمات البرمجية أكثر وضوحًا وبالتالي أسهل في التأمين. لذلك لإنشاء عقد <span dir="ltr">ERC-721</span> الخاص بك بلغة Vyper، فإنك تأخذ [هذا
العقد](https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy) وتقوم بتعديله
لتنفيذ منطق الأعمال الذي تريده.

## الخاتمة {#conclusion}

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

- لاستلام الرموز المميزة <span dir="ltr">ERC-721</span> بتحويل آمن، يجب أن تنفذ العقود واجهة `ERC721Receiver`.
- حتى إذا كنت تستخدم التحويل الآمن، فلا يزال من الممكن أن تعلق الرموز المميزة إذا أرسلتها إلى عنوان يكون مفتاحه الخاص
  غير معروف.
- عندما تكون هناك مشكلة في عملية ما، فمن الجيد `revert` الاستدعاء (التراجع عنه)، بدلاً من مجرد إرجاع
  قيمة فشل.
- توجد الرموز المميزة <span dir="ltr">ERC-721</span> عندما يكون لها مالك.
- هناك ثلاث طرق لتكون مخولاً لتحويل رمز مميز غير قابل للاستبدال (<span dir="ltr">NFT</span>). يمكنك أن تكون المالك، أو أن تكون معتمدًا لرمز مميز محدد،
  أو أن تكون مشغلاً لجميع الرموز المميزة للمالك.
- الأحداث الماضية مرئية فقط خارج سلسلة الكتل. لا يمكن للتعليمات البرمجية التي تعمل داخل سلسلة الكتل عرضها.

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

[انظر هنا للمزيد من أعمالي](https://cryptodocguy.pro/).
