Token-2022, SPL Token प्रोग्राम का एक नया backward-compatible वर्ज़न है जो extensions के रूप में अतिरिक्त फ़ीचर्स को सपोर्ट करता है। इन extensions का बाइटकोड (bytecode) स्वयं Token-2022 प्रोग्राम का ही हिस्सा है, इसके लिए कोई अलग प्रोग्राम डिप्लॉय नहीं किए जाते हैं। किसी विशेष टोकन के लिए एक्टिवेट किया गया बाइटकोड, mint या टोकन अकाउंट्स में इनेबल किए गए extension द्वारा निर्धारित किया जाता है।
उदाहरण के लिए, ओरिजिनल SPL Token प्रोग्राम में, टोकन का नाम, सिंबल, या लोगो URL जैसे मेटाडेटा (metadata) को जोड़ने के लिए Metaplex जैसे किसी बाहरी प्रोग्राम की आवश्यकता होती है। लेकिन Token-2022 में, आप टोकन mint पर एक मेटाडेटा extension को इनेबल करके मेटाडेटा जोड़ सकते हैं।
इस आर्टिकल में, हम जानेंगे कि Token-2022 आंतरिक रूप से कैसे काम करता है, और extensions को सपोर्ट करने के लिए इसमें क्या बदलाव किए गए हैं। हम Token-2022 का उपयोग करके extensions के साथ Solana टोकन बनाने के एक प्रैक्टिकल उदाहरण को भी विस्तार से समझेंगे।
Token-2022 का आर्किटेक्चर
सबसे पहले, हम देखेंगे कि कैसे Token-2022 आंतरिक रूप से ओरिजिनल SPL Token प्रोग्राम को एक्सटेंड करता है, कैसे अकाउंट लेआउट कम्पैटिबल (compatible) रहते हैं, और कैसे इंस्ट्रक्शन सेट (instruction set) मौजूदा प्रोग्राम्स को बिना ब्रेक किए बढ़ता है।
सुपरसेट डिज़ाइन (Superset design) और बैकवर्ड कम्पैटिबिलिटी (backward compatibility)
Token-2022, SPL Token प्रोग्राम के लिए एक ड्रॉप-इन रिप्लेसमेंट (drop-in replacement) है। हर वह ऑपरेशन जो आप पहले कर सकते थे—minting, transferring, burning, freezing, authorities बदलना—अभी भी बिल्कुल वैसे ही काम करता है। ऐसा इसलिए है क्योंकि Token-2022 ओरिजिनल SPL mint अकाउंट के पहले 82 बाइट्स और टोकन अकाउंट के पहले 165 बाइट्स को प्रिजर्व (preserve) करके रखता है, जिसमें supply, decimals, owner, freeze authority, और amount जैसी फ़ील्ड्स शामिल होती हैं। नीचे ओरिजिनल SPL mint अकाउंट का लेआउट दिया गया है:

एक डायग्राम जो SPL mint अकाउंट के पहले 82 बाइट्स का मेमोरी लेआउट दिखाता है
ओरिजिनल प्रोग्राम (SPL) केवल उसी डेटा को डीसीरियलाइज़ (deserialize) और प्रोसेस करता है; Token-2022 उसी हिस्से को डीसीरियलाइज़ करता है और फिर extensions के लिए उसके आगे भी पढ़ना जारी रखता है।
Extensions वैकल्पिक फ़ीचर्स (optional features) हैं जो Token-2022 प्रोग्राम में अंतर्निहित (built-in) होते हैं, जिन्हें आप mint अकाउंट या टोकन अकाउंट पर इनेबल कर सकते हैं। नीचे दिया गया डायग्राम Token-2022 mint का TLV लेआउट दिखाता है: ओरिजिनल SPL mint के फिक्स्ड 82 बाइट्स और उसके बाद वेरिएबल-साइज़ का TLV रीजन जो extensions डेटा स्टोर करता है।

Token-2022 अकाउंट लेआउट और TLV extensions
ओरिजिनल SPL Token प्रोग्राम में एक फिक्स्ड बाइनरी लेआउट होता है जहाँ प्रत्येक फ़ील्ड का साइज़, प्रकार और वैल्यू पूर्व निर्धारित होते हैं। Token-2022 प्रिजर्व किए गए पहले 82 से 165 बाइट्स वाले हिस्से में उसी फिक्स्ड बाइनरी लेआउट का उपयोग करता है, लेकिन उसके आगे यह extensions को स्टोर करने के लिए एक वेरिएबल Type-Length-Value (TLV) एन्कोडिंग स्कीम का उपयोग करता है।
Type–Length–Value (TLV) एक डेटा सीरियलाइज़ेशन स्कीम है। डेटा में मौजूद प्रत्येक ऑब्जेक्ट के तीन भाग होते हैं:
- Type: एक आइडेंटिफ़ायर (identifier) जो यह निर्दिष्ट करता है कि डेटा क्या दर्शाता है।
- Length: डेटा का साइज़ (बाइट्स में)।
- Value: वास्तविक डेटा बाइट्स।
चूँकि प्रत्येक एंट्री अपने प्रकार और अपनी लंबाई दोनों को दर्शाती है, एक प्रोग्राम यह समझने के लिए कि वह क्या पार्स (parse) कर रहा है, type पढ़ सकता है, वैल्यू के रूप में ठीक उतने बाइट्स पढ़ सकता है, और फिर सीधे अगली एंट्री पर जाने (skip) के लिए length का उपयोग कर सकता है।
यदि कोई type अज्ञात है, तब भी प्रोग्राम उसे छोड़कर (skip) आगे बढ़ने के लिए length का उपयोग कर सकता है।
उदाहरण के लिए, नीचे दिए गए डायग्राम में, 12 type वाली एंट्री की length 2 बाइट्स है, इसलिए इसके value फ़ील्ड में दो बाइट्स होते हैं। यदि प्रोग्राम 12 type को नहीं पहचानता है, तो वह उन 2 बाइट्स को छोड़ सकता है और सीधे अगली एंट्री, type 20 पर जा सकता है।

