Vyper ERC-721 কন্ট্রাক্ট ওয়াকথ্রু
ভূমিকা
ERC-721 স্ট্যান্ডার্ডটি নন-ফাঞ্জিবল টোকেন (NFT)-এর মালিকানা ধরে রাখতে ব্যবহৃত হয়। ERC-20 টোকেনগুলো একটি পণ্যের মতো আচরণ করে, কারণ প্রতিটি আলাদা টোকেনের মধ্যে কোনো পার্থক্য নেই। এর বিপরীতে, ERC-721 টোকেনগুলো এমন সম্পদের জন্য ডিজাইন করা হয়েছে যা একই রকম হলেও হুবহু এক নয়, যেমন বিভিন্ন বিড়ালের কার্টুন (opens in a new tab) বা বিভিন্ন রিয়েল এস্টেটের মালিকানা।
এই নিবন্ধে আমরা রিউয়া নাকামুরার ERC-721 কন্ট্রাক্ট (opens in a new tab) বিশ্লেষণ করব। এই কন্ট্রাক্টটি Vyper (opens in a new tab)-এ লেখা হয়েছে, যা একটি Python-এর মতো কন্ট্রাক্ট ভাষা এবং এটি এমনভাবে ডিজাইন করা হয়েছে যাতে Solidity-এর তুলনায় এতে অনিরাপদ কোড লেখা কঠিন হয়।
কন্ট্রাক্ট
# @dev ERC-721 নন-ফাঞ্জিবল টোকেন স্ট্যান্ডার্ডের ইমপ্লিমেন্টেশন।
# @author Ryuya Nakamura (@nrryuya)
# এখান থেকে পরিমার্জিত: https://github.com/vyperlang/vyper/blob/de74722bf2d8718cca46902be165f9fe0e3641dd/examples/tokens/ERC721.vy
Python-এর মতো Vyper-এও মন্তব্যগুলো একটি হ্যাশ (#) দিয়ে শুরু হয় এবং লাইনের শেষ পর্যন্ত চলতে থাকে। যেসব মন্তব্যে
@<keyword> থাকে, সেগুলো NatSpec (opens in a new tab) ব্যবহার করে মানুষের পড়ার উপযোগী
ডকুমেন্টেশন তৈরি করতে ব্যবহৃত হয়।
from vyper.interfaces import ERC721
implements: ERC721
ERC-721 ইন্টারফেসটি Vyper ভাষার ভেতরেই তৈরি করা আছে। আপনি এখানে কোডের সংজ্ঞা দেখতে পারেন (opens in a new tab)। ইন্টারফেসের সংজ্ঞাটি Vyper-এর পরিবর্তে Python-এ লেখা হয়েছে, কারণ ইন্টারফেসগুলো শুধু ব্লকচেইনের ভেতরেই ব্যবহৃত হয় না, বরং কোনো এক্সটার্নাল ক্লায়েন্ট থেকে ব্লকচেইনে ট্রানজ্যাকশন পাঠানোর সময়ও ব্যবহৃত হয়, যা Python-এ লেখা হতে পারে।
প্রথম লাইনটি ইন্টারফেস ইমপোর্ট করে এবং দ্বিতীয় লাইনটি নির্দিষ্ট করে যে আমরা এটি এখানে ইমপ্লিমেন্ট করছি।
ERC721Receiver ইন্টারফেস
# safeTransferFrom() দ্বারা কল করা কন্ট্রাক্ট এর জন্য ইন্টারফেস
interface ERC721Receiver:
def onERC721Received(
ERC-721 দুই ধরনের হস্তান্তর সমর্থন করে:
transferFrom, যা প্রেরককে যেকোনো গন্তব্য ঠিকানা নির্দিষ্ট করতে দেয় এবং হস্তান্তরের দায়িত্ব প্রেরকের ওপরই রাখে। এর মানে হলো আপনি একটি অবৈধ ঠিকানায় হস্তান্তর করতে পারেন, সেক্ষেত্রে NFT চিরতরে হারিয়ে যাবে।safeTransferFrom, যা গন্তব্য ঠিকানাটি কোনো কন্ট্রাক্ট কি না তা পরীক্ষা করে। যদি তা-ই হয়, তবে ERC-721 কন্ট্রাক্ট গ্রহণকারী কন্ট্রাক্টকে জিজ্ঞাসা করে যে সে NFT গ্রহণ করতে চায় কি না।
safeTransferFrom রিকোয়েস্টের উত্তর দিতে একটি গ্রহণকারী কন্ট্রাক্টকে অবশ্যই ERC721Receiver ইমপ্লিমেন্ট করতে হবে।
_operator: address,
_from: address,
_from ঠিকানাটি হলো টোকেনের বর্তমান মালিক। _operator ঠিকানাটি হলো সেটি যা
হস্তান্তরের অনুরোধ করেছে (অ্যালাউন্স থাকার কারণে এই দুটি একই নাও হতে পারে)।
_tokenId: uint256,
ERC-721 টোকেন আইডিগুলো 256 বিটের হয়। সাধারণত টোকেনটি যা উপস্থাপন করে তার একটি বিবরণ হ্যাশিং করার মাধ্যমে এগুলো তৈরি করা হয়।
_data: Bytes[1024]
রিকোয়েস্টে 1024 বাইট পর্যন্ত ইউজার ডেটা থাকতে পারে।
) -> bytes32: view
কোনো কন্ট্রাক্ট যাতে ভুলবশত কোনো হস্তান্তর গ্রহণ না করে, তা প্রতিরোধ করতে রিটার্ন ভ্যালু কোনো বুলিয়ান নয়, বরং একটি নির্দিষ্ট মানসহ 256 বিট হয়।
এই ফাংশনটি একটি view, যার মানে হলো এটি ব্লকচেইনের স্টেট পড়তে পারে, কিন্তু তা পরিবর্তন করতে পারে না।
ইভেন্ট
ব্লকচেইনের বাইরের ব্যবহারকারী এবং সার্ভারগুলোকে ইভেন্ট সম্পর্কে জানাতে ইভেন্ট এমিট করা হয়। মনে রাখবেন যে ইভেন্টের বিষয়বস্তু ব্লকচেইনের কন্ট্রাক্টগুলোর জন্য উপলব্ধ নয়।
# @dev যেকোনো প্রক্রিয়ায় কোনো NFT-এর মালিকানা পরিবর্তন হলে এটি ইমিট হয়। এই ইভেন্ট তখন ইমিট হয় যখন NFT-গুলো
# তৈরি করা হয় (`from` == 0) এবং ধ্বংস করা হয় (`to` == 0)। ব্যতিক্রম: কন্ট্রাক্ট তৈরির সময়, যেকোনো
# সংখ্যক NFT তৈরি এবং অ্যাসাইন করা যেতে পারে Transfer ইমিট না করেই। যেকোনো হস্তান্তরের সময়,
# সেই NFT-এর জন্য অনুমোদিত ঠিকানা (যদি থাকে) রিসেট করে none করা হয়।
# @param _from NFT-এর প্রেরক (যদি ঠিকানা জিরো অ্যাড্রেস হয় তবে এটি টোকেন তৈরি নির্দেশ করে)।
# @param _to NFT-এর প্রাপক (যদি ঠিকানা জিরো অ্যাড্রেস হয় তবে এটি টোকেন ধ্বংস নির্দেশ করে)।
# @param _tokenId যে NFT-টি হস্তান্তর করা হয়েছে।
event Transfer:
sender: indexed(address)
receiver: indexed(address)
tokenId: indexed(uint256)
এটি ERC-20 হস্তান্তর ইভেন্টের মতোই, তবে পার্থক্য হলো আমরা পরিমাণের পরিবর্তে একটি tokenId রিপোর্ট করি।
জিরো অ্যাড্রেস কারও মালিকানাধীন নয়, তাই প্রথা অনুযায়ী আমরা টোকেন তৈরি এবং ধ্বংস করার রিপোর্ট করতে এটি ব্যবহার করি।
# @dev কোনো NFT-এর জন্য অনুমোদিত ঠিকানা পরিবর্তন বা পুনর্নিশ্চিত করা হলে এটি ইমিট হয়। জিরো
# অ্যাড্রেস নির্দেশ করে যে কোনো অনুমোদিত ঠিকানা নেই। যখন একটি Transfer ইভেন্ট ইমিট হয়, এটি আরও
# নির্দেশ করে যে সেই NFT-এর জন্য অনুমোদিত ঠিকানা (যদি থাকে) রিসেট করে none করা হয়েছে।
# @param _owner NFT-এর মালিক।
# @param _approved যে ঠিকানা আমরা অনুমোদন করছি।
# @param _tokenId যে NFT আমরা অনুমোদন করছি।
event Approval:
owner: indexed(address)
approved: indexed(address)
tokenId: indexed(uint256)
একটি ERC-721 অনুমোদন একটি ERC-20 অ্যালাউন্সের মতোই। একটি নির্দিষ্ট ঠিকানাকে একটি নির্দিষ্ট টোকেন হস্তান্তর করার অনুমতি দেওয়া হয়। এটি কন্ট্রাক্টগুলোর জন্য একটি মেকানিজম প্রদান করে যাতে তারা কোনো টোকেন গ্রহণ করার সময় সাড়া দিতে পারে। কন্ট্রাক্টগুলো ইভেন্ট শুনতে পারে না, তাই আপনি যদি শুধু তাদের কাছে টোকেন হস্তান্তর করেন তবে তারা এটি সম্পর্কে "জানতে" পারে না। এই পদ্ধতিতে মালিক প্রথমে একটি অনুমোদন জমা দেন এবং তারপর কন্ট্রাক্টে একটি রিকোয়েস্ট পাঠান: "আমি আপনাকে টোকেন X হস্তান্তর করার অনুমোদন দিয়েছি, অনুগ্রহ করে করুন..."।
ERC-721 স্ট্যান্ডার্ডকে ERC-20 স্ট্যান্ডার্ডের মতো করার জন্য এটি একটি ডিজাইনগত সিদ্ধান্ত। যেহেতু ERC-721 টোকেনগুলো ফাঞ্জিবল নয়, তাই একটি কন্ট্রাক্ট টোকেনের মালিকানা দেখেও শনাক্ত করতে পারে যে এটি একটি নির্দিষ্ট টোকেন পেয়েছে।
# @dev কোনো মালিকের জন্য একজন অপারেটর সক্রিয় বা নিষ্ক্রিয় করা হলে এটি ইমিট হয়। অপারেটর মালিকের
# সমস্ত NFT পরিচালনা করতে পারে।
# @param _owner NFT-এর মালিক।
# @param _operator যে ঠিকানায় আমরা অপারেটর অধিকার সেট করছি।
# @param _approved অপারেটর অধিকারের স্ট্যাটাস (অপারেটর অধিকার দেওয়া হলে true এবং
# বাতিল করা হলে false)।
event ApprovalForAll:
owner: indexed(address)
operator: indexed(address)
approved: bool
পাওয়ার অফ অ্যাটর্নির মতো এমন একজন অপারেটর থাকা মাঝে মাঝে দরকারী, যে একটি অ্যাকাউন্টের নির্দিষ্ট ধরনের সমস্ত টোকেন (যেগুলো একটি নির্দিষ্ট কন্ট্রাক্ট দ্বারা পরিচালিত হয়) পরিচালনা করতে পারে। উদাহরণস্বরূপ, আমি এমন একটি কন্ট্রাক্টকে এই ক্ষমতা দিতে চাইতে পারি যা পরীক্ষা করে যে আমি ছয় মাস ধরে এর সাথে যোগাযোগ করিনি কি না, এবং যদি তা হয় তবে আমার সম্পদগুলো আমার উত্তরাধিকারীদের মধ্যে বিতরণ করে (যদি তাদের মধ্যে কেউ এটি চায়, কারণ ট্রানজ্যাকশন দ্বারা কল করা ছাড়া কন্ট্রাক্টগুলো কিছুই করতে পারে না)। ERC-20-তে আমরা একটি উত্তরাধিকার কন্ট্রাক্টকে শুধু একটি উচ্চ অ্যালাউন্স দিতে পারি, কিন্তু ERC-721-এর ক্ষেত্রে এটি কাজ করে না কারণ টোকেনগুলো ফাঞ্জিবল নয়। এটি তারই সমতুল্য।
approved মানটি আমাদের বলে যে ইভেন্টটি কোনো অনুমোদনের জন্য, নাকি কোনো অনুমোদন প্রত্যাহারের জন্য।
স্টেট ভেরিয়েবল
এই ভেরিয়েবলগুলোতে টোকেনগুলোর বর্তমান স্টেট থাকে: কোনগুলো উপলব্ধ আছে এবং সেগুলোর মালিক কে। এগুলোর বেশিরভাগই
হলো HashMap অবজেক্ট, দুটি টাইপের মধ্যে থাকা একমুখী ম্যাপিং (opens in a new tab)।
# @dev NFT ID থেকে এর মালিকানার ঠিকানায় ম্যাপিং।
idToOwner: HashMap[uint256, address]
# @dev NFT ID থেকে অনুমোদিত ঠিকানায় ম্যাপিং।
idToApprovals: HashMap[uint256, address]
ইথেরিয়াম-এ ব্যবহারকারী এবং কন্ট্রাক্টের পরিচয় 160-বিট ঠিকানা দ্বারা উপস্থাপন করা হয়। এই দুটি ভেরিয়েবল টোকেন আইডি থেকে তাদের মালিকদের এবং সেগুলো হস্তান্তর করার জন্য অনুমোদিত ব্যক্তিদের (প্রতিটির জন্য সর্বোচ্চ একজন) ম্যাপ করে। ইথেরিয়াম-এ, আনইনিশিয়ালাইজড ডেটা সর্বদা শূন্য হয়, তাই যদি কোনো মালিক বা অনুমোদিত হস্তান্তরকারী না থাকে তবে সেই টোকেনের মান শূন্য হয়।
# @dev মালিকের ঠিকানা থেকে তার টোকেন সংখ্যায় ম্যাপিং।
ownerToNFTokenCount: HashMap[address, uint256]
এই ভেরিয়েবলটি প্রতিটি মালিকের জন্য টোকেনের সংখ্যা ধারণ করে। মালিকদের থেকে টোকেনগুলোতে কোনো ম্যাপিং নেই, তাই
কোনো নির্দিষ্ট মালিকের মালিকানাধীন টোকেনগুলো শনাক্ত করার একমাত্র উপায় হলো ব্লকচেইনের ইভেন্ট ইতিহাসে ফিরে তাকানো
এবং উপযুক্ত Transfer ইভেন্টগুলো দেখা। আমরা এই ভেরিয়েবলটি ব্যবহার করে জানতে পারি কখন আমাদের কাছে সমস্ত NFT আছে এবং
সময়ের আরও পেছনে ফিরে দেখার প্রয়োজন নেই।
মনে রাখবেন যে এই অ্যালগরিদমটি শুধুমাত্র ইউজার ইন্টারফেস এবং এক্সটার্নাল সার্ভারের জন্য কাজ করে। ব্লকচেইনে চলা কোড নিজে অতীতের ইভেন্টগুলো পড়তে পারে না।
# @dev মালিকের ঠিকানা থেকে অপারেটর ঠিকানাগুলোর ম্যাপিংয়ে ম্যাপিং।
ownerToOperators: HashMap[address, HashMap[address, bool]]
একটি অ্যাকাউন্টের একাধিক অপারেটর থাকতে পারে। তাদের ট্র্যাক রাখার জন্য একটি সাধারণ HashMap অপর্যাপ্ত,
কারণ প্রতিটি কী একটি একক মানের দিকে নিয়ে যায়। এর পরিবর্তে, আপনি মান হিসেবে
HashMap[address, bool] ব্যবহার করতে পারেন। ডিফল্টরূপে প্রতিটি ঠিকানার মান হলো False, যার মানে এটি
কোনো অপারেটর নয়। আপনি প্রয়োজন অনুযায়ী মানগুলোকে True-এ সেট করতে পারেন।
# @dev মিন্টারের ঠিকানা, যিনি একটি টোকেন মিন্ট করতে পারেন
minter: address
নতুন টোকেনগুলো কোনো না কোনোভাবে তৈরি করতে হবে। এই কন্ট্রাক্টে শুধুমাত্র একটি এনটিটি এটি করার অনুমতিপ্রাপ্ত, যা হলো
minter। উদাহরণস্বরূপ, একটি গেমের জন্য এটি সম্ভবত যথেষ্ট। অন্যান্য উদ্দেশ্যে, আরও জটিল ব্যবসায়িক লজিক তৈরি করার প্রয়োজন হতে পারে।
# @dev ইন্টারফেস আইডি থেকে এটি সমর্থিত কিনা সেই সম্পর্কিত bool-এ ম্যাপিং
supportedInterfaces: HashMap[bytes32, bool]
# @dev ERC-165-এর ERC-165 ইন্টারফেস আইডি
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
# @dev ERC-721-এর ERC-165 ইন্টারফেস আইডি
ERC721_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000080ac58cd
ERC-165 (opens in a new tab) একটি কন্ট্রাক্টের জন্য এমন একটি মেকানিজম নির্দিষ্ট করে যার মাধ্যমে এটি প্রকাশ করতে পারে যে অ্যাপ্লিকেশনগুলো কীভাবে এর সাথে যোগাযোগ করতে পারে এবং এটি কোন ERC-গুলো মেনে চলে। এই ক্ষেত্রে, কন্ট্রাক্টটি ERC-165 এবং ERC-721 মেনে চলে।
ফাংশন
এগুলো হলো সেই ফাংশন যা আসলে ERC-721 ইমপ্লিমেন্ট করে।
কনস্ট্রাক্টর
@external
def __init__():
Python-এর মতো Vyper-এও কনস্ট্রাক্টর ফাংশনটিকে __init__ বলা হয়।
"""
@dev কন্ট্রাক্ট কনস্ট্রাক্টর।
"""
Python এবং Vyper-এ, আপনি একটি মাল্টি-লাইন স্ট্রিং (যা """ দিয়ে শুরু এবং শেষ হয়) নির্দিষ্ট করে
এবং কোনোভাবেই এটি ব্যবহার না করেও একটি মন্তব্য তৈরি করতে পারেন। এই মন্তব্যগুলোতে
NatSpec (opens in a new tab)-ও অন্তর্ভুক্ত থাকতে পারে।
self.supportedInterfaces[ERC165_INTERFACE_ID] = True
self.supportedInterfaces[ERC721_INTERFACE_ID] = True
self.minter = msg.sender
স্টেট ভেরিয়েবলগুলো অ্যাক্সেস করতে আপনি self.<variable name> ব্যবহার করেন (আবারও, Python-এর মতোই)।
ভিউ ফাংশন
এগুলো হলো এমন ফাংশন যা ব্লকচেইনের স্টেট পরিবর্তন করে না, এবং তাই যদি এগুলোকে বাইরে থেকে কল করা হয় তবে এগুলো বিনামূল্যে এক্সিকিউট করা যেতে পারে। যদি ভিউ ফাংশনগুলোকে কোনো কন্ট্রাক্ট দ্বারা কল করা হয় তবে সেগুলো এখনও প্রতিটি নোড-এ এক্সিকিউট করতে হবে এবং তাই গ্যাস খরচ হবে।
@view
@external
ফাংশন সংজ্ঞার আগে থাকা এই কিওয়ার্ডগুলো, যা একটি অ্যাট সাইন (@) দিয়ে শুরু হয়, সেগুলোকে ডেকোরেশন বলা হয়। এগুলো
নির্দিষ্ট করে যে কোন পরিস্থিতিতে একটি ফাংশন কল করা যেতে পারে।
@viewনির্দিষ্ট করে যে এই ফাংশনটি একটি ভিউ।@externalনির্দিষ্ট করে যে এই নির্দিষ্ট ফাংশনটি ট্রানজ্যাকশন এবং অন্যান্য কন্ট্রাক্ট দ্বারা কল করা যেতে পারে।
def supportsInterface(_interfaceID: bytes32) -> bool:
Python-এর বিপরীতে, Vyper হলো একটি স্ট্যাটিক টাইপড ভাষা (opens in a new tab)।
আপনি ডেটা টাইপ (opens in a new tab) শনাক্ত না করে কোনো ভেরিয়েবল বা ফাংশন প্যারামিটার ডিক্লেয়ার করতে পারবেন না। এই ক্ষেত্রে ইনপুট প্যারামিটারটি হলো bytes32, যা একটি 256-বিট মান
(256 বিট হলো ইথেরিয়াম ভার্চুয়াল মেশিন-এর নেটিভ ওয়ার্ড সাইজ)। আউটপুটটি হলো একটি বুলিয়ান
মান। প্রথা অনুযায়ী, ফাংশন প্যারামিটারের নামগুলো একটি আন্ডারস্কোর (_) দিয়ে শুরু হয়।
"""
@dev ইন্টারফেস আইডেন্টিফিকেশন ERC-165 এ নির্দিষ্ট করা আছে।
@param _interfaceID ইন্টারফেসের আইডি
"""
return self.supportedInterfaces[_interfaceID]
self.supportedInterfaces HashMap থেকে মানটি রিটার্ন করুন, যা কনস্ট্রাক্টরে (__init__) সেট করা আছে।
### ভিউ ফাংশনগুলো ###
এগুলো হলো সেই ভিউ ফাংশন যা ব্যবহারকারী এবং অন্যান্য কন্ট্রাক্টের কাছে টোকেন সম্পর্কে তথ্য উপলব্ধ করে।
@view
@external
def balanceOf(_owner: address) -> uint256:
"""
@dev `_owner` এর মালিকানাধীন NFT-এর সংখ্যা রিটার্ন করে।
`_owner` জিরো অ্যাড্রেস হলে থ্রো করে। জিরো অ্যাড্রেসে অ্যাসাইন করা NFT-গুলোকে অবৈধ বলে বিবেচনা করা হয়।
@param _owner ব্যালেন্স কোয়েরি করার জন্য ঠিকানা।
"""
assert _owner != ZERO_ADDRESS
এই লাইনটি অ্যাসার্ট (opens in a new tab) করে যে _owner
শূন্য নয়। যদি এটি শূন্য হয়, তবে একটি ত্রুটি দেখা দেয় এবং অপারেশনটি রিভার্ট করা হয়।
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 != ZERO_ADDRESS
return owner
ইথেরিয়াম ভার্চুয়াল মেশিনে (EVM) এমন যেকোনো স্টোরেজ যার মধ্যে কোনো মান সংরক্ষিত নেই, তা শূন্য হয়।
যদি _tokenId-এ কোনো টোকেন না থাকে তবে self.idToOwner[_tokenId]-এর মান শূন্য হয়। সেক্ষেত্রে
ফাংশনটি রিভার্ট করে।
@view
@external
def getApproved(_tokenId: uint256) -> address:
"""
@dev একটি একক NFT-এর জন্য অনুমোদিত ঠিকানা পান।
`_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
@param _tokenId যে NFT-এর অনুমোদনের কোয়েরি করতে হবে তার আইডি।
"""
# `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
assert self.idToOwner[_tokenId] != ZERO_ADDRESS
return self.idToApprovals[_tokenId]
মনে রাখবেন যে getApproved শূন্য রিটার্ন করতে পারে। যদি টোকেনটি বৈধ হয় তবে এটি self.idToApprovals[_tokenId] রিটার্ন করে।
যদি কোনো অনুমোদনকারী না থাকে তবে সেই মানটি শূন্য হয়।
@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।
হস্তান্তর হেল্পার ফাংশন
এই ফাংশনগুলো এমন অপারেশন ইমপ্লিমেন্ট করে যা টোকেন হস্তান্তর বা পরিচালনার অংশ।
### হস্তান্তর ফাংশন হেল্পারগুলো ###
@view
@internal
এই ডেকোরেশন, @internal, এর মানে হলো ফাংশনটি শুধুমাত্র একই কন্ট্রাক্টের ভেতরের অন্যান্য ফাংশন থেকে
অ্যাক্সেসযোগ্য। প্রথা অনুযায়ী, এই ফাংশনের নামগুলোও একটি আন্ডারস্কোর (_) দিয়ে শুরু হয়।
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
তিনটি উপায়ে একটি ঠিকানাকে টোকেন হস্তান্তর করার অনুমতি দেওয়া যেতে পারে:
- ঠিকানাটি টোকেনের মালিক
- ঠিকানাটি সেই টোকেন খরচ করার জন্য অনুমোদিত
- ঠিকানাটি টোকেনের মালিকের জন্য একজন অপারেটর
ওপরের ফাংশনটি একটি ভিউ হতে পারে কারণ এটি স্টেট পরিবর্তন করে না। অপারেটিং খরচ কমানোর জন্য, যেকোনো ফাংশন যা একটি ভিউ হতে পারে, তার একটি ভিউ হওয়া উচিত।
@internal
def _addTokenTo(_to: address, _tokenId: uint256):
"""
@dev একটি নির্দিষ্ট ঠিকানায় একটি NFT যোগ করুন
`_tokenId` কারও মালিকানাধীন হলে থ্রো করে।
"""
# `_tokenId` কারও মালিকানাধীন হলে থ্রো করে
assert self.idToOwner[_tokenId] == ZERO_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] = ZERO_ADDRESS
# কাউন্ট ট্র্যাকিং পরিবর্তন করুন
self.ownerToNFTokenCount[_from] -= 1
যখন হস্তান্তরে কোনো সমস্যা হয় তখন আমরা কলটি রিভার্ট করি।
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
"""
@dev একটি নির্দিষ্ট ঠিকানার অনুমোদন ক্লিয়ার করুন
`_owner` বর্তমান মালিক না হলে থ্রো করে।
"""
# `_owner` বর্তমান মালিক না হলে থ্রো করে
assert self.idToOwner[_tokenId] == _owner
if self.idToApprovals[_tokenId] != ZERO_ADDRESS:
# অনুমোদনগুলো রিসেট করুন
self.idToApprovals[_tokenId] = ZERO_ADDRESS
শুধুমাত্র প্রয়োজন হলেই মান পরিবর্তন করুন। স্টেট ভেরিয়েবলগুলো স্টোরেজে থাকে। স্টোরেজে লেখা হলো EVM (ইথেরিয়াম ভার্চুয়াল মেশিন) দ্বারা করা সবচেয়ে ব্যয়বহুল অপারেশনগুলোর মধ্যে একটি (গ্যাস-এর ক্ষেত্রে)। তাই, এটি কমানো একটি ভালো ধারণা, এমনকি বিদ্যমান মান লেখার ক্ষেত্রেও উচ্চ খরচ হয়।
@internal
def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address):
"""
@dev একটি NFT-এর হস্তান্তর কার্যকর করুন।
`msg.sender` বর্তমান মালিক, একজন অনুমোদিত অপারেটর, অথবা এই NFT-এর জন্য অনুমোদিত
ঠিকানা না হলে থ্রো করে। (নোট: প্রাইভেট ফাংশনে `msg.sender` অনুমোদিত নয় তাই `_sender` পাস করুন।)
`_to` জিরো অ্যাড্রেস হলে থ্রো করে।
`_from` বর্তমান মালিক না হলে থ্রো করে।
`_tokenId` একটি বৈধ NFT না হলে থ্রো করে।
"""
আমাদের এই ইন্টারনাল ফাংশনটি আছে কারণ টোকেন হস্তান্তর করার দুটি উপায় আছে (নিয়মিত এবং নিরাপদ), কিন্তু অডিটিং সহজ করার জন্য আমরা কোডে শুধুমাত্র একটি জায়গাতেই এটি করতে চাই।
# প্রয়োজনীয়তাগুলো চেক করুন
assert self._isApprovedOrOwner(_sender, _tokenId)
# `_to` জিরো অ্যাড্রেস হলে থ্রো করে
assert _to != ZERO_ADDRESS
# অনুমোদন ক্লিয়ার করুন। `_from` বর্তমান মালিক না হলে থ্রো করে
self._clearApproval(_from, _tokenId)
# NFT সরান। `_tokenId` একটি বৈধ NFT না হলে থ্রো করে
self._removeTokenFrom(_from, _tokenId)
# NFT যোগ করুন
self._addTokenTo(_to, _tokenId)
# হস্তান্তর লগ করুন
log Transfer(_from, _to, _tokenId)
Vyper-এ একটি ইভেন্ট এমিট করতে আপনি একটি log স্টেটমেন্ট ব্যবহার করেন (আরও বিস্তারিত জানতে এখানে দেখুন (opens in a new tab))।
হস্তান্তর ফাংশন
### হস্তান্তর ফাংশনগুলো ###
@external
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)
এই ফাংশনটি আপনাকে একটি ইচ্ছামতো ঠিকানায় হস্তান্তর করতে দেয়। যদি না ঠিকানাটি কোনো ব্যবহারকারী হয়, বা এমন কোনো কন্ট্রাক্ট হয় যা জানে কীভাবে টোকেন হস্তান্তর করতে হয়, তবে আপনার হস্তান্তর করা যেকোনো টোকেন সেই ঠিকানায় আটকে যাবে এবং অকেজো হয়ে যাবে।
@external
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)"))` না হয় তবে থ্রো করে।
নোট: bytes4 প্যাডিং সহ bytes32 দ্বারা উপস্থাপিত হয়
@param _from NFT-এর বর্তমান মালিক।
@param _to নতুন মালিক।
@param _tokenId হস্তান্তর করার জন্য NFT।
@param _data কোনো নির্দিষ্ট ফরম্যাট ছাড়া অতিরিক্ত ডেটা, যা `_to`-তে কলে পাঠানো হয়。
"""
self._transferFrom(_from, _to, _tokenId, msg.sender)
প্রথমে হস্তান্তর করা ঠিক আছে কারণ যদি কোনো সমস্যা হয় তবে আমরা এমনিতেই রিভার্ট করতে যাচ্ছি, তাই কলে করা সবকিছু বাতিল হয়ে যাবে।
if _to.is_contract: # `_to` একটি কন্ট্রাক্ট ঠিকানা কিনা তা চেক করুন
প্রথমে পরীক্ষা করে দেখুন ঠিকানাটি কোনো কন্ট্রাক্ট কি না (যদি এর কোড থাকে)। যদি না হয়, তবে ধরে নিন এটি একটি ব্যবহারকারীর
ঠিকানা এবং ব্যবহারকারী টোকেনটি ব্যবহার করতে বা হস্তান্তর করতে সক্ষম হবেন। তবে এটি যেন আপনাকে নিরাপত্তার
মিথ্যা অনুভূতি না দেয়। আপনি টোকেন হারাতে পারেন, এমনকি safeTransferFrom ব্যবহার করেও, যদি আপনি
সেগুলোকে এমন কোনো ঠিকানায় হস্তান্তর করেন যার প্রাইভেট কী কেউ জানে না।
returnValue: bytes32 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data)
টার্গেট কন্ট্রাক্টটি ERC-721 টোকেন গ্রহণ করতে পারে কি না তা দেখতে সেটিকে কল করুন।
# হস্তান্তরের গন্তব্য যদি এমন একটি কন্ট্রাক্ট হয় যা 'onERC721Received' ইমপ্লিমেন্ট করে না তবে থ্রো করে
assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes32)
যদি গন্তব্যটি একটি কন্ট্রাক্ট হয়, কিন্তু এমন একটি যা ERC-721 টোকেন গ্রহণ করে না (বা যা এই নির্দিষ্ট হস্তান্তরটি গ্রহণ না করার সিদ্ধান্ত নিয়েছে), তবে রিভার্ট করুন।
@external
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 != ZERO_ADDRESS
# `_approved` বর্তমান মালিক হলে থ্রো করে
assert _approved != owner
প্রথা অনুযায়ী যদি আপনি কোনো অনুমোদনকারী না রাখতে চান তবে আপনি নিজেকে নয়, বরং জিরো অ্যাড্রেস নিয়োগ করেন।
# প্রয়োজনীয়তাগুলো চেক করুন
senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender
senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender]
assert (senderIsOwner or senderIsApprovedForAll)
একটি অনুমোদন সেট করতে আপনি হয় মালিক হতে পারেন, অথবা মালিক দ্বারা অনুমোদিত একজন অপারেটর হতে পারেন।
# অনুমোদন সেট করুন
self.idToApprovals[_tokenId] = _approved
log Approval(owner, _approved, _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 ApprovalForAll(msg.sender, _operator, _approved)
নতুন টোকেন মিন্ট করা এবং বিদ্যমানগুলো ধ্বংস করা
যে অ্যাকাউন্টটি কন্ট্রাক্ট তৈরি করেছে সেটি হলো minter, সুপার ইউজার যা নতুন
NFT মিন্ট করার জন্য অনুমোদিত। তবে, এমনকি এটিকেও বিদ্যমান টোকেন পোড়ানোর অনুমতি দেওয়া হয় না। শুধুমাত্র মালিক, বা
মালিক দ্বারা অনুমোদিত কোনো এনটিটি এটি করতে পারে।
### মিন্ট এবং পোড়ানো ফাংশনগুলো ###
@external
def mint(_to: address, _tokenId: uint256) -> bool:
এই ফাংশনটি সর্বদা True রিটার্ন করে, কারণ যদি অপারেশনটি ব্যর্থ হয় তবে এটি রিভার্ট করা হয়।
"""
@dev টোকেন মিন্ট করার ফাংশন
`msg.sender` মিন্টার না হলে থ্রো করে।
`_to` জিরো অ্যাড্রেস হলে থ্রো করে।
`_tokenId` কারও মালিকানাধীন হলে থ্রো করে।
@param _to যে ঠিকানা মিন্ট করা টোকেনগুলো গ্রহণ করবে।
@param _tokenId মিন্ট করার জন্য টোকেন আইডি।
@return একটি বুলিয়ান যা নির্দেশ করে অপারেশনটি সফল হয়েছে কিনা。
"""
# `msg.sender` মিন্টার না হলে থ্রো করে
assert msg.sender == self.minter
শুধুমাত্র মিন্টার (যে অ্যাকাউন্টটি ERC-721 কন্ট্রাক্ট তৈরি করেছে) নতুন টোকেন মিন্ট করতে পারে। ভবিষ্যতে যদি আমরা মিন্টারের পরিচয় পরিবর্তন করতে চাই তবে এটি একটি সমস্যা হতে পারে। একটি প্রোডাকশন কন্ট্রাক্টে আপনি সম্ভবত এমন একটি ফাংশন চাইবেন যা মিন্টারকে অন্য কারও কাছে মিন্টারের সুবিধাগুলো হস্তান্তর করার অনুমতি দেয়।
# `_to` জিরো অ্যাড্রেস হলে থ্রো করে
assert _to != ZERO_ADDRESS
# NFT যোগ করুন। `_tokenId` কারও মালিকানাধীন হলে থ্রো করে
self._addTokenTo(_to, _tokenId)
log Transfer(ZERO_ADDRESS, _to, _tokenId)
return True
প্রথা অনুযায়ী, নতুন টোকেন মিন্টিং জিরো অ্যাড্রেস থেকে একটি হস্তান্তর হিসেবে গণ্য হয়।
@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)
যে কাউকে টোকেন হস্তান্তর করার অনুমতি দেওয়া হয়, তাকে এটি পোড়ানোরও অনুমতি দেওয়া হয়। যদিও পোড়ানো জিরো অ্যাড্রেসে হস্তান্তর করার সমতুল্য বলে মনে হয়, জিরো অ্যাড্রেস আসলে টোকেনটি গ্রহণ করে না। এটি আমাদের টোকেনের জন্য ব্যবহৃত সমস্ত স্টোরেজ খালি করার অনুমতি দেয়, যা ট্রানজ্যাকশনের গ্যাস খরচ কমাতে পারে।
এই কন্ট্রাক্ট ব্যবহার করা
Solidity-এর বিপরীতে, Vyper-এ ইনহেরিটেন্স নেই। কোডটিকে আরও পরিষ্কার এবং তাই সুরক্ষিত করা সহজ করার জন্য এটি একটি ইচ্ছাকৃত ডিজাইনগত সিদ্ধান্ত। তাই আপনার নিজস্ব Vyper ERC-721 কন্ট্রাক্ট তৈরি করতে আপনি এই কন্ট্রাক্টটি (opens in a new tab) নিতে পারেন এবং আপনি যে ব্যবসায়িক লজিক চান তা ইমপ্লিমেন্ট করতে এটি পরিবর্তন করতে পারেন।
উপসংহার
পর্যালোচনার জন্য, এখানে এই কন্ট্রাক্টের সবচেয়ে গুরুত্বপূর্ণ কিছু ধারণা দেওয়া হলো:
- নিরাপদ হস্তান্তরের মাধ্যমে ERC-721 টোকেন গ্রহণ করতে, কন্ট্রাক্টগুলোকে
ERC721Receiverইন্টারফেস ইমপ্লিমেন্ট করতে হবে। - এমনকি আপনি নিরাপদ হস্তান্তর ব্যবহার করলেও, টোকেনগুলো আটকে যেতে পারে যদি আপনি সেগুলোকে এমন কোনো ঠিকানায় পাঠান যার প্রাইভেট কী অজানা।
- যখন কোনো অপারেশনে সমস্যা হয় তখন শুধু একটি ব্যর্থতার মান রিটার্ন করার পরিবর্তে কলটি
revertকরা একটি ভালো ধারণা। - ERC-721 টোকেনগুলোর অস্তিত্ব থাকে যখন সেগুলোর একজন মালিক থাকে।
- একটি NFT হস্তান্তর করার জন্য অনুমোদিত হওয়ার তিনটি উপায় রয়েছে। আপনি মালিক হতে পারেন, একটি নির্দিষ্ট টোকেনের জন্য অনুমোদিত হতে পারেন, অথবা মালিকের সমস্ত টোকেনের জন্য একজন অপারেটর হতে পারেন।
- অতীতের ইভেন্টগুলো শুধুমাত্র ব্লকচেইনের বাইরে দৃশ্যমান। ব্লকচেইনের ভেতরে চলা কোড সেগুলো দেখতে পারে না।
এখন যান এবং সুরক্ষিত Vyper কন্ট্রাক্ট ইমপ্লিমেন্ট করুন।