Ethereum पर, डिफ़ॉल्ट रूप से अकाउंट्स Externally Owned Accounts (EOAs) होते हैं। प्रत्येक अकाउंट एक प्राइवेट की (private key) द्वारा नियंत्रित होता है, और यदि इससे समझौता हो जाता है (compromised), तो नुकसान को सीमित करने या एक्सेस रद्द करने का कोई तरीका नहीं है। यदि की (key) खो जाती है, तो अकाउंट हमेशा के लिए चला जाता है।
EOAs अपने काम करने के तरीके में भी कठोर (rigid) होते हैं। कई ऑपरेशन्स को एक ही ट्रांजेक्शन में बंडल करने के लिए कोई नेटिव सपोर्ट नहीं है। यूज़र्स को फीस चुकाने के लिए नेटिव गैस टोकन (gas token) रखना पड़ता है, भले ही वे वास्तव में किसी भी टोकन का उपयोग कर रहे हों। और इसमें मल्टी-पार्टी अप्रूवल, की रिकवरी (key recovery) आदि जैसे नियम जोड़ने का कोई इन-बिल्ट तरीका नहीं है।
Starknet में कोई EOAs नहीं हैं। हर अकाउंट एक स्मार्ट कॉन्ट्रैक्ट है, जिसे अकाउंट कॉन्ट्रैक्ट (account contract) के रूप में जाना जाता है।
हर अकाउंट को स्मार्ट कॉन्ट्रैक्ट बनाना ही Account Abstraction (AA) है, और यह अकाउंट लॉजिक को प्रोग्रामेबल बनाकर उपरोक्त सीमाओं को हल करता है।
इस आर्टिकल में, हम जानेंगे कि Account Abstraction क्या है, Starknet का दृष्टिकोण Ethereum की तुलना में कैसा है, यह कौन सी सुविधाएँ प्रदान करता है, और Starknet पर एक अकाउंट कॉन्ट्रैक्ट कैसे बनाया जाता है।
Account Abstraction क्या है?
Account Abstraction (AA) एक ब्लॉकचेन डिज़ाइन पैटर्न है जो अकाउंट्स को प्रोग्रामेबल बनाता है। इसके बजाय कि प्रोटोकॉल यह तय करे कि ट्रांजेक्शन्स को कैसे वैलिडेट और एक्ज़ीक्यूट किया जाए, अकाउंट स्वयं उस लॉजिक को परिभाषित करता है। Starknet पर, इसका मतलब है कि सभी ट्रांजेक्शन्स को एक्ज़ीक्यूट होने से पहले वैलिडेशन के लिए अकाउंट कॉन्ट्रैक्ट से गुजरना होगा।
क्योंकि अकाउंट लॉजिक प्रोग्रामेबल है, इसलिए रिकवरी ऑप्शन्स, ट्रांजेक्शन बैचिंग (transaction batching), किसी भी टोकन में गैस पेमेंट, और फाइन-ग्रेन्ड परमिशन्स (fine-grained permissions) जैसी सुविधाएँ संभव हो जाती हैं।
Account Abstraction को नेटिव माना जाता है जब प्रोग्रामेबल अकाउंट को सीधे प्रोटोकॉल स्तर पर लागू किया जाता है। वैकल्पिक रूप से, इसे मौजूदा प्रोटोकॉल्स के ऊपर एक लेयर के रूप में रखा जा सकता है, लेकिन इस तरह के इम्प्लीमेंटेशन्स नेटिव Account Abstraction के सभी लाभ नहीं देते हैं। Starknet नेटिव अप्रोच अपनाता है, जो शुरुआत से ही प्रोटोकॉल में इन-बिल्ट है।
Account Abstraction, EOAs से कैसे अलग है
EOAs के साथ, प्राइवेट की (signer) और अकाउंट मजबूती से जुड़े (tightly coupled) होते हैं। अकाउंट एड्रेस प्राइवेट की से प्राप्त (derived) होता है, और वही की ट्रांजेक्शन्स को साइन करने का एकमात्र तरीका है। प्राइवेट की ही अकाउंट है।
Account Abstraction के साथ, साइनर (signer) और अकाउंट कॉन्ट्रैक्ट अलग-अलग (decoupled) होते हैं। यह अलगाव ही अकाउंट को प्रोग्रामेबल बनाता है: क्योंकि अकाउंट अब प्राइवेट की से बंधा नहीं है, यह लॉजिक कस्टमाइज़ किया जा सकता है कि कौन ट्रांजेक्शन्स को ऑथराइज़ कर सकता है और उन्हें कैसे एक्ज़ीक्यूट किया जाता है। Starknet पर, यह इस प्रकार काम करता है:
- साइनर एक प्राइवेट की है, जो यूज़र के डिवाइस पर वॉलेट सॉफ़्टवेयर द्वारा रखी जाती है, जिसका उपयोग यूज़र द्वारा अप्रूव किए जाने पर ट्रांजेक्शन्स को साइन करने के लिए किया जाता है।
- अकाउंट कॉन्ट्रैक्ट ऑनचेन (onchain) डिप्लॉय किया गया एक स्मार्ट कॉन्ट्रैक्ट है जो यूज़र के एसेट्स को होल्ड करता है और ट्रांजेक्शन्स को वैलिडेट और एक्ज़ीक्यूट करने के लॉजिक को परिभाषित करता है।
जब कोई यूज़र कोई एक्शन शुरू करता है, तो वॉलेट ट्रांजेक्शन डिटेल्स को ऑफ-चेन साइन करने के लिए प्राइवेट की का उपयोग करता है और एक सिग्नेचर (signature) तैयार करता है जो यह साबित करता है कि यूज़र ने ट्रांजेक्शन को ऑथराइज़ किया है। इस सिग्नेचर के साथ ट्रांजेक्शन फिर सीक्वेंसर (sequencer - वह नोड जो ट्रांजेक्शन्स को ऑर्डर करता है और Starknet पर ब्लॉक्स बनाता है) को भेजा जाता है, जो इसे यूज़र के अकाउंट कॉन्ट्रैक्ट को फॉरवर्ड करता है। अकाउंट कॉन्ट्रैक्ट ट्रांजेक्शन डिटेल्स के खिलाफ सिग्नेचर को वेरिफाई करता है और, यदि यह वैध (valid) है, तो ट्रांजेक्शन को एक्ज़ीक्यूट करता है।
चूँकि वैलिडेशन लॉजिक प्रोटोकॉल स्तर के बजाय अकाउंट कॉन्ट्रैक्ट के अंदर रहता है, इसलिए अकाउंट का मालिक यह परिभाषित करने के लिए स्वतंत्र है कि “valid” का क्या अर्थ है। अकाउंट कॉन्ट्रैक्ट में एक सिंगल की सिग्नेचर, मल्टीपल की सिग्नेचर्स, या कोई भी कस्टम वेरिफिकेशन लॉजिक आवश्यक हो सकता है जिसे डेवलपर लागू करना चुनता है।
Ethereum में भी Account Abstraction है
Account Abstraction कोई नई बात नहीं है। 2017 की शुरुआत में, Vitalik Buterin ने light account contracts का प्रस्ताव रखा था, कि अकाउंट्स केवल प्रोटोकॉल-स्तर के सिग्नेचर चेक पर निर्भर रहने के बजाय अपने स्वयं के वैलिडेशन लॉजिक के साथ कॉन्ट्रैक्ट हो सकते हैं।
तब से, इसने Ethereum इकोसिस्टम में कर्षण (traction) प्राप्त किया है। आज सबसे मैच्योर इम्प्लीमेंटेशन EIP-4337 है, जो Ethereum प्रोटोकॉल में ही बदलाव की आवश्यकता के बिना स्मार्ट कॉन्ट्रैक्ट वॉलेट्स पेश करता है। EIP-4337 को मेननेट (mainnet) पर डिप्लॉय किया गया है, जिसमें कई प्रोडक्शन-रेडी इम्प्लीमेंटेशन्स हैं।
हाल ही में, EIP-7702, जिसे Pectra अपग्रेड के हिस्से के रूप में पेश किया गया था, Account Abstraction को प्रोटोकॉल स्तर के करीब लाया। यह EOAs को पहले से डिप्लॉय किए गए स्मार्ट कॉन्ट्रैक्ट को पॉइंट करने की अनुमति देता है, जिससे उन्हें नए एड्रेस पर माइग्रेट किए बिना ट्रांजेक्शन बैचिंग और गैस एब्स्ट्रैक्शन जैसी स्मार्ट अकाउंट क्षमताएं मिलती हैं। यह प्रक्रिया, जिसे डेलिगेशन (delegation) के रूप में जाना जाता है, वैकल्पिक (optional) है और तब तक बनी रहती है जब तक कि यूज़र शून्य एड्रेस (zero address) पर डेलिगेट करके इसे हटा नहीं देता।
जब आप Starknet अकाउंट बनाते हैं तो क्या होता है
जब भी आप अपने वॉलेट सॉफ़्टवेयर के साथ एक नया अकाउंट बनाते हैं, तो वॉलेट द्वारा एक नया अकाउंट कॉन्ट्रैक्ट डिप्लॉय किया जाता है। जैसा कि हमने “Understanding Starknet’s Contract Deployment Model” चैप्टर में कवर किया था, एक ही क्लास (class) से कई कॉन्ट्रैक्ट इंस्टेंसेस बनाए जा सकते हैं, जो एक ही कोड शेयर करते हैं लेकिन प्रत्येक का अपना एड्रेस और स्टोरेज होता है। वॉलेट प्रोवाइडर्स एक बार Starknet पर अपनी अकाउंट क्लास डिक्लेयर (declare) करते हैं, और बनाया गया हर नया वॉलेट उसी क्लास का एक नया इंस्टेंस होता है।
अकाउंट कॉन्ट्रैक्ट का कंस्ट्रक्टर यूज़र की पब्लिक की (public key) जैसे पैरामीटर्स के साथ इनिशियलाइज़ (initialize) किया जाता है, और परिणामी अकाउंट एड्रेस डिटरमिनिस्टिक (deterministic) होता है: यह क्लास हैश (class hash), एक साल्ट (salt), डिप्लॉयर एड्रेस और कंस्ट्रक्टर calldata सहित कई इनपुट्स से प्राप्त (derived) होता है।
याद रखें कि अकाउंट डिप्लॉयमेंट से पहले ही एसेट्स प्राप्त कर सकता है क्योंकि इसका एड्रेस डिटरमिनिस्टिक है, लेकिन यह तब तक किसी भी ट्रांजेक्शन को एक्ज़ीक्यूट नहीं कर सकता जब तक कि अकाउंट कॉन्ट्रैक्ट डिप्लॉय नहीं हो जाता।
एक बार डिप्लॉय हो जाने के बाद, अकाउंट कॉन्ट्रैक्ट अपने लॉजिक से बंधा (bound) होता है, और किसी दूसरे अकाउंट कॉन्ट्रैक्ट पर स्विच करने का मतलब है पूरी तरह से एक नया अकाउंट बनाना।
अकाउंट कॉन्ट्रैक्ट्स के बीच स्विच करना
सिद्धांत रूप में, कोई भी अपना स्वयं का अकाउंट कॉन्ट्रैक्ट लागू (implement) करने के लिए स्वतंत्र है। प्रोटोकॉल स्तर पर, आवश्यकताएं हैं:
__validate__और__execute__को लागू करना, वे एंट्रीपॉइंट्स जिन्हें प्रोटोकॉल ट्रांजेक्शन लाइफसाइकिल के दौरान कॉल करता है- इच्छित क्षमताओं के आधार पर,
__validate_declare__या__validate_deploy__को लागू करना - SNIP-6 standard का पालन करना, जो dApps और ऑफ-चेन सिग्नेचर वेरिफिकेशन के साथ इंटरऑपरेबिलिटी (interoperability) के लिए
is_valid_signatureफंक्शन जोड़ता है
हम अगले सेक्शन में इनमें से प्रत्येक फंक्शन की व्याख्या करेंगे।
हालाँकि, व्यवहार में, कस्टम अकाउंट कॉन्ट्रैक्ट इम्प्लीमेंटेशन का उपयोग करना मुश्किल है। भले ही आप मानक (standard) का पालन करें और तकनीकी रूप से सब कुछ काम करता हो, फिर भी आपको किसी वॉलेट सॉफ़्टवेयर के माध्यम से अकाउंट कॉन्ट्रैक्ट का उपयोग करना होगा।
चूँकि कोई भी न्यूनतम Account Abstraction आवश्यकताओं के ऊपर कार्यक्षमता जोड़ने के लिए स्वतंत्र है, इसलिए अधिकांश लोकप्रिय वॉलेट्स (जैसे कि Ready) के अपने स्वयं के अकाउंट कॉन्ट्रैक्ट इम्प्लीमेंटेशन्स होते हैं। ये इंटरचेंजेबल (interchangeable) नहीं हैं। यह उनके बीच स्विच करना या अपने स्वयं के इम्प्लीमेंटेशन का उपयोग करना मुश्किल बनाता है।
एक अकाउंट कॉन्ट्रैक्ट की संरचना (Anatomy)
आइए एक बहुत ही न्यूनतम अकाउंट कॉन्ट्रैक्ट इम्प्लीमेंटेशन को देखें। इसे जानबूझकर सरल बनाया गया है और यह अत्यधिक असुरक्षित (highly insecure) है, यह बिना किसी जाँच के सभी ट्रांजेक्शन्स और सिग्नेचर्स को अप्रूव कर देता है। लक्ष्य एक Starknet अकाउंट कॉन्ट्रैक्ट के लिए आवश्यक न्यूनतम संरचना (minimal structure) को स्पष्ट करना है, और यह केवल प्रदर्शन (demonstration) के उद्देश्यों के लिए प्रदान किया गया है, प्रोडक्शन उपयोग के लिए नहीं।
एक नया Scarb प्रोजेक्ट बनाएँ और उसमें नेविगेट करें:
scarb new aa
cd aa
सबसे पहले, हम src/lib.cairo में Call स्ट्रक्ट (struct) को इम्पोर्ट करते हैं। यह स्ट्रक्ट एक ट्रांजेक्शन में एक सिंगल ऑपरेशन को दर्शाता है। प्रत्येक Call में एक टारगेट कॉन्ट्रैक्ट एड्रेस, एक फंक्शन सेलेक्टर, और पास करने के लिए calldata होता है। हमारा अकाउंट कॉन्ट्रैक्ट इसका उपयोग यह जानने के लिए करेगा कि किस कॉन्ट्रैक्ट को कॉल करना है और किस डेटा के साथ।
use starknet::account::Call;
इसके बाद, हम SNIP-6 इंटरफ़ेस (ISRC6) को परिभाषित करते हैं जिसे प्रत्येक अकाउंट कॉन्ट्रैक्ट द्वारा लागू किए जाने की अपेक्षा की जाती है। इसके तीन फंक्शंस हैं:
- ट्रांजेक्शन वैलिडेशन के लिए
__validate__, - ट्रांजेक्शन एक्ज़ीक्यूशन के लिए
__execute__, और - ऑफ-चेन सिग्नेचर वेरिफिकेशन के लिए
is_valid_signature:
use starknet::account::Call;
//////NEWLY ADDED///////
#[starknet::interface]
trait ISRC6<T> {
fn __validate__(self: @T, calls: Array<Call>) -> felt252;
fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
}
अब चलिए स्वयं अकाउंट कॉन्ट्रैक्ट को परिभाषित करते हैं। #[starknet::contract(account)] एट्रिब्यूट (attribute) कंपाइलर को बताता है कि यह एक अकाउंट कॉन्ट्रैक्ट है, जो प्रोटोकॉल को ट्रांजेक्शन प्रोसेसिंग के दौरान इसके __validate__ और __execute__ एंट्रीपॉइंट्स को कॉल करने में सक्षम बनाता है। इस एट्रिब्यूट के बिना, कॉन्ट्रैक्ट को एक रेगुलर स्मार्ट कॉन्ट्रैक्ट माना जाएगा और इसे अकाउंट के रूप में उपयोग नहीं किया जा सकेगा। हम call_contract_syscall भी इम्पोर्ट करते हैं जिसका उपयोग हम अन्य कॉन्ट्रैक्ट्स को कॉल करने के लिए __execute__ में करेंगे, और उन कॉल्स के परिणाम को संभालने के लिए SyscallResultTrait इम्पोर्ट करते हैं:
use starknet::account::Call;
#[starknet::interface]
trait ISRC6<T> {
fn __validate__(self: @T, calls: Array<Call>) -> felt252;
fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
}
//////NEWLY ADDED///////
#[starknet::contract(account)]
mod Account {
use starknet::SyscallResultTrait;
use starknet::syscalls::call_contract_syscall;
use super::Call;
#[storage]
struct Storage {}
}
अब चलिए प्रत्येक फंक्शन को लागू (implement) करते हैं।
__validate__ फंक्शन
प्रोटोकॉल किसी ट्रांजेक्शन को एक्ज़ीक्यूट करने से पहले इस फंक्शन को कॉल करता है। एक प्रोडक्शन अकाउंट कॉन्ट्रैक्ट में, यह वह जगह है जहाँ ट्रांजेक्शन के सिग्नेचर को वेरिफाई किया जाता है ताकि यह पुष्टि हो सके कि कॉलर ऑथराइज़्ड है। हमारे इम्प्लीमेंटेशन में, यह बिना किसी जाँच के हर ट्रांजेक्शन के लिए केवल 'VALID' लौटाता है:
#[abi(embed_v0)]
impl AccountImpl of super::ISRC6<ContractState> {
fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
'VALID'
}
}
__execute__ फंक्शन
__validate__ पास होने के बाद प्रोटोकॉल इस फंक्शन को कॉल करता है। यह कॉल्स (calls) का एक ऐरे (array) प्राप्त करता है और call_contract_syscall का उपयोग करके, जिस क्रम में वे दिखाई देते हैं, उन्हें क्रमिक रूप से (sequentially) एक्ज़ीक्यूट करता है। यही multicall को सक्षम बनाता है, जो एक ही ट्रांजेक्शन में कई ऑपरेशन्स को एक्ज़ीक्यूट करने की क्षमता है:
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
let mut results = ArrayTrait::new();
for call in calls {
let result = call_contract_syscall(call.to, call.selector, call.calldata)
.unwrap_syscall();
results.append(result);
}
results
}
is_valid_signature फंक्शन
यह फंक्शन प्रोटोकॉल द्वारा कॉल नहीं किया जाता है। यह ऑफ-चेन सिग्नेचर वेरिफिकेशन के लिए मौजूद है और dApps को यह पुष्टि करने की अनुमति देता है कि एक सिग्नेचर इस अकाउंट का है। यहाँ यह बिना कुछ चेक किए 'VALID' लौटाता है:
fn is_valid_signature(
self: @ContractState, hash: felt252, signature: Array<felt252>,
) -> felt252 {
'VALID'
}
यह उदाहरण कॉन्ट्रैक्ट सभी वैलिडेशन को छोड़ देता है और इसका उपयोग कभी भी प्रोडक्शन में नहीं किया जाना चाहिए। यूज़र एसेट्स और एक्सेस की सुरक्षा के लिए प्रोडक्शन अकाउंट कॉन्ट्रैक्ट्स को उचित वैलिडेशन और सिग्नेचर चेक्स लागू करने चाहिए।
यहाँ पूरा Account कॉन्ट्रैक्ट कोड है:
use starknet::account::Call;
#[starknet::interface]
trait ISRC6<T> {
fn __validate__(self: @T, calls: Array<Call>) -> felt252;
fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
}
#[starknet::contract(account)]
mod Account {
use starknet::SyscallResultTrait;
use starknet::syscalls::call_contract_syscall;
use super::Call;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl AccountImpl of super::ISRC6<ContractState> {
fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
'VALID'
}
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
let mut results = ArrayTrait::new();
for call in calls {
let result = call_contract_syscall(call.to, call.selector, call.calldata)
.unwrap_syscall();
results.append(result);
}
results
}
fn is_valid_signature(
self: @ContractState, hash: felt252, signature: Array<felt252>,
) -> felt252 {
'VALID'
}
}
}
आइए हम यह देखने के लिए कि यह अन्य कॉन्ट्रैक्ट्स के साथ कैसे इंटरेक्ट करता है, एक लोकल डेवनेट (local devnet) में Account कॉन्ट्रैक्ट का उपयोग करें। बाद में, “How the account contract is called” सेक्शन में, हम समझाएंगे कि प्रत्येक ट्रांजेक्शन के दौरान प्रोटोकॉल पर्दे के पीछे क्या करता है।
हमारे अकाउंट कॉन्ट्रैक्ट को डिप्लॉय करना और उसके साथ इंटरेक्ट करना
एक न्यूनतम अकाउंट कॉन्ट्रैक्ट अन्य कॉन्ट्रैक्ट्स को टारगेट करने वाले ट्रांजेक्शन्स को इनवोक (invoke) करने में सक्षम है। हालाँकि, यह अन्य कॉन्ट्रैक्ट्स को डिक्लेयर (declare) और डिप्लॉय (deploy) करने में सक्षम नहीं है। चूँकि इस ट्यूटोरियल फ्लो में एक अलग कॉन्ट्रैक्ट को डिक्लेयर और डिप्लॉय करना शामिल है, इसलिए हमें दो और वैलिडेशन फंक्शंस जोड़ने होंगे:
__validate_declare__: जब यूज़र एक नई कॉन्ट्रैक्ट क्लास डिक्लेयर करना चाहता है तो प्रोटोकॉल द्वारा कॉल किया जाता है।__validate_deploy__: एकDEPLOY_ACCOUNTट्रांजेक्शन के दौरान प्रोटोकॉल द्वारा कॉल किया जाता है, जब एक नया अकाउंट कॉन्ट्रैक्ट पहली बार डिप्लॉय किया जा रहा हो।
जिस तरह __validate__ एक्ज़ीक्यूशन से पहले इनवोक (invoke) ट्रांजेक्शन्स को वैलिडेट करता है, ठीक उसी तरह __validate_declare__ डिक्लेयर (declare) ट्रांजेक्शन्स के लिए और __validate_deploy__ अकाउंट डिप्लॉयमेंट ट्रांजेक्शन्स के लिए ऐसा ही करता है।
इंटरफ़ेस में इन दो फंक्शंस को जोड़ें:
fn __validate_declare__(self: @T, class_hash: felt252) -> felt252;
fn __validate_deploy__(
self: @T, class_hash: felt252, contract_address_salt: felt252, public_key: felt252,
) -> felt252;
कॉन्ट्रैक्ट में उनके इम्प्लीमेंटेशन्स भी जोड़ें:
fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
return 'VALID';
}
fn __validate_deploy__(
self: @ContractState,
class_hash: felt252,
contract_address_salt: felt252,
public_key: felt252,
) -> felt252 {
return 'VALID';
}
Starknet के नेटिव Account Abstraction के साथ, विभिन्न वॉलेट प्रोवाइडर्स कम्पैटिबिलिटी (compatibility) बनाए रखते हुए अपने वैलिडेशन फंक्शंस को कस्टमाइज़ कर सकते हैं। उदाहरण के लिए, Ready का अकाउंट कॉन्ट्रैक्ट कस्टम पैरामीटर्स के साथ __validate_deploy__ को परिभाषित करता है:
// Ready's customized validation function
__validate_deploy__(
class_hash: felt252,
contract_address_salt: felt252,
owner: Signer, // uses Signer type
guardian: Option<Signer> // adds guardian support
) -> felt252
ध्यान दें कि Ready का वर्ज़न मानक public_key: felt252 पैरामीटर के बजाय owner: Signer और guardian: Option<Signer> लेता है। यह कस्टमाइज़ेशन Ready को अनुमति देता है:
- एक साधारण पब्लिक की के बजाय उनके कस्टम
Signerटाइप का उपयोग करने की, जो विभिन्न सिग्नेचर स्कीम्स को दर्शा सकता है - सोशल रिकवरी के लिए गार्जियन (guardian) कार्यक्षमता जोड़ने की। गार्जियन एक विश्वसनीय पार्टी (जैसे कि एक अन्य वॉलेट, एक दोस्त, या स्वयं वॉलेट प्रोवाइडर) है जो प्राइमरी साइनर के खो जाने पर अकाउंट का एक्सेस पुनः प्राप्त करने में मदद कर सकता है।
इन अलग-अलग पैरामीटर्स के बावजूद, Ready का __validate_deploy__ अभी भी उसी अंतर्निहित (underlying) वैलिडेशन लॉजिक को कॉल करता है जो सिग्नेचर्स की जाँच करता है और यह सुनिश्चित करता है कि डिप्लॉयमेंट ऑथराइज़्ड है। कस्टम पैरामीटर्स केवल Ready को उनकी वैलिडेशन प्रक्रिया के माध्यम से अतिरिक्त जानकारी (जैसे गार्जियन कीज़) पास करने की अनुमति देते हैं।
Starknet जिस वैलिडेशन इंटरफ़ेस की अपेक्षा करता है उसका पालन करते हुए वैलिडेशन पैरामीटर्स को कस्टमाइज़ करने का यह लचीलापन (flexibility) उन लाभों में से एक है जो Account Abstraction सक्षम बनाता है। यह वॉलेट प्रोवाइडर्स को प्रोटोकॉल से विचलित हुए बिना मल्टी-सिग्नेचर सपोर्ट, सेशन कीज़ (session keys), या सोशल रिकवरी जैसी सुविधाएँ जोड़ने की अनुमति देता है।
यहाँ __validate_declare__ और __validate_deploy__ के साथ पूरा Account कॉन्ट्रैक्ट कोड जोड़ा गया है:
use starknet::account::Call;
#[starknet::interface]
trait ISRC6<T> {
fn __validate__(self: @T, calls: Array<Call>) -> felt252;
fn __execute__(ref self: T, calls: Array<Call>) -> Array<Span<felt252>>;
fn is_valid_signature(self: @T, hash: felt252, signature: Array<felt252>) -> felt252;
fn __validate_declare__(self: @T, class_hash: felt252) -> felt252;
fn __validate_deploy__(
self: @T, class_hash: felt252, contract_address_salt: felt252, public_key: felt252,
) -> felt252;
}
#[starknet::contract(account)]
mod Account {
use starknet::SyscallResultTrait;
use starknet::syscalls::call_contract_syscall;
use super::Call;
#[storage]
struct Storage {}
#[abi(embed_v0)]
impl AccountImpl of super::ISRC6<ContractState> {
fn __validate__(self: @ContractState, calls: Array<Call>) -> felt252 {
'VALID'
}
fn __execute__(ref self: ContractState, calls: Array<Call>) -> Array<Span<felt252>> {
let mut results = ArrayTrait::new();
for call in calls {
let result = call_contract_syscall(call.to, call.selector, call.calldata)
.unwrap_syscall();
results.append(result);
}
results
}
fn is_valid_signature(
self: @ContractState, hash: felt252, signature: Array<felt252>,
) -> felt252 {
'VALID'
}
fn __validate_declare__(self: @ContractState, class_hash: felt252) -> felt252 {
return 'VALID';
}
fn __validate_deploy__(
self: @ContractState,
class_hash: felt252,
contract_address_salt: felt252,
public_key: felt252,
) -> felt252 {
return 'VALID';
}
}
}
src/lib.cairo के कंटेंट को ऊपर दिए गए Account कॉन्ट्रैक्ट से बदलें।
इंटरेक्ट करने के लिए एक कॉन्ट्रैक्ट जोड़ें
चूँकि हम यह प्रदर्शित करना चाहते हैं कि अकाउंट कॉन्ट्रैक्ट का उपयोग कैसे किया जाता है, इसलिए हमें इंटरेक्ट करने के लिए कुछ अन्य कॉन्ट्रैक्ट की आवश्यकता है। आइए उसी src/lib.cairo फ़ाइल में एक साधारण Counter कॉन्ट्रैक्ट जोड़ें:
#[starknet::interface]
pub trait ICounter<TContractState> {
fn increase_counter(ref self: TContractState, amount: felt252);
fn get_counter(self: @TContractState) -> felt252;
}
#[starknet::contract]
mod Counter {
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
#[storage]
struct Storage {
counter: felt252,
}
#[abi(embed_v0)]
impl CounterImpl of super::ICounter<ContractState> {
fn increase_counter(ref self: ContractState, amount: felt252) {
self.counter.write(self.counter.read() + amount);
}
fn get_counter(self: @ContractState) -> felt252 {
self.counter.read()
}
}
}
कॉन्ट्रैक्ट्स को कंपाइल करने के लिए scarb build रन करें।
हमारे अकाउंट कॉन्ट्रैक्ट के साथ एक डेवनेट शुरू करें
यदि आपने starkup का उपयोग करके अपने Starknet डेवलपमेंट टूल्स इंस्टॉल किए हैं, तो starknet-devnet पहले से ही इंस्टॉल है। आप इसे रन करके वेरिफाई कर सकते हैं:
starknet-devnet --version
लिखते समय, अपेक्षित वर्ज़न 0.7.2 है। यदि आपके पास यह इंस्टॉल नहीं है, तो पहले प्लगइन जोड़ें, फिर इंस्टॉल करें:
asdf plugin add starknet-devnet
asdf install starknet-devnet 0.7.2
asdf set starknet-devnet 0.7.2
अब चलिए डेवनेट (devnet) शुरू करते हैं। डिफ़ॉल्ट रूप से, starknet-devnet एक स्टैण्डर्ड अकाउंट कॉन्ट्रैक्ट का उपयोग करके फंड किए गए अकाउंट्स के एक सेट को पहले से डिप्लॉय (predeploy) करता है। हम चाहते हैं कि वे इसके बजाय हमारे अकाउंट कॉन्ट्रैक्ट का उपयोग करें, इसलिए हम इसे एक फ्लैग के रूप में पास करते हैं:
starknet-devnet --seed 1 --account-class-custom target/dev/aa_Account.contract_class.json
--seedपैरामीटर यह सुनिश्चित करता है कि डेवनेट के हर बार रीस्टार्ट होने पर समान प्रीडिप्लॉयड अकाउंट्स (एड्रेस और कीज़) जनरेट हों, ताकि आपको हर रीस्टार्ट के बाद उन्हें फिर से इम्पोर्ट न करना पड़े।--account-class-customफ्लैग डेवनेट को प्रीडिप्लॉयड अकाउंट्स के लिए हमारे कंपाइल किए गए अकाउंट कॉन्ट्रैक्ट का उपयोग करने के लिए कहता है। फ़ाइल का नाम आपके Scarb प्रोजेक्ट नाम (aa) और कॉन्ट्रैक्ट नाम (Account) को जोड़ता है।