Type Length Value को दर्शाने वाला एक डायग्राम
Token-2022 को इसी TLV एन्कोडिंग स्कीम के साथ इम्प्लीमेंट किया गया है। प्रत्येक extension को एक TLV एंट्री के रूप में प्रस्तुत किया जाता है और इसके संदर्भ में:
- Type extension की यूनिक ID है।
- Length बाइट्स में सीरियलाइज़ किए गए extension डेटा का साइज़ है।
- Value स्वयं सीरियलाइज़ किया गया extension डेटा है।
यह स्ट्रक्चर (structure) कई extensions को बिना किसी टकराव के एक ही अकाउंट में क्रमबद्ध तरीके से पैक करने की अनुमति देता है।

TLV एन्कोडिंग वास्तव में कैसे काम करती है, यह स्पष्ट करने के लिए, आइए दो extensions को देखते हैं: ImmutableOwner और MetadataPointer।
ImmutableOwner TLV लेआउट
ImmutableOwner extension एक Token-2022 extension है जो किसी टोकन अकाउंट के मालिक (owner) को उसके निर्माण के बाद बदलने से रोकता है। हम अभी TLV लेआउट को देखेंगे और इस आर्टिकल में बाद में इसके उपयोग के बारे में अधिक चर्चा करेंगे।
ImmutableOwner में निम्नलिखित TLV प्रॉपर्टीज होती हैं:
- Type (T):
0x0a(ImmutableOwnerके लिए यूनिक ID) - Length (L):
0x01 0x00 0x00 0x00(नंबर 1 को 4-byte little-endian इंटीजर के रूप में एन्कोड किया गया है) - Value (V):
0x01(यह extension डेटा वैल्यू का एक सिंगल बाइट स्टोर करता है, या तो0या1)
इसकी Rust परिभाषा एक खाली struct है:
pub struct ImmutableOwner {}
हालाँकि इस struct में कोई फ़ील्ड नहीं है, फिर भी TLV एन्कोडिंग V के लिए एक बाइट रिज़र्व करती है जो यह दर्शाता है कि फ्लैग इनेबल है या नहीं (1 इनेबल के लिए, 0 डिसेबल के लिए)। यही कारण है कि इस एंट्री की length शून्य (non-zero) नहीं होती है।
इसलिए, इसका TLV लेआउट कुछ इस तरह दिखेगा:

MetadataPointer TLV लेआउट
MetadataPointer extension यह परिभाषित करता है कि टोकन का ऑफ़-चेन (off-chain) मेटाडेटा कहाँ स्थित है और उसे कौन अपडेट कर सकता है।
ImmutableOwner extension के विपरीत, MetadataPointer में वैल्यूज़ होती हैं: दो public key वैल्यूज़ (authority और metadata_address)।
यहाँ दिया गया है कि Rust struct कैसा दिखता है:
struct MetadataPointer {
authority: PubKey;
metadata_address: PubKey;
}
TLV में, लेआउट में निम्नलिखित जानकारी होगी:
0x1a:MetadataPointerके लिए Type ID (T)0x40 0x00 0x00 0x00: Length (L) = 64 बाइट्स (little-endian)<64 bytes>: Value (V), सीरियलाइज़ किए गएauthorityऔरmetadata_addressजो मेटाडेटा के पब्लिक एड्रेस को पॉइंट करते हैं।
चूँकि V में एक authority और metadata_address पब्लिक-की (public key) शामिल है, आइए उन्हें दर्शाने के लिए सैंपल के तौर पर 32 बाइट की पब्लिक-कीज़ (public keys) का उपयोग करें ताकि हम बेहतर ढंग से समझ सकें कि लेआउट कैसा दिखेगा।
मान लीजिए अकाउंट की authority पब्लिक-की (public key) है:
7c4YH58z6Yd1H5pa9vHqPqN8P3f9DuzGcbj2duq5Vn6a
और metadata_address पब्लिक-की है:
9A4q8Xzj8cQ6w6sKuS27rrR2i1cC6VnV4c7pg1Zg1Vgk
Solana पर, पब्लिक-कीज़ Base58-एन्कोडेड 32-byte वैल्यूज़ होती हैं। वैल्यू (V), authority और metadata_address का संयोजन (concatenation) है। जब दोनों पब्लिक-कीज़ को Base58 से उनके raw bytes में डीकोड किया जाता है और फिर हेक्साडेसिमल (hexadecimal) रूप में लिखा जाता है, तो परिणामी 64-byte सीक्वेंस होगा:
// authority (32 bytes)
0x62 0x21 0x73 0xa4 0x94 0x0c 0x4c 0x3c 0x29 0x7a 0x7f 0x3c 0x4f 0xc1 0x12 0x3f
0x3b 0x34 0xc6 0x51 0x3f 0x3e 0x24 0x23 0xf3 0x1c 0xaa 0x88 0x83 0x44 0xa3 0x37
// metadata_address (32 bytes)
0x79 0x30 0x0d 0x97 0x56 0x47 0xc2 0x18 0x79 0x35 0x0d 0xe6 0x18 0x9f 0x80 0xec
0xd6 0xca 0x36 0xa5 0xb1 0x77 0x5a 0xa8 0xe4 0x45 0x66 0x7b 0x85 0xf3 0x32 0xe1
यह मानते हुए कि एक Token-2022 अकाउंट में ImmutableOwner और MetadataPointer दोनों extensions इनिशियलाइज़ किए गए हैं, इसका TLV लेआउट कुछ इस तरह दिखेगा:

