---
title: "Vyper ERC-721 কন্ট্রাক্ট ওয়াকথ্রু"
description: "রিউয়া নাকামুরার ERC-721 কন্ট্রাক্ট এবং এটি কীভাবে কাজ করে"
author: "ওরি পোমেরান্টজ"
lang: bn
tags: ["Vyper", "erc-721", "Python"]
skill: beginner
breadcrumb: Vyper ERC-721
published: 2021-04-01
---

## ভূমিকা {#introduction}

[ERC-721](/developers/docs/standards/tokens/erc-721/) স্ট্যান্ডার্ডটি নন-ফাঞ্জিবল টোকেন (NFT)-এর মালিকানা ধরে রাখতে ব্যবহৃত হয়।
[ERC-20](/developers/docs/standards/tokens/erc-20/) টোকেনগুলো একটি পণ্যের মতো আচরণ করে, কারণ প্রতিটি আলাদা টোকেনের মধ্যে কোনো পার্থক্য নেই।
এর বিপরীতে, ERC-721 টোকেনগুলো এমন সম্পদের জন্য ডিজাইন করা হয়েছে যা একই রকম হলেও হুবহু এক নয়, যেমন বিভিন্ন [বিড়ালের কার্টুন](https://www.cryptokitties.co/)
বা বিভিন্ন রিয়েল এস্টেটের মালিকানা।

এই নিবন্ধে আমরা [রিউয়া নাকামুরার ERC-721 কন্ট্রাক্ট](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
```

Python-এর মতো Vyper-এও মন্তব্যগুলো একটি হ্যাশ (`ethereum.ercs`) দিয়ে শুরু হয় এবং লাইনের শেষ পর্যন্ত চলতে থাকে। যেসব মন্তব্যে
`@<keyword>` থাকে, সেগুলো [NatSpec](https://vyper.readthedocs.io/en/latest/natspec.html) ব্যবহার করে মানুষের পড়ার উপযোগী
ডকুমেন্টেশন তৈরি করতে ব্যবহৃত হয়।

```python
from vyper.interfaces import ERC721

implements: ERC721
```

ERC-721 ইন্টারফেসটি Vyper ভাষার ভেতরেই তৈরি করা আছে।
[আপনি এখানে কোডের সংজ্ঞা দেখতে পারেন](https://github.com/vyperlang/vyper/blob/master/vyper/builtin_interfaces/ERC721.py)।
ইন্টারফেসের সংজ্ঞাটি Vyper-এর পরিবর্তে Python-এ লেখা হয়েছে, কারণ ইন্টারফেসগুলো শুধু
ব্লকচেইনের ভেতরেই ব্যবহৃত হয় না, বরং কোনো এক্সটার্নাল ক্লায়েন্ট থেকে ব্লকচেইনে ট্রানজ্যাকশন পাঠানোর সময়ও ব্যবহৃত হয়, যা
Python-এ লেখা হতে পারে।

প্রথম লাইনটি ইন্টারফেস ইমপোর্ট করে এবং দ্বিতীয় লাইনটি নির্দিষ্ট করে যে আমরা এটি এখানে ইমপ্লিমেন্ট করছি।

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

```python
#pragma version >0.3.10
```
### ERC721Receiver ইন্টারফেস

```python
# safeTransferFrom() দ্বারা কল করা কন্ট্রাক্টের জন্য ইন্টারফেস
interface ERC721Receiver:
    def onERC721Received(
```

ERC-721 দুই ধরনের হস্তান্তর সমর্থন করে:

- `transferFrom`, যা প্রেরককে যেকোনো গন্তব্য ঠিকানা নির্দিষ্ট করতে দেয় এবং হস্তান্তরের দায়িত্ব প্রেরকের ওপর অর্পণ করে। এর মানে হলো আপনি একটি অবৈধ ঠিকানায় হস্তান্তর করতে পারেন, সেক্ষেত্রে NFT চিরতরে হারিয়ে যায়।
- `safeTransferFrom`, যা গন্তব্য ঠিকানাটি একটি কন্ট্রাক্ট কিনা তা চেক করে। যদি তাই হয়, তবে ERC-721 কন্ট্রাক্ট গ্রহণকারী কন্ট্রাক্টকে জিজ্ঞাসা করে যে এটি NFT গ্রহণ করতে চায় কিনা।

`safeTransferFrom` রিকোয়েস্টের উত্তর দিতে একটি গ্রহণকারী কন্ট্রাক্টকে `ERC721Receiver` ইমপ্লিমেন্ট করতে হবে।

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

`_from` ঠিকানাটি হলো টোকেনের বর্তমান মালিক। `_operator` ঠিকানাটি হলো সেটি যা হস্তান্তরের রিকোয়েস্ট করেছে (অ্যালাউন্সের কারণে এই দুটি একই নাও হতে পারে)। প্রথা অনুযায়ী, এই কন্ট্রাক্টের বেশিরভাগ ফাংশন প্যারামিটার একটি আন্ডারস্কোর (`_`) দিয়ে শুরু হয়।

```python
            _tokenId: uint256,
```

ERC-721 টোকেন আইডিগুলো 256 বিটের হয়। সাধারণত টোকেনটি যা উপস্থাপন করে তার একটি বিবরণ হ্যাশিং করে এগুলো তৈরি করা হয়।

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

রিকোয়েস্টে 1024 বাইট পর্যন্ত ইউজার ডেটা থাকতে পারে।

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

কোনো কন্ট্রাক্ট ভুলবশত কোনো হস্তান্তর গ্রহণ করার ঘটনা রোধ করতে রিটার্ন ভ্যালু কোনো বুলিয়ান নয়, বরং একটি নির্দিষ্ট চার-বাইট ভ্যালু, যা `onERC721Received`-এর ফাংশন সিলেক্টর। ফাংশনটি `nonpayable` কারণ একটি গ্রহণকারী কন্ট্রাক্ট টোকেন গ্রহণ করার সময় তার নিজস্ব স্টেট পরিবর্তন করতে পারে।

### ইভেন্ট

ব্লকচেইনের বাইরের ইউজার এবং সার্ভারগুলোকে ইভেন্ট সম্পর্কে জানাতে [ইভেন্ট](/developers/docs/smart-contracts/anatomy/#events-and-logs)
এমিট করা হয়। মনে রাখবেন যে ইভেন্টের বিষয়বস্তু ব্লকচেইনে থাকা কন্ট্রাক্টগুলোর জন্য উপলব্ধ নয়। তিনটি ERC-721 ইভেন্ট আমাদের ইমপোর্ট করা `IERC721` ইন্টারফেস দ্বারা সংজ্ঞায়িত করা হয়েছে, তাই এই কন্ট্রাক্টটি নিজে থেকে সেগুলো ডিক্লেয়ার করে না; এটি `log IERC721.<Event>(...)` দিয়ে সেগুলো এমিট করে, যেমনটি আমরা নিচের হস্তান্তর ফাংশনগুলোতে দেখতে পাব।

`Transfer` (`sender`, `receiver`, `token_id`) একটি NFT-এর মালিকানা পরিবর্তনের রিপোর্ট করে। এটি ERC-20 Transfer ইভেন্টের মতোই, তবে আমরা পরিমাণের পরিবর্তে একটি `token_id` রিপোর্ট করি। জিরো অ্যাড্রেসের কোনো মালিক নেই, তাই প্রথা অনুযায়ী আমরা টোকেন তৈরি এবং ধ্বংসের রিপোর্ট করতে এটি ব্যবহার করি। এর একটি ব্যতিক্রম হলো কন্ট্রাক্ট তৈরি, যে সময়ে `Transfer` এমিট না করেই যেকোনো সংখ্যক NFT তৈরি এবং অ্যাসাইন করা যেতে পারে।

একটি ERC-721 অনুমোদন একটি ERC-20 অ্যালাউন্সের মতোই: একটি নির্দিষ্ট ঠিকানাকে একটি নির্দিষ্ট টোকেন হস্তান্তর করার অনুমতি দেওয়া হয়, এবং যখনই সেই অনুমোদিত ঠিকানাটি সেট বা পুনরায় নিশ্চিত করা হয় তখন `Approval` (`owner`, `approved`, `token_id`) এমিট করা হয়। এটি কন্ট্রাক্টগুলোকে টোকেন গ্রহণ করার সময় সাড়া দেওয়ার একটি মেকানিজম দেয়। কন্ট্রাক্টগুলো ইভেন্ট শুনতে পারে না, তাই আপনি যদি শুধু তাদের কাছে টোকেন হস্তান্তর করেন তবে তারা এটি সম্পর্কে "জানতে" পারে না। এইভাবে মালিক প্রথমে একটি অনুমোদন সাবমিট করে এবং তারপর কন্ট্রাক্টে একটি রিকোয়েস্ট পাঠায়: "আমি আপনাকে টোকেন X হস্তান্তর করার অনুমোদন দিয়েছি, অনুগ্রহ করে ... করুন"। ERC-721 স্ট্যান্ডার্ডকে ERC-20 স্ট্যান্ডার্ডের মতো করার জন্য এটি একটি ডিজাইনগত পছন্দ। যেহেতু ERC-721 টোকেনগুলো ফাঞ্জিবল নয়, তাই একটি কন্ট্রাক্ট টোকেনের মালিকানা দেখেও শনাক্ত করতে পারে যে এটি একটি নির্দিষ্ট টোকেন পেয়েছে।

সবশেষে, যখন কোনো মালিকের জন্য একজন _অপারেটর_ এনাবল বা ডিজেবল করা হয় তখন `ApprovalForAll` (`owner`, `operator`, `approved`) এমিট করা হয়। পাওয়ার অফ অ্যাটর্নির মতো এমন একজন অপারেটর থাকা মাঝে মাঝে দরকারী যে একটি অ্যাকাউন্টের নির্দিষ্ট ধরনের (যেগুলো একটি নির্দিষ্ট কন্ট্রাক্ট দ্বারা পরিচালিত হয়) সমস্ত টোকেন পরিচালনা করতে পারে। উদাহরণস্বরূপ, আমি এমন একটি কন্ট্রাক্টকে এই ক্ষমতা দিতে চাইতে পারি যা চেক করে যে আমি ছয় মাস ধরে এর সাথে যোগাযোগ করিনি কিনা, এবং যদি তাই হয় তবে আমার সম্পদগুলো আমার উত্তরাধিকারীদের মধ্যে বিতরণ করে (যদি তাদের মধ্যে কেউ এটি চায়, ট্রানজ্যাকশন দ্বারা কল করা ছাড়া কন্ট্রাক্টগুলো কিছুই করতে পারে না)। ERC-20-তে আমরা শুধু একটি ইনহেরিটেন্স কন্ট্রাক্টকে একটি উচ্চ অ্যালাউন্স দিতে পারি, কিন্তু ERC-721-এর জন্য এটি কাজ করে না কারণ টোকেনগুলো ফাঞ্জিবল নয়। এটি তারই সমতুল্য। `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` ইভেন্টগুলো দেখা। আমরা এই ভেরিয়েবলটি ব্যবহার করে জানতে পারি কখন আমাদের কাছে সমস্ত NFT আছে এবং সময়ের আরও পেছনে ফিরে দেখার প্রয়োজন নেই।

মনে রাখবেন যে এই অ্যালগরিদমটি শুধুমাত্র ইউজার ইন্টারফেস এবং এক্সটার্নাল সার্ভারগুলোর জন্য কাজ করে। ব্লকচেইনে চলা কোড অতীতের ইভেন্টগুলো পড়তে পারে না।

```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,
    # ERC721-এর ERC165 ইন্টারফেস আইডি
    0x80ac58cd,
]
```

[ERC-165](https://eips.ethereum.org/EIPS/eip-165) একটি কন্ট্রাক্টের জন্য একটি মেকানিজম নির্দিষ্ট করে যা প্রকাশ করে যে অ্যাপ্লিকেশনগুলো কীভাবে এর সাথে যোগাযোগ করতে পারে, এটি কোন ERC-গুলো মেনে চলে। `SUPPORTED_INTERFACES` হলো দুটি চার-বাইট ইন্টারফেস আইডির একটি ধ্রুবক তালিকা যা এই কন্ট্রাক্টটি মেনে চলে: ERC-165 নিজে এবং ERC-721।

### ফাংশন {#functions}

এগুলো হলো সেই ফাংশন যা আসলে ERC-721 ইমপ্লিমেন্ট করে।

#### কনস্ট্রাক্টর

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

Python-এর মতো Vyper-এও কনস্ট্রাক্টর ফাংশনটিকে `__init__` বলা হয়। এটি `@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
```

ফাংশন সংজ্ঞার আগে থাকা এই কিওয়ার্ডগুলো যা একটি অ্যাট সাইন (`@`) দিয়ে শুরু হয় সেগুলোকে _ডেকোরেশন_ বলা হয়। এগুলো নির্দিষ্ট করে যে কোন পরিস্থিতিতে একটি ফাংশন কল করা যেতে পারে।

- `@view` নির্দিষ্ট করে যে এই ফাংশনটি একটি ভিউ।
- `@external` নির্দিষ্ট করে যে এই নির্দিষ্ট ফাংশনটি ট্রানজ্যাকশন এবং অন্যান্য কন্ট্রাক্ট দ্বারা কল করা যেতে পারে।

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

Python-এর বিপরীতে, Vyper হলো একটি [স্ট্যাটিক টাইপড ভাষা](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
```

যদি `interface_id` `SUPPORTED_INTERFACES` তালিকার ইন্টারফেস আইডিগুলোর মধ্যে একটি হয় তবে `True` রিটার্ন করে।

```python
### ভিউ ফাংশনগুলো ###
```

এগুলো হলো সেই ভিউ ফাংশন যা ইউজার এবং অন্যান্য কন্ট্রাক্টের কাছে টোকেন সম্পর্কে তথ্য উপলব্ধ করে।

```python
@view
@external
def balanceOf(_owner: address) -> uint256:
    """
    @dev `_owner`-এর মালিকানাধীন NFT-এর সংখ্যা রিটার্ন করে।
         `_owner` জিরো অ্যাড্রেস হলে থ্রো করে। জিরো অ্যাড্রেসে অ্যাসাইন করা NFT-গুলোকে অবৈধ বলে মনে করা হয়।
    @param _owner যে ঠিকানার জন্য ব্যালেন্স কোয়েরি করতে হবে।
    """
    assert _owner != empty(address)
```

এই লাইনটি [অ্যাসার্ট](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` একটি বৈধ NFT না হলে থ্রো করে।
    @param _tokenId একটি NFT-এর আইডেন্টিফায়ার।
    """
    owner: address = self.idToOwner[_tokenId]
    # `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
    assert owner != empty(address)
    return owner
```

ইথেরিয়াম ভার্চুয়াল মেশিনে (EVM) এমন যেকোনো স্টোরেজ যার মধ্যে কোনো ভ্যালু স্টোর করা নেই তা শূন্য হয়। যদি `_tokenId`-এ কোনো টোকেন না থাকে তবে `self.idToOwner[_tokenId]`-এর ভ্যালু শূন্য হয়। সেক্ষেত্রে ফাংশনটি রিভার্ট হয়。

```python
@view
@external
def getApproved(_tokenId: uint256) -> address:
    """
    @dev একটি একক NFT-এর জন্য অনুমোদিত ঠিকানা পান।
         `_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
    @param _tokenId যে NFT-এর অনুমোদনের কোয়েরি করতে হবে তার আইডি।
    """
    # `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
    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 যে ঠিকানার মালিকানায় NFT-গুলো রয়েছে।
    @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` বর্তমান মালিক, একজন অনুমোদিত অপারেটর, অথবা এই NFT-এর জন্য অনুমোদিত
         ঠিকানা না হলে থ্রো করে। (দ্রষ্টব্য: প্রাইভেট ফাংশনে `msg.sender` অনুমোদিত নয় তাই `_sender` পাস করুন।)
         `_to` জিরো অ্যাড্রেস হলে থ্রো করে।
         `_from` বর্তমান মালিক না হলে থ্রো করে।
         `_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
    """
```

আমাদের এই ইন্টারনাল ফাংশনটি রয়েছে কারণ টোকেন হস্তান্তর করার দুটি উপায় রয়েছে (নিয়মিত এবং নিরাপদ), তবে অডিটিং সহজ করার জন্য আমরা কোডে শুধুমাত্র একটি স্থানে এটি করতে চাই।

```python
    # প্রয়োজনীয়তাগুলো চেক করুন
    assert self._isApprovedOrOwner(_sender, _tokenId)
    # `_to` জিরো অ্যাড্রেস হলে থ্রো করে
    assert _to != empty(address)
    # অনুমোদন ক্লিয়ার করুন। `_from` বর্তমান মালিক না হলে থ্রো করে
    self._clearApproval(_from, _tokenId)
    # NFT সরান। `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
    self._removeTokenFrom(_from, _tokenId)
    # NFT যোগ করুন
    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` বর্তমান মালিক, একজন অনুমোদিত অপারেটর, অথবা এই NFT-এর জন্য অনুমোদিত
         ঠিকানা না হলে থ্রো করে।
         `_from` বর্তমান মালিক না হলে থ্রো করে।
         `_to` জিরো অ্যাড্রেস হলে থ্রো করে।
         `_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
    @notice কলারকে নিশ্চিত করতে হবে যে `_to` NFT গ্রহণ করতে সক্ষম, অন্যথায়
            সেগুলো স্থায়ীভাবে হারিয়ে যেতে পারে।
    @param _from NFT-এর বর্তমান মালিক।
    @param _to নতুন মালিক।
    @param _tokenId হস্তান্তর করার জন্য NFT।
    """
    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` বর্তমান মালিক, একজন অনুমোদিত অপারেটর, অথবা এই NFT-এর জন্য
         অনুমোদিত ঠিকানা না হলে থ্রো করে।
         `_from` বর্তমান মালিক না হলে থ্রো করে।
         `_to` জিরো অ্যাড্রেস হলে থ্রো করে।
         `_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
         যদি `_to` একটি স্মার্ট কন্ট্রাক্ট হয়, তবে এটি `_to`-তে `onERC721Received` কল করে এবং রিটার্ন ভ্যালু
         `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` না হলে থ্রো করে।
    @param _from NFT-এর বর্তমান মালিক।
    @param _to নতুন মালিক।
    @param _tokenId হস্তান্তর করার জন্য NFT।
    @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)
```

টার্গেট কন্ট্রাক্টটি ERC-721 টোকেন গ্রহণ করতে পারে কিনা তা দেখতে সেটিকে কল করুন। Vyper 0.4-এ অন্যান্য কন্ট্রাক্টে কলগুলোকে চিহ্নিত করা প্রয়োজন, তাই কলের আগে `extcall` যুক্ত করা হয়েছে।

```python
        # হস্তান্তরের গন্তব্য যদি এমন কোনো কন্ট্রাক্ট হয় যা 'onERC721Received' ইমপ্লিমেন্ট করে না, তবে থ্রো করে
        assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4)
```

যদি গন্তব্যটি একটি কন্ট্রাক্ট হয়, কিন্তু এমন একটি যা ERC-721 টোকেন গ্রহণ করে না (অথবা যা এই নির্দিষ্ট হস্তান্তরটি গ্রহণ না করার সিদ্ধান্ত নিয়েছে), তবে রিভার্ট করুন।

```python
@external
@payable
def approve(_approved: address, _tokenId: uint256):
    """
    @dev একটি NFT-এর জন্য অনুমোদিত ঠিকানা সেট বা পুনরায় নিশ্চিত করে। জিরো অ্যাড্রেস নির্দেশ করে যে কোনো অনুমোদিত ঠিকানা নেই।
         `msg.sender` বর্তমান NFT মালিক, অথবা বর্তমান মালিকের একজন অনুমোদিত অপারেটর না হলে থ্রো করে।
         `_tokenId` একটি বৈধ NFT না হলে থ্রো করে। (নোট: এটি EIP-তে লেখা নেই)
         `_approved` বর্তমান মালিক হলে থ্রো করে। (নোট: এটি EIP-তে লেখা নেই)
    @param _approved প্রদত্ত NFT আইডির জন্য অনুমোদিত ঠিকানা।
    @param _tokenId অনুমোদিত করার জন্য টোকেনের আইডি।
    """
    owner: address = self.idToOwner[_tokenId]
    # `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
    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`, সুপার ইউজার যা নতুন
NFT মিন্ট করার জন্য অনুমোদিত। তবে, এমনকি এটিকেও বিদ্যমান টোকেন পোড়ানোর অনুমতি দেওয়া হয় না। শুধুমাত্র মালিক, বা
মালিক দ্বারা অনুমোদিত কোনো এনটিটি এটি করতে পারে।

```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
```

শুধুমাত্র মিন্টার (যে অ্যাকাউন্টটি ERC-721 কন্ট্রাক্ট তৈরি করেছে) নতুন টোকেন মিন্ট করতে পারে। ভবিষ্যতে যদি আমরা মিন্টারের পরিচয় পরিবর্তন করতে চাই তবে এটি একটি
সমস্যা হতে পারে। একটি প্রোডাকশন কন্ট্রাক্টে আপনি সম্ভবত এমন একটি ফাংশন চাইবেন যা মিন্টারকে অন্য কারও কাছে
মিন্টারের সুবিধাগুলো হস্তান্তর করার অনুমতি দেয়।

```python
    # `_to` জিরো অ্যাড্রেস হলে থ্রো করে
    assert _to != ZERO_ADDRESS
    # NFT যোগ করুন। `_tokenId` কারও মালিকানাধীন হলে থ্রো করে
    self._addTokenTo(_to, _tokenId)
    log Transfer(ZERO_ADDRESS, _to, _tokenId)
    return True
```

প্রথা অনুযায়ী, নতুন টোকেন মিন্টিং জিরো অ্যাড্রেস থেকে একটি হস্তান্তর হিসেবে গণ্য হয়।

```python

@external
def burn(_tokenId: uint256):
    """
    @dev একটি নির্দিষ্ট ERC-721 টোকেন পোড়ানো হয়।
         `msg.sender` বর্তমান মালিক, একজন অনুমোদিত অপারেটর, অথবা এই NFT-এর জন্য অনুমোদিত
         ঠিকানা না হলে থ্রো করে।
         `_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
    @param _tokenId পোড়ানোর জন্য ERC-721 টোকেনের uint256 আইডি。
    """
    # প্রয়োজনীয়তাগুলো চেক করুন
    assert self._isApprovedOrOwner(msg.sender, _tokenId)
    owner: address = self.idToOwner[_tokenId]
    # `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
    assert owner != ZERO_ADDRESS
    self._clearApproval(owner, _tokenId)
    self._removeTokenFrom(owner, _tokenId)
    log Transfer(owner, ZERO_ADDRESS, _tokenId)
```

যে কাউকে টোকেন হস্তান্তর করার অনুমতি দেওয়া হয়, তাকে এটি পোড়ানোরও অনুমতি দেওয়া হয়। যদিও পোড়ানো
জিরো অ্যাড্রেসে হস্তান্তর করার সমতুল্য বলে মনে হয়, জিরো অ্যাড্রেস আসলে টোকেনটি গ্রহণ করে না। এটি আমাদের
টোকেনের জন্য ব্যবহৃত সমস্ত স্টোরেজ খালি করার অনুমতি দেয়, যা ট্রানজ্যাকশনের গ্যাস খরচ কমাতে পারে।

## এই কন্ট্রাক্ট ব্যবহার করা {#using-contract}

Solidity-এর বিপরীতে, Vyper-এ ইনহেরিটেন্স নেই। কোডটিকে আরও পরিষ্কার এবং তাই সুরক্ষিত করা সহজ করার জন্য এটি একটি ইচ্ছাকৃত
ডিজাইনগত সিদ্ধান্ত। তাই আপনার নিজস্ব Vyper ERC-721 কন্ট্রাক্ট তৈরি করতে আপনি [এই
কন্ট্রাক্টটি](https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy) নিতে পারেন এবং আপনি যে ব্যবসায়িক লজিক চান তা ইমপ্লিমেন্ট করতে এটি
পরিবর্তন করতে পারেন।

## উপসংহার {#conclusion}

পর্যালোচনার জন্য, এখানে এই কন্ট্রাক্টের সবচেয়ে গুরুত্বপূর্ণ কিছু ধারণা দেওয়া হলো:

- নিরাপদ হস্তান্তরের মাধ্যমে ERC-721 টোকেন গ্রহণ করতে, কন্ট্রাক্টগুলোকে `ERC721Receiver` ইন্টারফেস ইমপ্লিমেন্ট করতে হবে।
- এমনকি আপনি নিরাপদ হস্তান্তর ব্যবহার করলেও, টোকেনগুলো আটকে যেতে পারে যদি আপনি সেগুলোকে এমন কোনো ঠিকানায় পাঠান যার প্রাইভেট কী
  অজানা।
- যখন কোনো অপারেশনে সমস্যা হয় তখন শুধু একটি ব্যর্থতার মান রিটার্ন করার পরিবর্তে কলটি `revert` করা একটি ভালো ধারণা।
- ERC-721 টোকেনগুলোর অস্তিত্ব থাকে যখন সেগুলোর একজন মালিক থাকে।
- একটি NFT হস্তান্তর করার জন্য অনুমোদিত হওয়ার তিনটি উপায় রয়েছে। আপনি মালিক হতে পারেন, একটি নির্দিষ্ট টোকেনের জন্য অনুমোদিত হতে পারেন,
  অথবা মালিকের সমস্ত টোকেনের জন্য একজন অপারেটর হতে পারেন।
- অতীতের ইভেন্টগুলো শুধুমাত্র ব্লকচেইনের বাইরে দৃশ্যমান। ব্লকচেইনের ভেতরে চলা কোড সেগুলো দেখতে পারে না।

এখন যান এবং সুরক্ষিত Vyper কন্ট্রাক্ট ইমপ্লিমেন্ট করুন।

[আমার আরও কাজের জন্য এখানে দেখুন](https://cryptodocguy.pro/)।