ट्रांजेक्शन्स भेजने के लिए एक अकाउंट इम्पोर्ट करें
डेवनेट ने अब हमारे अकाउंट कॉन्ट्रैक्ट का उपयोग करके अकाउंट्स को प्रीडिप्लॉय कर दिया है, लेकिन हमारी लोकल मशीन पर sncast को अभी उनके बारे में पता नहीं है। हमें एक इम्पोर्ट करने की आवश्यकता है ताकि sncast ट्रांजेक्शन्स भेजने के लिए इसका उपयोग कर सके। एक नया टर्मिनल खोलें (चूंकि वर्तमान टर्मिनल डेवनेट चला रहा है), फिर पहले अकाउंट का एड्रेस और प्राइवेट की कॉपी करें जिसे डेवनेट सूचीबद्ध करता है और इम्पोर्ट कमांड में उनका उपयोग करें:
sncast \
account import \
--url http://127.0.0.1:5050 \
--address <PREDEPLOYED_ACCOUNT_ADDRESS> \
--private-key <PREDEPLOYED_PRIVATE_KEY> \
--type oz
--urlफ्लैग लोकल डेवनेट RPC एंडपॉइंट को पॉइंट करता है।- चूँकि हमारा अकाउंट कॉन्ट्रैक्ट सिग्नेचर्स को वेरिफाई नहीं करता है, इसलिए यहाँ प्राइवेट की वैल्यू मायने नहीं रखती है। हालाँकि,
sncastको एक अनिवार्य फ़ील्ड के रूप में इसकी आवश्यकता होती है क्योंकि अधिकांश अकाउंट कॉन्ट्रैक्ट ऑथराइज़ेशन के लिए सिग्नेचर्स पर निर्भर करते हैं। —-typeफ्लैगsncastको बताता है कि अकाउंट के लिए ट्रांजेक्शन्स को कैसे फॉर्मेट किया जाए। उपलब्ध ऑप्शन्सready,braavos, याoz(OpenZeppelin) हैं। चूँकि हमारा कस्टम अकाउंट किसी विशिष्ट वॉलेट प्रोवाइडर से मेल नहीं खाता है, हमozको सबसे करीबी जेनेरिक विकल्प के रूप में उपयोग करते हैं।
कमांड रन करने के बाद, sncast पूछेगा कि क्या आप इस अकाउंट को डिफ़ॉल्ट बनाना चाहते हैं। लोकल डिफ़ॉल्ट (local default) विकल्प चुनें ताकि अकाउंट केवल इसी प्रोजेक्ट तक स्कोप्ड (scoped) रहे।