किसी अकाउंट को पढ़ते समय, प्रोग्राम TLV सेक्शन के माध्यम से इटरेट (iterate) कर सकता है, और चुनिंदा रूप से केवल उन्हीं extensions को डीकोड कर सकता है जिन्हें वह पहचानता है। अज्ञात (Unknown) extension types को उनकी घोषित (declared) lengths का उपयोग करके छोड़ दिया जाता है (skipped)।
आइए विचार करें कि प्रोग्राम एक अज्ञात extension UnknownExtension को कैसे हैंडल करेगा:
- प्रोग्राम
UnknownExtensionकी TLV एंट्री को पढ़ता है:- T =
0x0b(एक अज्ञात type) - L =
0x14 0x00 0x00 0x00(20, little-endian) - V =
0x4…0xed(20 बाइट्स लंबा, जैसा कि L द्वारा निर्दिष्ट किया गया है)
- T =
- चूँकि type पहचाना नहीं गया है, इसलिए प्रोग्राम वैल्यू को इंटरप्रेट (interpret) करने का प्रयास नहीं करता है
- लेकिन यह फिर भी length को पढ़ता है और उतने बाइट्स को छोड़कर (skip) आगे बढ़ जाता है (इस स्थिति में, 20)
- फिर यह अगली TLV एंट्री पर आगे बढ़ता है, यदि कोई हो
अब मान लीजिए कि अज्ञात extension की 64-बाइट वैल्यू थी, तो प्रोग्राम L से वैल्यू 64 पढ़ेगा और फिर अगला T खोजने के लिए 64 बाइट्स (V के ऊपर से) छोड़कर आगे बढ़ जाएगा। यह अप्रोच Token-2022 को फॉरवर्ड-कम्पैटिबल (forward-compatible) बनाती है; भविष्य के extensions मौजूदा प्रोग्राम्स को ब्रेक नहीं करेंगे।
Token-2022 इंस्ट्रक्शन कम्पैटिबिलिटी और नई फंक्शनलिटी
एक Solana इंस्ट्रक्शन (instruction) ऑन-चेन प्रोग्राम को किया गया एक कॉल है, इसमें तीन फ़ील्ड्स होती हैं:
- एक Program ID, उस ऑन-चेन प्रोग्राम की पब्लिक-की (public key) जिसे कॉल करना है,
- उन अकाउंट्स की सूची जिन्हें प्रोग्राम पढ़ेगा (read) या जिन पर लिखेगा (write)
- इंस्ट्रक्शन डेटा — बाइट्स का एक आर्बिट्रेरी (arbitrary) सीक्वेंस जिसका फ़ॉर्मेट प्रोग्राम द्वारा परिभाषित किया जाता है।
ओरिजिनल SPL Token प्रोग्राम में 25 यूनिक (unique) इंस्ट्रक्शन्स हैं। Token-2022 इन सभी इंस्ट्रक्शन्स को सपोर्ट करता है और नए extension की फंक्शनलिटी को इनेबल करने के लिए 25वें इंस्ट्रक्शन के बाद नए इंस्ट्रक्शन्स जोड़ता है।
दूसरे शब्दों में, Token-2022 में मौजूदा टोकन इंस्ट्रक्शन्स जैसे MintTo, Transfer, या Burn बिल्कुल SPL की तरह ही व्यवहार करते हैं।
यहाँ एक इंस्ट्रक्शन लेआउट का उदाहरण दिया गया है जो टोकन प्रोग्राम को एक mint अकाउंट से डेस्टिनेशन (destination) टोकन अकाउंट में 100 टोकन mint करने के लिए कहता है:

