Vyper ERC-721 কন্ট্রাক্ট ওয়াকথ্রু
ভূমিকা
ERC-721 স্ট্যান্ডার্ডটি নন-ফান্জেবল টোকেন (NFT)-এর মালিকানা ধরে রাখতে ব্যবহৃত হয়। ERC-20 টোকেনগুলো একটি পণ্যের মতো আচরণ করে, কারণ প্রতিটি টোকেনের মধ্যে কোনো পার্থক্য নেই। এর বিপরীতে, ERC-721 টোকেনগুলো এমন সম্পদের জন্য ডিজাইন করা হয়েছে যা একই রকম কিন্তু হুবহু এক নয়, যেমন বিভিন্ন cat cartoons (opens in a new tab) বা বিভিন্ন রিয়েল এস্টেটের মালিকানা।
এই নিবন্ধে আমরা রিউয়া নাকামুরার ERC-721 কন্ট্রাক্ট (opens in a new tab) বিশ্লেষণ করব। এই কন্ট্রাক্টটি Vyper (opens in a new tab)-এ লেখা হয়েছে, যা একটি Python-এর মতো কন্ট্রাক্ট ভাষা এবং এটি এমনভাবে ডিজাইন করা হয়েছে যাতে Solidity-এর তুলনায় অনিরাপদ কোড লেখা কঠিন হয়।
কন্ট্রাক্ট
# @dev ERC-721 নন-ফাঞ্জিবল টোকেন স্ট্যান্ডার্ডের ইমপ্লিমেন্টেশন।
# @author রিউয়া নাকামুরা (@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() দ্বারা কল করা চুক্তির (contract) জন্য ইন্টারফেস
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, যার মানে এটি ব্লকচেইন-এর স্টেট পড়তে পারে, কিন্তু তা পরিবর্তন করতে পারে না।
ইভেন্ট
ইভেন্টগুলো (opens in a new tab) ব্লকচেইন-এর বাইরের ব্যবহারকারী এবং সার্ভারগুলোকে বিভিন্ন ঘটনা সম্পর্কে জানাতে এমিট করা হয়। মনে রাখবেন যে ইভেন্টগুলোর বিষয়বস্তু ব্লকচেইন-এর কন্ট্রাক্টগুলোর জন্য উপলব্ধ নয়।
# @dev যেকোনো প্রক্রিয়ায় কোনো NFT-এর মালিকানা পরিবর্তন হলে এটি এমিট হয়। এই ইভেন্টটি তখন এমিট হয় যখন NFT-গুলো
# তৈরি করা হয় (`from` == 0) এবং ধ্বংস করা হয় (`to` == 0)। ব্যতিক্রম: চুক্তি (contract) তৈরির সময়, যেকোনো
# সংখ্যক 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 ERC165-এর ERC165 ইন্টারফেস আইডি
ERC165_INTERFACE_ID: constant(bytes32) = 0x0000000000000000000000000000000000000000000000000000000001ffc9a7
# @dev ERC721-এর ERC165 ইন্টারফেস আইডি
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` শূন্য ঠিকানা হলে থ্রো (Throw) করে। শূন্য ঠিকানায় অ্যাসাইন করা 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 না হলে থ্রো (Throw) করে।
@param _tokenId একটি NFT-এর আইডেন্টিফায়ার।
owner: address = self.idToOwner[_tokenId]
# `_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে
assert owner != ZERO_ADDRESS
return owner
ইথিরিয়াম ভার্চুয়াল মেশিন (EVM)-এ এমন যেকোনো স্টোরেজ যার মধ্যে কোনো ভ্যালু স্টোর করা নেই, তা শূন্য হয়। যদি _tokenId-এ কোনো টোকেন না থাকে তবে self.idToOwner[_tokenId]-এর ভ্যালু শূন্য হয়। সেক্ষেত্রে ফাংশনটি রিভার্ট করে।
@view
@external
def getApproved(_tokenId: uint256) -> address:
# @dev একটি একক NFT-এর জন্য অনুমোদিত ঠিকানা পান।
`_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে।
@param _tokenId অনুমোদনের কোয়েরি করার জন্য NFT-এর আইডি।
# `_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে
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` কারও মালিকানাধীন হলে থ্রো (Throw) করে।
# `_tokenId` কারও মালিকানাধীন হলে থ্রো (Throw) করে
assert self.idToOwner[_tokenId] == ZERO_ADDRESS
# মালিক পরিবর্তন করুন
self.idToOwner[_tokenId] = _to
# কাউন্ট ট্র্যাকিং পরিবর্তন করুন
self.ownerToNFTokenCount[_to] += 1
@internal
def _removeTokenFrom(_from: address, _tokenId: uint256):
# @dev একটি নির্দিষ্ট ঠিকানা থেকে একটি NFT সরান
`_from` বর্তমান মালিক না হলে থ্রো (Throw) করে।
# `_from` বর্তমান মালিক না হলে থ্রো (Throw) করে
assert self.idToOwner[_tokenId] == _from
# মালিক পরিবর্তন করুন
self.idToOwner[_tokenId] = ZERO_ADDRESS
# কাউন্ট ট্র্যাকিং পরিবর্তন করুন
self.ownerToNFTokenCount[_from] -= 1
যখন কোনো ট্রান্সফারে সমস্যা হয় তখন আমরা কলটি রিভার্ট করি।
@internal
def _clearApproval(_owner: address, _tokenId: uint256):
# @dev একটি নির্দিষ্ট ঠিকানার অনুমোদন ক্লিয়ার করুন
`_owner` বর্তমান মালিক না হলে থ্রো (Throw) করে।
# `_owner` বর্তমান মালিক না হলে থ্রো (Throw) করে
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-এর জন্য অনুমোদিত
ঠিকানা না হলে থ্রো (Throw) করে। (নোট: প্রাইভেট ফাংশনে `msg.sender` অনুমোদিত নয় তাই `_sender` পাস করুন।)
`_to` শূন্য ঠিকানা হলে থ্রো (Throw) করে।
`_from` বর্তমান মালিক না হলে থ্রো (Throw) করে।
`_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে।
আমাদের এই ইন্টারনাল ফাংশনটি রয়েছে কারণ টোকেন ট্রান্সফার করার দুটি উপায় রয়েছে (নিয়মিত এবং নিরাপদ), তবে অডিটিং সহজ করার জন্য আমরা কোডে শুধুমাত্র একটি স্থানে এটি করতে চাই।
# প্রয়োজনীয়তাগুলো চেক করুন
assert self._isApprovedOrOwner(_sender, _tokenId)
# `_to` শূন্য ঠিকানা হলে থ্রো (Throw) করে
assert _to != ZERO_ADDRESS
# অনুমোদন ক্লিয়ার করুন। `_from` বর্তমান মালিক না হলে থ্রো (Throw) করে
self._clearApproval(_from, _tokenId)
# NFT সরান। `_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে
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-এর জন্য অনুমোদিত
ঠিকানা না হলে থ্রো (Throw) করে।
`_from` বর্তমান মালিক না হলে থ্রো (Throw) করে।
`_to` শূন্য ঠিকানা হলে থ্রো (Throw) করে।
`_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে।
@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-এর জন্য
অনুমোদিত ঠিকানা না হলে থ্রো (Throw) করে।
`_from` বর্তমান মালিক না হলে থ্রো (Throw) করে।
`_to` শূন্য ঠিকানা হলে থ্রো (Throw) করে।
`_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে।
যদি `_to` একটি স্মার্ট কন্ট্রাক্ট হয়, তবে এটি `_to`-তে `onERC721Received` কল করে এবং রিটার্ন ভ্যালু
`bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` না হলে থ্রো (Throw) করে।
নোট: 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 টোকেন গ্রহণ করতে পারে কি না তা দেখতে সেটিকে কল করুন।
# ট্রান্সফারের গন্তব্য এমন কোনো কন্ট্রাক্ট হলে থ্রো (Throw) করে যা '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 মালিক, অথবা বর্তমান মালিকের একজন অনুমোদিত অপারেটর না হলে থ্রো (Throw) করে।
`_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে। (নোট: এটি EIP-তে লেখা নেই)
`_approved` বর্তমান মালিক হলে থ্রো (Throw) করে। (নোট: এটি EIP-তে লেখা নেই)
@param _approved প্রদত্ত NFT আইডির জন্য অনুমোদন করার ঠিকানা।
@param _tokenId অনুমোদন করার টোকেনের আইডি।
owner: address = self.idToOwner[_tokenId]
# `_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে
assert owner != ZERO_ADDRESS
# `_approved` বর্তমান মালিক হলে থ্রো (Throw) করে
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` হয় তবে থ্রো (Throw) করে। (নোট: এটি EIP-তে লেখা নেই)
@notice প্রেরকের কাছে সেই মুহূর্তে কোনো টোকেন না থাকলেও এটি কাজ করে।
@param _operator অনুমোদিত অপারেটরদের সেটে যোগ করার ঠিকানা।
@param _approved অপারেটর অনুমোদিত হলে True, অনুমোদন বাতিল করতে হলে false।
# `_operator` যদি `msg.sender` হয় তবে থ্রো (Throw) করে
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` মিন্টার না হলে থ্রো (Throw) করে।
`_to` শূন্য ঠিকানা হলে থ্রো (Throw) করে।
`_tokenId` কারও মালিকানাধীন হলে থ্রো (Throw) করে।
@param _to যে ঠিকানাটি মিন্ট করা টোকেনগুলো গ্রহণ করবে।
@param _tokenId মিন্ট করার টোকেন আইডি।
@return একটি বুলিয়ান যা নির্দেশ করে অপারেশনটি সফল হয়েছে কিনা।
# `msg.sender` মিন্টার না হলে থ্রো (Throw) করে
assert msg.sender == self.minter
শুধুমাত্র মিন্টার (যে একাউন্ট-টি ERC-721 কন্ট্রাক্ট তৈরি করেছে) নতুন টোকেন মিন্ট করতে পারে। ভবিষ্যতে যদি আমরা মিন্টারের পরিচয় পরিবর্তন করতে চাই তবে এটি একটি সমস্যা হতে পারে। একটি প্রোডাকশন কন্ট্রাক্টে আপনি সম্ভবত এমন একটি ফাংশন চাইবেন যা মিন্টারকে অন্য কারও কাছে মিন্টারের সুবিধাগুলো ট্রান্সফার করার অনুমতি দেয়।
# `_to` শূন্য ঠিকানা হলে থ্রো (Throw) করে
assert _to != ZERO_ADDRESS
# NFT যোগ করুন। `_tokenId` কারও মালিকানাধীন হলে থ্রো (Throw) করে
self._addTokenTo(_to, _tokenId)
log Transfer(ZERO_ADDRESS, _to, _tokenId)
return True
প্রথা অনুযায়ী, নতুন টোকেন মিন্ট করাকে জিরো এডড্রেস থেকে ট্রান্সফার হিসেবে গণ্য করা হয়।
@external
def burn(_tokenId: uint256):
# @dev একটি নির্দিষ্ট ERC721 টোকেন বার্ন করে।
`msg.sender` বর্তমান মালিক, একজন অনুমোদিত অপারেটর, অথবা এই NFT-এর জন্য অনুমোদিত
ঠিকানা না হলে থ্রো (Throw) করে।
`_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে।
@param _tokenId বার্ন করার ERC721 টোকেনের uint256 আইডি।
# প্রয়োজনীয়তাগুলো চেক করুন
assert self._isApprovedOrOwner(msg.sender, _tokenId)
owner: address = self.idToOwner[_tokenId]
# `_tokenId` একটি বৈধ NFT না হলে থ্রো (Throw) করে
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 কন্ট্রাক্ট ইমপ্লিমেন্ট করুন।
আমার আরও কাজের জন্য এখানে দেখুন (opens in a new tab)।
পেজ সর্বশেষ আপডেট করা হয়েছে: 28 এপ্রিল, 2026