नीचे दी गई इमेज दिखाती है कि डेवनेट आउटपुट (बाएं) इम्पोर्ट कमांड (दाएं) पर कैसे मैप करता है। पीली रेखा अकाउंट एड्रेस को जोड़ती है, और लाल रेखा डेवनेट के प्रीडिप्लॉयड अकाउंट से प्राइवेट की को sncast account import कमांड के संबंधित फ़ील्ड्स से जोड़ती है।

इम्पोर्ट आउटपुट से अकाउंट का नाम नोट करें और आने वाली कमांड्स में
<ACCOUNT_NAME>के स्थान पर इसका उपयोग करें।
यदि आपको विभिन्न डिटेल्स के साथ अकाउंट को फिर से इम्पोर्ट करने की आवश्यकता है या टेस्टिंग के बाद क्लीन अप करना है, तो आप इसे निम्न के साथ हटा सकते हैं:
sncast \
account delete \
--url [http://127.0.0.1:5050](http://127.0.0.1:5050/) \
--name <ACCOUNT_NAME>
Counter कॉन्ट्रैक्ट को डिक्लेयर और डिप्लॉय करें
अब जब हमारे पास एक इम्पोर्टेड अकाउंट कॉन्ट्रैक्ट है, तो आइए इसका उपयोग अपने Counter कॉन्ट्रैक्ट को डिक्लेयर और डिप्लॉय करने के लिए करें।
कॉन्ट्रैक्ट को डिक्लेयर करें:
sncast \
--account <ACCOUNT_NAME> \
declare \
--url http://127.0.0.1:5050 \
--contract-name Counter

परिणामी क्लास हैश को नोट करें और कॉन्ट्रैक्ट को डिप्लॉय करने के लिए निम्न कमांड में इसे रिप्लेस करें:
sncast \
--account <ACCOUNT_NAME> \
deploy \
--class-hash <CLASS_HASH> \
--url http://127.0.0.1:5050

परिणामी कॉन्ट्रैक्ट एड्रेस को नोट करें।
Counter कॉन्ट्रैक्ट के साथ इंटरेक्ट करें
अब हम Counter कॉन्ट्रैक्ट के साथ इंटरेक्ट करने के लिए तैयार हैं। याद रखें कि हमने जो प्रीडिप्लॉयड अकाउंट इम्पोर्ट किया है वह इम्पोर्ट स्टेप के दौरान हमारे द्वारा निर्दिष्ट <ACCOUNT_NAME> के तहत लोकली स्टोर किया गया है।
काउंटर बढ़ाने के लिए निम्न कमांड रन करें। <CONTRACT_ADDRESS> को डिप्लॉय आउटपुट से प्राप्त कॉन्ट्रैक्ट एड्रेस से बदलें:
sncast \
--account <ACCOUNT_NAME> \
invoke \
--url http://127.0.0.1:5050 \
--contract-address <CONTRACT_ADDRESS> \
--function 'increase_counter' \
--arguments 23

अब हम नई काउंटर वैल्यू देखने के लिए क्वेरी (query) कर सकते हैं। इसे 23 (हेक्साडेसिमल में एन्कोडेड) लौटाना चाहिए:
sncast \
call \
--url http://127.0.0.1:5050 \
--contract-address <CONTRACT_ADDRESS> \
--function 'get_counter'

Account (AA) कॉन्ट्रैक्ट को कैसे कॉल किया जाता है
ऊपर दिए गए उदाहरण फ्लो (flow) में, हमने:
- प्रीडिप्लॉयड अकाउंट इम्प्लीमेंटेशन के रूप में अपने अकाउंट कॉन्ट्रैक्ट के साथ एक डेवनेट शुरू किया।
- प्रीडिप्लॉयड अकाउंट्स में से एक को इम्पोर्ट किया ताकि
sncastइसे लोकली उपयोग कर सके। - डेवनेट में एक नया कॉन्ट्रैक्ट डिक्लेयर किया:
Counter। Counterकॉन्ट्रैक्ट को डिप्लॉय किया।- Counter कॉन्ट्रैक्ट पर
increase_counterको इनवोक (invoke) किया। - काउंटर वैल्यू पढ़ने के लिए
get_counterको कॉल किया।
पर्दे के पीछे, चरण 3-6 के दौरान, प्रोटोकॉल ने प्रत्येक चरण में हमारे अकाउंट कॉन्ट्रैक्ट के वैलिडेशन और एक्ज़ीक्यूशन फंक्शंस को कॉल किया:
- Step 3 - Declaring the Counter contract: यह एक
DECLAREट्रांजेक्शन है। सीक्वेंसर ने हमारे अकाउंट कॉन्ट्रैक्ट पर__validate_declare__कॉल किया। चूँकि इसने'VALID'लौटाया, इसलिए सीक्वेंसर ने नेटवर्क परCounterक्लास को रजिस्टर कर दिया। - Step 4 - Deploying the Counter contract: यह कोई
DEPLOY_ACCOUNTट्रांजेक्शन नहीं है क्योंकिCounterएक रेगुलर कॉन्ट्रैक्ट है, अकाउंट नहीं। यह Universal Deployer Contract के माध्यम सेINVOKEट्रांजेक्शन के रूप में जाता है। सीक्वेंसर ने हमारे अकाउंट कॉन्ट्रैक्ट पर__validate__कॉल किया, और वैलिडेशन पास होने के बाद, डिप्लॉयमेंट को प्रोसेस करने के लिए__execute__को कॉल किया। - Step 5 - Invoking
increase_counter: यह एकINVOKEट्रांजेक्शन है। सीक्वेंसर ने हमारे अकाउंट कॉन्ट्रैक्ट पर__validate__कॉल किया, और वैलिडेशन पास होने के बाद,__execute__कॉल किया जिसने कॉल कोCounterकॉन्ट्रैक्ट को फॉरवर्ड कर दिया। - Step 6 - Calling
get_counter: यह एक रीड-ओनली (read-only) कॉल है। कोई ट्रांजेक्शन सबमिट नहीं किया जाता है, कोई गैस का भुगतान नहीं किया जाता है, और हमारा अकाउंट कॉन्ट्रैक्ट इसमें बिल्कुल भी शामिल नहीं होता है।
ध्यान दें कि हमारे फ्लो में
__validate_deploy__को कभी भी ट्रिगर नहीं किया गया था। इस फंक्शन को केवलDEPLOY_ACCOUNTट्रांजेक्शन के दौरान कॉल किया जाता है, जिसका उपयोग तब किया जाता है जब स्क्रैच (counterfactual deployment) से एक अकाउंट कॉन्ट्रैक्ट डिप्लॉय किया जाता है। चूँकि डेवनेट ने हमारे लिए अकाउंट्स को प्रीडिप्लॉय किया था, इसलिए कोईDEPLOY_ACCOUNTट्रांजेक्शन नहीं था। इसे केवल तभी कॉल किया जाएगा जब हम एक नया अकाउंट बनाएँगे और उसे डिप्लॉय करेंगे।
वैलिडेशन को एक्शन में देखने के लिए, Account कॉन्ट्रैक्ट में __validate_declare__ की रिटर्न वैल्यू को 'VALID' से 'INVALID' में बदलने का प्रयास करें। कॉन्ट्रैक्ट को रीबिल्ड करें, निम्न का उपयोग करके अपडेट किए गए कॉन्ट्रैक्ट के साथ डेवनेट को रीस्टार्ट करें:
starknet-devnet --seed 1 --account-class-custom target/dev/aa_Account.contract_class.json
फिर पहले प्रीडिप्लॉयड अकाउंट को वैसे ही इम्पोर्ट करें जैसे हमने पहले किया था, और Counter कॉन्ट्रैक्ट को फिर से डिक्लेयर करने का प्रयास करें। आप देखेंगे कि ट्रांजेक्शन इस तरह की एरर (error) के साथ फेल हो जाएगा:
Error: Transaction execution error: The `validate` entry point should return
`VALID`. Got Retdata([0x494e56414c4944]).
यह पुष्टि करता है कि प्रोटोकॉल वैलिडेशन फंक्शंस की रिटर्न वैल्यू की जाँच करता है और किसी भी ऐसे ट्रांजेक्शन को रिजेक्ट कर देता है जो 'VALID' नहीं लौटाता है।
एक अकाउंट को रिकवर करना
चूँकि EOA एड्रेसेस सीधे प्राइवेट की से प्राप्त (derived) होते हैं, जैसा कि हमने पहले चर्चा की थी, केवल प्राइवेट की से अकाउंट को रिकवर करना सीधा (straightforward) है। हालाँकि, यदि की (key) खो जाती है, तो कोई रिकवरी विकल्प नहीं है।
Starknet पर, चूँकि साइनर और अकाउंट अलग-अलग (decoupled) हैं, एक्सेस रिस्टोर करने के लिए प्राइवेट की और अकाउंट एड्रेस दोनों की आवश्यकता होती है, क्योंकि एक को दूसरे से प्राप्त नहीं किया जा सकता है। यही कारण है कि Ready जैसे वॉलेट्स पर अकाउंट इम्पोर्ट करने के लिए दोनों की आवश्यकता होती है। हालाँकि, क्योंकि अकाउंट एक स्मार्ट कॉन्ट्रैक्ट है, डेवलपर्स या वॉलेट प्रोवाइडर्स वैकल्पिक रिकवरी मैकेनिज्म लागू कर सकते हैं। उदाहरण के लिए, Ready उस गार्जियन सिस्टम का उपयोग करता है जिस पर हमने पहले चर्चा की थी, ताकि यदि प्राइमरी साइनर खो जाए तो यूज़र्स को अपने अकाउंट का एक्सेस पुनः प्राप्त करने में मदद मिल सके।
Account Abstraction द्वारा सक्षम की गई सुविधाएँ (Features)
Account Abstraction ऐसी सुविधाएँ पेश करता है जो EOAs के साथ कठिन या असंभव हैं। इनमें शामिल हैं:
- Gas payment in any token (किसी भी टोकन में गैस पेमेंट)। नेटिव गैस टोकन में गैस का भुगतान करने के बजाय, एक पेमास्टर सर्विस (paymaster service) यूज़र के टोकन स्वीकार कर सकती है, उन्हें स्वैप कर सकती है, और पर्दे के पीछे आवश्यक गैस टोकन में गैस शुल्क को कवर कर सकती है। यूज़र अभी भी भुगतान करता है, बस एक अलग टोकन में।
- Sponsored transactions (प्रायोजित ट्रांजेक्शन्स)। एक थर्ड पार्टी (third party) संपूर्ण गैस शुल्क को पूरी तरह से प्रायोजित (sponsor) कर सकती है, जिससे यूज़र्स को मुफ़्त में ट्रांजेक्शन्स सबमिट करने की अनुमति मिलती है। इसका उपयोग आमतौर पर ऑनबोर्डिंग (onboarding) या ऐप उपयोग को सब्सिडी देने के लिए किया जाता है।
- Custom signature schemes (कस्टम सिग्नेचर स्कीम्स)। जबकि अधिकांश Starknet अकाउंट्स ECDSA सिग्नेचर्स का उपयोग करते हैं,
__validate__फंक्शन किसी भी वेरिफिकेशन लॉजिक को लागू कर सकता है, जिसमें विभिन्न क्रिप्टोग्राफ़िक स्कीम्स शामिल हैं या विशेष मामलों में सिग्नेचर चेक्स को पूरी तरह से छोड़ भी सकता है। - Multi-signature and custom access control (मल्टी-सिग्नेचर और कस्टम एक्सेस कंट्रोल)। अकाउंट कॉन्ट्रैक्ट्स में ट्रांजेक्शन को अप्रूव करने के लिए कई पार्टियों की आवश्यकता हो सकती है, समय-आधारित नियम लागू किए जा सकते हैं, या कोई भी कस्टम एक्सेस लॉजिक लागू किया जा सकता है।
- Account recovery (अकाउंट रिकवरी)। यदि प्राइमरी एक्सेस विधि विफल हो जाती है, उदाहरण के लिए चाबियां (keys) खो जाने के कारण, तो अकाउंट कॉन्ट्रैक्ट में वैकल्पिक रिकवरी ऑप्शन्स बनाए जा सकते हैं।
- Rate-limited accounts (रेट-लिमिटेड अकाउंट्स)। अकाउंट कॉन्ट्रैक्ट एक निश्चित अवधि के भीतर ट्रांजेक्शन्स की संख्या को प्रतिबंधित कर सकते हैं, जो यूसेज कैप्स (usage caps) वाले प्रायोजित अकाउंट्स के लिए उपयोगी है।
सुरक्षा समझौते (Security tradeoffs)
Account Abstraction के साथ, प्रत्येक अतिरिक्त सुविधा (कस्टम लॉजिक, रिकवरी, मल्टीसिग, रेट लिमिट्स) अकाउंट कॉन्ट्रैक्ट में जटिलता (complexity) जोड़ती है। इस लॉजिक में बग्स या गलत कॉन्फ़िगरेशन के कारण एक्सेस या फंड्स का अपरिवर्तनीय नुकसान हो सकता है।
इसके अलावा, Starknet प्रोटोकॉल अभी भी विकसित हो रहा है। नए Account Abstraction से संबंधित फीचर्स पेश किए जा रहे हैं, और सर्वोत्तम प्रथाएं (best practices) सक्रिय रूप से बदल रही हैं। नवीनतम विकासों के साथ अप टू डेट रहना महत्वपूर्ण है। आपको केवल ऑडिट किए गए अकाउंट कॉन्ट्रैक्ट्स का ही उपयोग करना चाहिए।
निष्कर्ष (Conclusion)
Account Abstraction अकाउंट्स को प्रोग्रामेबल बनाता है। यह किसी भी टोकन में गैस पेमेंट, प्रायोजित ट्रांजेक्शन्स, मल्टीसिग स्कीम्स, और कस्टम रिकवरी मेथड्स जैसी सुविधाएँ सक्षम करता है जो EOAs के साथ बिल्कुल संभव नहीं हैं। Starknet के नेटिव इम्प्लीमेंटेशन का अर्थ है कि ये सुविधाएँ डिफ़ॉल्ट रूप से हर अकाउंट के लिए उपलब्ध हैं।
हालाँकि, ये लाभ कुछ ट्रेड-ऑफ़्स (trade-offs) के साथ आते हैं। सुरक्षित अकाउंट कॉन्ट्रैक्ट्स को लागू करना जटिल है, और विभिन्न वॉलेट प्रोवाइडर्स के इम्प्लीमेंटेशन्स के बीच स्विच करना मुश्किल बना हुआ है, जैसा कि हमने पहले चर्चा की थी।
अंततः, Account Abstraction का उद्देश्य यूज़र एक्सपीरियंस को बेहतर बनाना है। यह किसी विशिष्ट गैस टोकन की आवश्यकता या सीधे प्राइवेट कीज़ को मैनेज करने जैसी बाधाओं को दूर करता है, जिससे नए लोगों के लिए ऑनबोर्डिंग (onboarding) आसान हो जाती है।