एप्लीकेशंस (Applications) अपने इंस्ट्रक्शन्स में केवल Program ID बदलकर Token-2022 को अपना सकती हैं।
यहाँ Token प्रोग्राम के ओरिजिनल 25 इंस्ट्रक्शन्स के अलावा Token-2022 इंस्ट्रक्शन्स की सूची दी गई है। token instructions के नाम उन extensions से मेल खाने के लिए रखे गए हैं जिन्हें वे इनिशियलाइज़ या मैनेज करते हैं:
25: InitializeMintCloseAuthority
26: TransferFeeExtension
27: ConfidentialTransferExtension
28: DefaultAccountStateExtension
29: Reallocate
30: MemoTransferExtension
31: CreateNativeMint
32: InitializeNonTransferableMint
33: InterestBearingMintExtension
34: CpiGuardExtension
35: InitializePermanentDelegate
36: TransferHookExtension
37: ConfidentialTransferFeeExtension
38: WithdrawExcessLamports
39: MetadataPointerExtension
40: GroupPointerExtension
41: GroupMemberPointerExtension
42: ConfidentialMintBurnExtension
43: ScaledUiAmountExtension
44: PausableExtension
इम्प्लीमेंटेशन पैटर्न और Token-2022 टोकन कैसे बनाएँ
Token-2022 में, mint या टोकन अकाउंट को इनिशियलाइज़ करने से पहले सभी extensions को निर्दिष्ट (specify) किया जाना चाहिए, ताकि उनके डेटा के लिए पर्याप्त स्पेस आवंटित (allocate) किया जा सके। एक बार इनिशियलाइज़ होने के बाद, कोई अतिरिक्त extensions नहीं जोड़े जा सकते हैं।
आप spl-token CLI का उपयोग करके Token-2022 टोकन को उनके extensions के साथ बना सकते हैं। यह आवश्यक अकाउंट साइज़ की गणना करेगा, प्रत्येक extension की TLV एंट्री को mint अकाउंट में लिखेगा, और अंततः बैकग्राउंड में (behind the scenes) InitializeMint2 इंस्ट्रक्शन के साथ mint को इनिशियलाइज़ करेगा।
CLI टेम्प्लेट कुछ इस तरह दिखेगा:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
create-token <extension flags>
मान लें कि हम एक mint में दो extensions जोड़ना चाहते हैं: एक जो टोकन के लिए फिक्स्ड ब्याज दर (fixed interest rate) परिभाषित करता है (InterestBearingConfig) और दूसरा जो mint को ऑफ़-चेन (off-chain) मेटाडेटा का रेफरेंस देने में सक्षम बनाता है (MetadataPointer)। हम --interest-rate और --enable-metadata फ्लैग्स को जोड़कर नीचे दी गई कमांड को रन कर सकते हैं। नीचे दी गई कमांड में 5 ब्याज दर (interest rate) है:
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
create-token --interest-rate 5 --enable-metadata
यह कमांड InterestBearingConfig और MetadataPointer दोनों extensions इनेबल होने के साथ एक mint अकाउंट बनाता है। आंतरिक रूप से (Internally), यह ExtensionType::try_calculate_account_len::<Mint>(&[InterestBearingConfig, MetadataPointer])?; का उपयोग करके पूरा अकाउंट साइज़ आवंटित करता है, तुरंत interest rate पैरामीटर्स लिखता है जो अंतर्निहित (implicitly) रूप से InterestBearingConfig extension को इनिशियलाइज़ करता है, और मेटाडेटा के लिए एक TLV स्लॉट रिज़र्व करता है। अब, हमें मैन्युअल रूप से मेटाडेटा extension को इनिशियलाइज़ करना होगा।
try_calculate_account_len (token-2022 library से) फ़ंक्शन चुने गए extensions के आधार पर आवश्यक कुल स्पेस की गणना करता है, और अंतिम mint इनिशियलाइज़ेशन के अकाउंट स्ट्रक्चर को लॉक करने से पहले प्रत्येक extension का इनिशियलाइज़ेशन इंस्ट्रक्शन उसके विशिष्ट पैरामीटर्स को कॉन्फ़िगर करता है।
यदि आप extensions को इनेबल करने के लिए पहले बताई गई spl-token कमांड रन करते हैं, तो आपको इस तरह का एक आउटपुट दिखाई देगा:

इस स्तर (stage) पर:
- interest rate extension को
5की ब्याज दर के साथ पूरी तरह से इनिशियलाइज़ कर दिया गया है। - मेटाडेटा extension के पास एक रिज़र्व किया गया TLV स्लॉट है, लेकिन अभी तक कोई मेटाडेटा कंटेंट नहीं लिखा गया है।
आपको रिस्पॉन्स में यह मैसेज दिखाई देगा: “To initialize metadata inside the mint, please run spl-token initialize-metadata 5bL18vT46c7SkdN37F3pb1GdxsN8kTcZCPoRcYj6cS5w <YOUR_TOKEN_NAME> <YOUR_TOKEN_SYMBOL> <YOUR_TOKEN_URI>, and sign with the mint authority."
मेटाडेटा इनिशियलाइज़ेशन को पूरा करने और पहले से आवंटित (already-allocated) मेटाडेटा TLV ब्लॉक को पॉप्युलेट (populate) करने के लिए हम उस कमांड को रन करेंगे।
spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
initialize-metadata 5bL18vT46c7SkdN37F3pb1GdxsN8kTcZCPoRcYj6cS5w \
"MyToken" "MTKN" "https://example.com/mytoken.json"
इसका परिणाम कुछ इस तरह दिखेगा:

यदि आपने पहले --enable-metadata को छोड़ (omit) दिया होता, तो यह स्टेप फ़ेल हो जाता, क्योंकि एक बार mint के इनिशियलाइज़ होने के बाद कोई नया TLV स्पेस नहीं जोड़ा जा सकता। इसलिए, आपको इस तरह का एक एरर दिखाई देगा:

एक mint में extensions जोड़ने के लिए आवश्यक स्टेप्स का सारांश इस प्रकार है:
- Extension के लिए स्पेस आवंटित (allocate) करके extension को इनेबल करना।
- Extension को इनिशियलाइज़ करना। आप एक साथ कई extensions को इनिशियलाइज़ कर सकते हैं।
Interest-bearing extension जैसे extensions के लिए, जो आपको एक ही बार में आवश्यक पैरामीटर प्रदान करने की अनुमति देते हैं (जो हमने ऊपर --interest-rate 5 के साथ किया था), आप एक ही समय में extension को इनेबल और इनिशियलाइज़ करेंगे।
यहाँ कुछ टोकन extensions और वे फ्लैग्स (flags) दिए गए हैं जिनका उपयोग करके आप उन्हें CLI पर इनेबल कर सकते हैं।
| Extension | CLI Flag |
|---|---|
| Mint Close Authority | –enable-close |
| Transfer Fees | –transfer-fee-basis-points |
| Non-Transferable | –enable-non-transferable |
| Interest-Bearing | –interest-rate |
| Permanent Delegate | –enable-permanent-delegate |
| Transfer Hook | –transfer-hook |
| Metadata | –enable-metadata |
| Metadata Pointer | –metadata-address |
| Confidential Transfers | –enable-confidential-transfers auto |
Solana Token-2022 source code में ExtensionType enum सभी उपलब्ध extensions को परिभाषित करता है।
Extensions को मिलाना (Combining extensions)
आपने देखा कि जब हमने mint अकाउंट बनाया था, तो हमने Interest Bearing और Metadata extensions को कैसे जोड़ा (combine किया) था। आपने यह भी देखा कि extensions को इनेबल और इनिशियलाइज़ कैसे किया जाता है।
हालाँकि, सभी extensions को एक-दूसरे के साथ नहीं जोड़ा जा सकता है। उदाहरण के लिए, आप NonTransferable और TransferHook को नहीं मिला सकते, क्योंकि यदि टोकन ट्रांसफर नहीं हो सकते हैं, तो ट्रांसफर हुक (transfer hook) का कोई सार्थक काम नहीं रह जाता है। SVM और डेवलपमेंट टूल्स (development tools) इस नियम को लागू करते हैं, इसलिए इन दो extensions को मिलाने का प्रयास करने पर एग्ज़ीक्यूशन (execution) फ़ेल हो जाएगा।
साथ ही, आप ConfidentialTransfer और TransferHook extensions को नहीं मिला सकते क्योंकि कॉन्फिडेंशियल ट्रांसफर अमाउंट (amount) को एन्क्रिप्ट (encrypt) करते हैं और विजिबिलिटी को सीमित करते हैं। चूँकि ट्रांसफर हुक ट्रांसफर किए गए अमाउंट को पढ़ने पर निर्भर करते हैं, इसलिए दोनों एक साथ काम नहीं कर सकते।
यहाँ extensions के कुछ अन्य कॉम्बिनेशन्स दिए गए हैं जो एक साथ काम नहीं करेंगे:
- ConfidentialTransfer और TransferFeeConfig
- ConfidentialTransfer और PermanentDelegate
सारांश (Summary)
इससे पहले कि हम इस आर्टिकल के अगले भाग में जाएँ, आइए अब तक हमने जो चर्चा की है उसका सारांश देखें:
- Token अकाउंट के पहले 165 बाइट्स और Mint अकाउंट के पहले 82 बाइट्स दोनों ही Token-2022 में प्रिजर्व (preserve) रहते हैं।
- Extensions को एक TLV (Type-Length-Value) एन्कोडिंग फ़ॉर्मेट का उपयोग करके स्टोर किया जाता है।
- Token-2022 ओरिजिनल SPL Token प्रोग्राम के 25 इंस्ट्रक्शन्स में 20 नए इंस्ट्रक्शन्स जोड़ता है।
- अकाउंट बनाते समय (account creation) डेवलपर को उन सभी extensions के लिए पहले ही (upfront) पर्याप्त स्पेस आवंटित करना होगा जिन्हें वे इनेबल करना चाहते हैं।
- Mint बनाने से पहले Extensions को इनिशियलाइज़ किया जाना चाहिए।
ImmutableOwner और Non-Transferable Extensions
अब हम दो extensions: (ImmutableOwner और NonTransferable) पर चर्चा करेंगे और प्रदर्शित करेंगे कि क्रेडेंशियल इश्यूइंग प्रोग्राम (credential issuing program) बनाने के लिए हम उन्हें कैसे जोड़ सकते हैं।
Immutable Owner Extension
लिगेसी (legacy) Token प्रोग्राम आपको SetAuthority इंस्ट्रक्शन का उपयोग करके टोकन अकाउंट के मालिक (owner) को दूसरे अकाउंट में बदलने की अनुमति देता है। Token-2022 आपको ImmutableOwner extension के साथ मालिक को अपरिवर्तनीय (unchangeable) बनाने में सक्षम बनाता है। ImmutableOwner extension किसी टोकन अकाउंट के स्वामित्व (ownership) को स्थायी रूप से लॉक कर देता है। इसका मतलब है, एक बार इस extension के साथ टोकन अकाउंट बन जाने के बाद, वह स्थायी रूप से उसी निर्दिष्ट वॉलेट (specified wallet) का रहेगा।
यहाँ एक ऐसा परिदृश्य (scenario) दिया गया है जहाँ ImmutableOwner extension फिशिंग हमलों (phishing attacks) को रोकता है जो लिगेसी Token प्रोग्राम के साथ हो सकते थे, जो कि अकाउंट बनाने के बाद स्वामित्व में बदलाव की अनुमति देता है।
लिगेसी Token प्रोग्राम में (बिना ImmutableOwner के):
- एलिस (Alice) अपने वॉलेट एड्रेस और एक विशिष्ट mint से प्राप्त,
AliceTokenAccountनामक एक एसोसिएटेड टोकन अकाउंट (ATA) बनाती है। - उसे धोखे से एक ऐसा ट्रांजैक्शन (transaction) साइन करवा लिया जाता है जो उस ATA का स्वामित्व बॉब (Bob) को ट्रांसफर कर देता है।
- बाद में, एक एप्लीकेशन स्टैण्डर्ड डेरिवेशन फ़ंक्शन (standard derivation function) का उपयोग करके एलिस के ATA एड्रेस की गणना करती है। यह फ़ंक्शन अभी भी
AliceTokenAccountरिटर्न करता है क्योंकि यह स्वामित्व में हुए बदलावों की जाँच नहीं करता है। - एप्लीकेशन यह मानकर उस एड्रेस पर टोकन भेजती है कि एलिस उसकी मालिक है। लेकिन अब बॉब उसे कंट्रोल करता है, इसलिए बॉब को वे टोकन प्राप्त हो जाते हैं।
- एलिस के “ATA” में होने वाले कोई भी भविष्य के ट्रांसफर भी बॉब को ही जाएँगे।
Token-2022 के ImmutableOwner extension के साथ (जो ATA पर डिफ़ॉल्ट रूप से इनेबल होता है):
- चरण 2 में ट्रांजैक्शन फ़ेल हो जाएगा - स्वामित्व नहीं बदला जा सकता
- दुर्भावनापूर्ण (malicious) ट्रांजैक्शन्स पर साइन करने के लिए धोखा दिए जाने पर भी एलिस का अपने ATA पर कंट्रोल बना रहता है।
- उसके प्राप्त (derived) ATA एड्रेस पर भेजे गए टोकन हमेशा उसी तक पहुँचेंगे
यहाँ बताया गया है कि Token-2022 ImmutableOwner extension के साथ इस प्रतिबंध (constraint) को कैसे लागू करता है
जब एक SetAuthority इंस्ट्रक्शन (वह इंस्ट्रक्शन जिसका उपयोग लिगेसी SPL टोकन प्रोग्राम में टोकन अकाउंट स्वामित्व बदलने के लिए किया जाता है) एग्ज़ीक्यूट किया जाता है, तो यह process_set_authority फ़ंक्शन को कॉल करता है जो मौजूद authority type की जाँच करता है। यदि authority type AuthorityType::AccountOwner है, तो यह ImmutableOwner चेक ट्रिगर करता है, एक एरर रिटर्न करता है और स्वामित्व परिवर्तन (ownership change) को ब्लॉक कर देता है:

यह इम्प्लीमेंटेशन GitHub पर Token-2022 प्रोग्राम के कोर लॉजिक (core logic) में पाया जा सकता है।
Token-2022 प्रोग्राम के साथ बनाए गए ATA के लिए ImmutableOwner extension हमेशा डिफ़ॉल्ट रूप से इनेबल होता है।
जब आप Token-2022 के साथ स्टैण्डर्ड ATA प्रोग्राम का उपयोग करके एक ATA बनाते हैं, तो ATA प्रोग्राम अकाउंट इनिशियलाइज़ेशन प्रक्रिया में स्वचालित रूप से (automatically) ImmutableOwner extension को शामिल कर लेता है।
यह इस बात की परवाह किए बिना होता है कि आप इसे स्पष्ट रूप से (explicitly) निर्दिष्ट करते हैं या नहीं - यह Token-2022 अकाउंट्स के लिए ATA प्रोग्राम के लॉजिक में अंतर्निहित (built-in) है। यह सिस्टम createAccount इंस्ट्रक्शन के साथ मैन्युअल रूप से बनाए गए टोकन अकाउंट्स से अलग है, जहाँ यदि आप चाहें तो आपको स्पष्ट रूप से ImmutableOwner extension को इनिशियलाइज़ करने की आवश्यकता होगी।
यह डिज़ाइन सुनिश्चित करता है कि सबसे सामान्य टोकन अकाउंट पैटर्न (ATAs) को डिफ़ॉल्ट रूप से अपरिवर्तनीय स्वामित्व (immutable ownership) के सुरक्षा लाभ (security benefits) प्राप्त हों।
Non-Transferable Extension
NonTransferable extension टोकन ट्रांसफर्स को पूरी तरह से डिसेबल कर देता है। आप अभी भी अकाउंट्स में टोकन mint कर सकते हैं, लेकिन एक बार प्राप्त होने के बाद, उन्हें कहीं और नहीं भेजा जा सकता है।
ऐसे अकाउंट्स पर एकमात्र मान्य (valid) ऑपरेशन टोकन को बर्न (burn) करना (Ethereum बैकग्राउंड से आने वालों के लिए, Solana टोकन बर्निंग को ट्रांसफरिंग से अलग एक सेपरेट ऑपरेशन मानता है) या खाली अकाउंट्स को बंद करना (close) है।
यह उन सिस्टम्स के निर्माण के लिए उपयोगी है जहाँ टोकन non-transferable एसेट्स (assets) का प्रतिनिधित्व करते हैं। उदाहरण के लिए, सर्टिफिकेट्स (certificates) या ऑन-चेन आइडेंटिटी मार्कर्स को यूज़र्स के बीच नहीं ले जाया जाना चाहिए, या कोई ऋण (debt) जो किसी यूज़र को किसी प्रोटोकॉल को चुकाना है। ऐसे मामलों में, आप NonTransferable extension के साथ NFT जारी करेंगे।
Token-2022 प्रोग्राम लॉजिक में इस प्रतिबंध को लागू करता है। यदि आप किसी ऐसे अकाउंट से ट्रांसफर करने का प्रयास करते हैं जिसमें non-transferable टोकन हैं, तो इंस्ट्रक्शन एक एरर के साथ फ़ेल हो जाएगा।
आप इस चेक को processor.rs में देख सकते हैं, जहाँ प्रोग्राम NonTransferableAccount extension के लिए अकाउंट का निरीक्षण करता है और यदि यह मौजूद है तो NonTransferable एरर के साथ ट्रांसफर को रिजेक्ट (reject) कर देता है।

Non-transferable टोकन केवल immutable owners वाले अकाउंट्स पर ही mint किए जा सकते हैं। इसे अकाउंट स्वामित्व परिवर्तन (account ownership changes) के माध्यम से होने वाले अप्रत्यक्ष (indirect) टोकन ट्रांसफर को रोकने के लिए डिज़ाइन किया गया है।
जब कोई mint NonTransferable extension के साथ बनाया जाता है, तो उस mint के लिए बनाए गए कोई भी टोकन अकाउंट्स स्वचालित रूप से दो extensions इनहेरिट (inherit) कर लेंगे: NonTransferableAccount (जो ट्रांसफर को रोकता है) और ImmutableOwner (जो स्वामित्व परिवर्तन को रोकता है)। यहाँ बताया गया है कि Token-2022 प्रोग्राम लॉजिक में इसे कैसे लागू किया जाता है और इसे उसी फ़ाइल में पाया जा सकता है जिसका हमने पहले GitHub पर उल्लेख किया था।
if mint.get_extension::<NonTransferable>().is_ok()
&& destination_account.get_extension::<ImmutableOwner>().is_err()
{
return Err(TokenError::NonTransferableNeedsImmutableOwnership.into());
}
उदाहरण: एक मिनिमल क्रेडेंशियल इश्यूइंग प्रोग्राम बनाने के लिए ImmutableOwner और NonTransferable extensions का उपयोग करना
इस सेक्शन (section) में शामिल होगा:
- एक ऐसे mint को कैसे परिभाषित करें जिसमें
ImmutableOwnerऔरNonTransferableदोनों extensions हों। - प्राप्तकर्ताओं (recipients) को non-transferable क्रेडेंशियल टोकन कैसे जारी (issue) करें।
- और कैसे Token-2022 प्रोग्राम लॉजिक यह सुनिश्चित करता है कि क्रेडेंशियल्स को ट्रांसफर नहीं किया जा सकता।
अस्वीकरण (Disclaimer): यहाँ बताया गया Anchor प्रोग्राम केवल शैक्षिक उद्देश्य (educational purpose) के लिए है। प्रोडक्शन के लिए, सुनिश्चित करें कि आपके प्रोग्राम्स में पूर्ण वैलिडेशन, सिक्योरिटी चेक्स (security checks) शामिल हों और उनका रिव्यू (review) किया गया हो।
प्रोजेक्ट सेटअप
anchor init credentials कमांड रन करके एक नया Anchor प्रोजेक्ट बनाएँ। हम अपना सारा कोड programs/src/lib.rs फ़ाइल में लिखेंगे।
कॉन्फ़िगरेशन (Configuration)
programs/src/Cargo.toml खोलें और [features] और [dependencies] सेक्शन्स को अपडेट करें।
[features] सेक्शन में, idl-build जोड़ें और इसे anchor-lang और anchor-spl में संबंधित सब-फ़ीचर्स (sub-features) से लिंक करें।
[dependencies] सेक्शन में, anchor-lang और anchor-spl जोड़ें। सुनिश्चित करें कि anchor-lang के लिए init-if-needed फ़ीचर शामिल हो—हमें बाद में एसोसिएटेड टोकन अकाउंट (ATAs) बनाने के लिए इसकी आवश्यकता होगी।
...
[features]
anchor-debug = []
cpi = ["no-entrypoint"]
default = []
**idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]**
no-entrypoint = []
no-log-ix-name = []
custom-heap = []
custom-panic = []
no-idl = []
[dependencies]
**anchor-lang = {version = "0.31.0", features = ["init-if-needed"]}
anchor-spl = {version = "0.31.0"}**
ये डिपेंडेंसीज़ (dependencies) ही एकमात्र ऐसी हैं जिन्हें हम इस पूरे उदाहरण में इम्पॉर्ट (import) और उपयोग करेंगे।
// Import necessary modules from the Anchor framework and SPL token program.
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_2022_extensions::non_transferable_mint_initialize,
token_interface::{
mint_to,
Mint,
TokenAccount,
TokenInterface,
},
};
Mint सेटअप
अपना mint अकाउंट बनाने के लिए, हम इसे NonTransferable extension के साथ इनिशियलाइज़ करते हैं। यह extension केवल स्वयं mint पर लागू होता है। बाद में, जब Token-2022 ATA प्रोग्राम के माध्यम से इस mint के लिए एक एसोसिएटेड टोकन अकाउंट बनाया जाता है, तो ATA प्रोग्राम नए टोकन अकाउंट में स्वचालित रूप से ImmutableOwner और NonTransferableAccount extensions जोड़ देता है, जैसा कि हमने पहले चर्चा की थी।
नीचे दिया गया कोड दिखाता है कि हम mint अकाउंट को कैसे इनिशियलाइज़ करेंगे।
- यह सबसे पहले
NonTransferableMintInitializeइंस्ट्रक्शन के साथNonTransferableextension को इनिशियलाइज़ करता है। - और फिर यह
initialize_mint2इंस्ट्रक्शन का उपयोग करके मान्य authorities के साथ स्वयं mint अकाउंट को इनिशियलाइज़ करता है।
// Import necessary modules from the Anchor framework and SPL token program.
use anchor_lang::prelude::*;
use anchor_spl::{
associated_token::AssociatedToken,
token_2022_extensions::non_transferable_mint_initialize,
token_interface::{
mint_to,
Mint,
TokenAccount,
TokenInterface,
},
};
/// Initializes a new mint for the credentials
/// with NonTransferable extension.
pub fn initialize_credential_mint(ctx: Context<InitializeCredentialMint>) -> Result<()> {
// Initialize the NonTransferable extension.
non_transferable_mint_initialize(CpiContext::new(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_2022_extensions::NonTransferableMintInitialize {
mint: ctx.accounts.mint.to_account_info(),
token_program_id: ctx.accounts.token_program.to_account_info(),
},
))?;
// Initialize the mint itself, setting decimals to 0 and defining authorities.
anchor_spl::token_interface::initialize_mint2(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_interface::InitializeMint2 {
mint: ctx.accounts.mint.to_account_info(),
},
),
0, // Decimals are set to 0 because credentials are whole units and cannot be fractional.
&ctx.accounts.mint.key(), // The mint authority is the program-derived address (PDA) itself.
Some(&ctx.accounts.mint.key()), // The freeze authority is also the PDA.
)?;
Ok(())
}
हम नीचे दिए गए struct में परिभाषित अकाउंट्स का उपयोग करके mint अकाउंट को इनिशियलाइज़ करेंगे:
-
Struct mint के लिए 98 बाइट्स स्पेस आवंटित करता है
- अकाउंट डिस्क्रिमिनेटर (account discriminator) के लिए 8 बाइट्स
- mint के बारे में बुनियादी जानकारी, जैसे इसकी कुल सप्लाई (total supply), डेसिमल की संख्या (number of decimals), और किसके पास अधिक टोकन बनाने की authority है, को स्टोर करने के लिए 82 बाइट्स।
- और
NonTransferableextension के लिए अन्य 8 बाइट्स।
व्यवहार में, हम डायनामिक रूप से साइज़ की गणना करने के लिए
ExtensionType::try_calculate_account_len::<PodMint>(&[ExtensionType::NonTransferable])?;मेथड का उपयोग करेंगे।
मैंने इसके बाकी हिस्से को बेहतर ढंग से समझने के लिए कोड में कुछ कमैंट्स (comments) छोड़े हैं:
/// Defines the accounts required for the `initialize_credential_mint` instruction.
#[derive(Accounts)]
pub struct InitializeCredentialMint<'info> {
// The mint account to be initialized as a Program Derived Address (PDA).
#[account(
init,
payer = payer,
// The space allocation for the account's data:
// 8 bytes: for the account discriminator, a unique identifier for the account type in Anchor.
// 82 bytes: the standard fixed size of a SPL Token Mint account.
// 8 bytes: additional space reserved for the NonTransferable extension.
space = 8 + 82 + 8,
owner = token_program.key(),
// Defines the seeds for the Program Derived Address (PDA).
seeds = [b"mint"],
bump
)]
pub mint: InterfaceAccount<'info, Mint>,
// The account paying for the transaction and rent.
#[account(mut)]
pub payer: Signer<'info>,
// System program, required for creating accounts.
pub system_program: Program<'info, System>,
// The SPL token program.
pub token_program: Interface<'info, TokenInterface>,
}
क्रेडेंशियल्स जारी करना (Issuing credentials)
अब जब हमने अपना mint अकाउंट बना लिया है, तो चलिए क्रेडेंशियल्स जारी करना शुरू करते हैं। हम ठीक एक टोकन (क्रेडेंशियल टोकन) mint करेंगे और उसे यूज़र को भेजेंगे।
pub fn issue_credential(ctx: Context<IssueCredential>) -> Result<()> {
// Mint one token to the recipient's associated token account.
mint_to(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
anchor_spl::token_interface::MintTo {
mint: ctx.accounts.mint.to_account_info(),
to: ctx.accounts.recipient_ata.to_account_info(),
authority: ctx.accounts.authority.to_account_info(),
},
),
1, // Mint exactly one token.
)?;
Ok(())
}
issue_credential फ़ंक्शन अपने कॉन्टेक्स्ट (context) के रूप में IssueCredential struct की अपेक्षा करता है। क्रेडेंशियल जारी करते समय, हम Token-2022 एसोसिएटेड टोकन अकाउंट (ATA) प्रोग्राम का उपयोग करके यूज़र का टोकन अकाउंट बनाते हैं। यह स्वचालित रूप से अकाउंट पर ImmutableOwner extension लागू करता है। चूँकि mint को NonTransferable के रूप में चिह्नित किया गया है, इसलिए यूज़र के ATA पर भी NonTransferableAccount extension अंतर्निहित (implicitly) रूप से लागू होगा।
/// Defines the accounts required for the `issue_credential` instruction.
#[derive(Accounts)]
pub struct IssueCredential<'info> {
// The mint account, must be mutable.
#[account(
mut,
seeds = [b"mint"],
bump,
constraint = mint.mint_authority.unwrap() == authority.key() // Ensure the authority is the mint authority.
)]
pub mint: InterfaceAccount<'info, Mint>,
// The authority signing the transaction (must be the mint authority).
#[account(mut)]
pub authority: Signer<'info>,
// The recipient's associated token account, created if it doesn't exist.
#[account(
init_if_needed,
payer = authority,
associated_token::mint = mint,
associated_token::authority = recipient,
associated_token::token_program = token_program
)]
pub recipient_ata: InterfaceAccount<'info, TokenAccount>,
// The recipient of the credential.
pub recipient: Signer<'info>,
// The SPL token program.
pub token_program: Interface<'info, TokenInterface>,
// The associated token program.
pub associated_token_program: Program<'info, AssociatedToken>,
// The system program.
pub system_program: Program<'info, System>,
}
इसलिए, इस इम्प्लीमेंटेशन के साथ यह करना असंभव होगा:
- क्रेडेंशियल टोकन ट्रांसफर करना: ट्रांसफर
TokenError::NonTransferableके साथ फ़ेल हो जाएगा। हमने इस एरर को पहले Token-2022 प्रोग्राम सोर्स कोड में देखा है। - टोकन अकाउंट के मालिक को बदलना: अकाउंट अपरिवर्तनीय (immutable) है और बदलाव फ़ेल हो जाएगा।
इस प्रोग्राम का पूरा सोर्स कोड GitHub पर पाया जा सकता है।
निष्कर्ष
Token-2022 Solana पर बिल्डिंग (building) को अधिक लचीला (flexible) बनाता है। हमने Token-2022 के आर्किटेक्चर और यह कैसे ओरिजिनल SPL टोकन प्रोग्राम के साथ बैकवर्ड कम्पैटिबिलिटी (backward compatibility) बनाए रखता है, इसके बारे में सीखा।
हमने देखा कि कैसे extensions का उपयोग टोकन में नया व्यवहार (behavior) जोड़ने के लिए किया जाता है। जहाँ extensions का एक साथ उपयोग किया जा सकता है, वहीं अन्य कॉम्बिनेशन्स की अनुमति नहीं है।
और अंत में, हमने एक क्रेडेंशियल इश्यूइंग प्रोग्राम बनाया जो NonTransferable और ImmutableOwner extensions को मिलाता है। इससे पता चला कि कैसे एक mint पर NonTransferable को इनेबल करने से टोकन को ट्रांसफर होने से रोका जा सकता है, जो इस बात पर सख्त नियंत्रण लागू करता है कि क्रेडेंशियल्स कैसे रखे जाते हैं।
यह आर्टिकल Solana पर ट्यूटोरियल सीरीज़ (tutorial series) का हिस्सा है।