Events कॉन्ट्रैक्ट निष्पादन (execution) से डेटा को ट्रांज़ैक्शन रसीद (transaction receipt) में उत्सर्जित (emit) करते हैं। रसीद में निष्पादन के दौरान क्या हुआ, इसके बारे में मेटाडेटा होता है, जिसे बाहरी एप्लिकेशन द्वारा क्वेरी (query) या इंडेक्स किया जा सकता है। Cairo का इवेंट सिंटैक्स Solidity की तुलना में अधिक विस्तृत (verbose) है, लेकिन यह समान उद्देश्य पूरा करता है।
इस लेख में, आप समझेंगे कि Starknet में Events कैसे काम करते हैं।
Cairo में Events का स्ट्रक्चर
Cairo में Events को #[event] एट्रिब्यूट के साथ चिह्नित एक Event enum में सूचीबद्ध किया जाना चाहिए। Solidity के व्यक्तिगत इवेंट घोषणाओं (declarations) के विपरीत, Cairo में सभी events को एक केंद्रीय enum स्ट्रक्चर के भीतर व्यवस्थित करने की आवश्यकता होती है।
यहाँ एक उदाहरण दिया गया है जो दो events को सूचीबद्ध करता है, एक यूज़र रजिस्ट्रेशन के लिए और दूसरा यूज़र लॉगिन के लिए:
// Event emitted when a new user registers
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
pub user_id: u32,
pub username: ByteArray
}
// Event emitted when a user logs in
#[derive(Drop, starknet::Event)]
pub struct UserLoggedIn {
pub user_id: u32,
pub timestamp: u64
}
// Main event enum that holds all possible events this contract can emit
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
NewUser: UserRegistered, // references UserRegistered struct
UserLogin: UserLoggedIn // references UserLoggedIn struct
}
नोट: Drop ट्रेट (trait) Cairo को structs और enums को मेमोरी से स्वचालित रूप से साफ़ करने की अनुमति देता है जब उनकी आवश्यकता नहीं होती है। आप अधिकांश Cairo structs और enums पर #[derive(Drop)] देखेंगे।
इन दो events का Solidity प्रतिनिधित्व (representation) कुछ इस तरह होगा:
event NewUser(uint32 userID, string username);
event UserLogin(uint32 userID, uint64 timestamp);
ऊपर दिए गए Cairo कोड में, हम दो इवेंट structs (UserRegistered और UserLoggedIn) परिभाषित करते हैं जो प्रत्येक इवेंट प्रकार के लिए डेटा स्ट्रक्चर निर्दिष्ट करते हैं। दोनों structs डिराइव (derive) एट्रिब्यूट के माध्यम से starknet::Event ट्रेट को इम्प्लीमेंट करते हैं।
इन अलग-अलग structs को फिर एक एकल Event enum के तहत एकीकृत (एकरूप) किया जाता है, जहाँ प्रत्येक वैरिएंट अपने संबंधित struct को संदर्भित करता है। जब events उत्सर्जित होते हैं, तो enum वैरिएंट नाम (NewUser, UserLogin) खोजने योग्य इवेंट आइडेंटिफ़ायर के रूप में कार्य करते हैं।
हालाँकि आप आमतौर पर enum वैरिएंट (जैसे NewUser) और उससे जुड़े struct (जैसे UserRegistered) दोनों के लिए एक ही नाम का उपयोग देखेंगे, लेकिन उनका मेल खाना आवश्यक नहीं है। अंतर को उजागर करने के लिए यहाँ एक अलग नाम का उपयोग किया गया है।
Starknet SDKs, जैसे कि Starknet.js, इन आइडेंटिफ़ायर का उपयोग करके events को फ़िल्टर और क्वेरी कर सकते हैं। उदाहरण के लिए, सभी यूज़र रजिस्ट्रेशन खोजने के लिए, आप "NewUser" नामक events के लिए क्वेरी करेंगे।
Events के साथ काम करते समय, आप अक्सर उन विशिष्ट डेटा द्वारा events को फ़िल्टर करना चाहेंगे जो उनमें शामिल हैं, जैसे किसी विशेष यूज़र आईडी के लिए या एक निश्चित वैल्यू रेंज के भीतर सभी events खोजना। यहीं पर इंडेक्स किए गए पैरामीटर्स (indexed parameters) काम आते हैं, ठीक Solidity की तरह।
Indexed Events (Key Fields)
इवेंट फ़ील्ड्स को #[key] एट्रिब्यूट (Solidity में indexed कीवर्ड के समान) का उपयोग करके इंडेक्सिंग के लिए चिह्नित किया जा सकता है। उदाहरण के लिए, यदि हम यूज़र रजिस्ट्रेशन में user_id को खोजने योग्य और यूज़र लॉगिन में timestamp को खोजने योग्य बनाना चाहते हैं, तो हम यह करेंगे:
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
#[key]
pub user_id: u32, // user_id IS MARKED is AS INDEXED (searchable key)
pub username: ByteArray // username IS STORED AS EVENT DATA (not indexed)
}
#[derive(Drop, starknet::Event)]
pub struct UserLoggedIn {
pub user_id: u32, // user_id IS STORED AS EVENT DATA (not indexed)
#[key]
pub timestamp: u64 // timestamp IS MARKED AS INDEXED
}
// Main event enum that holds all possible events this contract can emit
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
UserRegistered: UserRegistered,
UserLoggedIn: UserLoggedIn
}
इसका Solidity समकक्ष होगा:
event UserRegistered(uint32 indexed userID, string username);
event UserLoggedIn(uint32 userID, uint64 indexed timestamp);
#[key] का स्थान इस बात पर निर्भर करता है कि आप इवेंट लॉग्स में किस विशिष्ट फ़ील्ड को खोजने योग्य बनाना चाहते हैं। एक field किसी struct के भीतर एक डेटा तत्व होता है, उदाहरण के लिए, UserRegistered struct में user_id और username फ़ील्ड्स हैं।
UserRegistered में, हम यूज़र द्वारा फ़िल्टर करने के लिए user_id को इंडेक्स कर रहे हैं, जबकि UserLoggedIn में, हम समय द्वारा फ़िल्टर करने के लिए timestamp को इंडेक्स कर रहे हैं।
आपको केवल उन फ़ील्ड्स में #[key] जोड़ना चाहिए जिन्हें आप वास्तव में क्वेरी या फ़िल्टर करेंगे, प्रत्येक फ़ील्ड को आपकी विशिष्ट फ़िल्टरिंग आवश्यकताओं के आधार पर व्यक्तिगत रूप से एनोटेट (annotate) किया जाना चाहिए।
Starknet SDKs को सभी इवेंट डेटा को प्रोसेस किए बिना events को तेज़ी से फ़िल्टर करने में सक्षम बनाने के लिए ट्रांज़ैक्शन रसीदों (transaction receipts) में नियमित डेटा फ़ील्ड्स से की (key) फ़ील्ड्स को अलग से संग्रहीत किया जाता है।
ट्रांज़ैक्शन रसीदों (Transaction Receipts) में Events डेटा स्ट्रक्चर
ट्रांज़ैक्शन रसीद (transaction receipt) एक रिकॉर्ड है जिसमें एक सफल ट्रांज़ैक्शन के बारे में विस्तृत जानकारी होती है। इसमें ब्लॉक विवरण (block_hash, block_number), ट्रांज़ैक्शन हैश, निष्पादन स्थिति (execution_status, finality_status), गैस खपत (execution_resources), ट्रांज़ैक्शन शुल्क (actual_fee), आदि शामिल हैं, और निष्पादन के दौरान उत्सर्जित होने वाले कोई भी events भी।
प्रत्येक ट्रांज़ैक्शन रसीद में सभी उत्सर्जित events के लिए keys और डेटा के साथ एक events ऐरे होता है जहाँ:
data: गैर-इंडेक्स किए गए फ़ील्ड मानों (serialized non-indexed field values) वाले ऐरे का प्रतिनिधित्व करता हैfrom_address: वह कॉन्ट्रैक्ट एड्रेस है जिसने इवेंट को उत्सर्जित (emit) कियाkeys: एक ऐरे है जो हमेशाkeys[0]पर इवेंट सिलेक्टर हैश (event selector hash) रखता है, और साथ हीkeys[1],keys[2], आदि पर कोई भी इंडेक्स किए गए फ़ील्ड मान (indexed field values) रखता है।
keys ऐरे हर इवेंट में मौजूद होता है, चाहे आप अपने कॉन्ट्रैक्ट में #[key] एनोटेशन का उपयोग करते हों या नहीं। इसमें कम से कम इवेंट सिलेक्टर हैश होता है जो इवेंट के प्रकार (जैसे Transfer, आदि) की पहचान करता है।
नीचे एक उदाहरण ट्रांज़ैक्शन रसीद दिखाई गई है जिसमें गुलाबी बॉक्स में events ऐरे स्ट्रक्चर को हाईलाइट किया गया है:

यहाँ वह Typescript कोड है जिसने getTransactionReceipt मेथड का उपयोग करके दिए गए ट्रांज़ैक्शन हैश के आधार पर यह ट्रांज़ैक्शन रसीद उत्पन्न की है:
import { RpcProvider } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function getTxnReceipt() {
// Initialize RPC provider with Sepolia testnet endpoint
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
// initialize provider for Sepolia testnet with Alchemy
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_8/${alchemyApiKey}`,
});
// Transaction hash to query (replace with actual hash)
const transactionHash =
"0x5df0e42012440f59eb9cdd7994a3001b72cebc781bd8527fb3a5343cdb9d6f7";
try {
// Fetch transaction receipt from the network
const receipt: any = await provider.getTransactionReceipt(transactionHash);
// Display formatted receipt data
console.log(JSON.stringify(receipt, null, 2));
} catch (error) {
// Handle network or transaction errors
console.error("Error getting transaction receipt:", error);
}
}
// Execute the function
getTxnReceipt();
यह उदाहरण केवल यह दर्शाता है कि ट्रांज़ैक्शन रसीदों में events कैसे दिखाई देते हैं। Starknet.js के साथ विभिन्न क्वेरींग तकनीकों (querying techniques) को लेख के बाद के भाग में कवर किया गया है।
Keys Array को समझना
ऊपर दी गई ट्रांज़ैक्शन रसीद में, इवेंट में केवल एक की (key) (keys[0]) है जिसमें इवेंट सिलेक्टर हैश 0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9 शामिल है।

यह इवेंट सिलेक्टर हैश (keys[0]) बिना किसी इंडेक्स किए गए (no indexed) पैरामीटर के एक Transfer इवेंट का प्रतिनिधित्व करता है, जिसके सभी गैर-इंडेक्स (non-indexed) फ़ील्ड data ऐरे में संग्रहीत होते हैं:

इवेंट सिलेक्टर हैश की गणना (computation) इसका उपयोग करके की जाती है:
const nameHash = num.toHex(hash.starknetKeccak('EventName'));
Cairo Event Structure की Solidity के साथ तुलना
Solidity में, इवेंट डेटा को topics और data का उपयोग करके संरचित किया जाता है जैसा कि इस example में देखा जा सकता है:

- topic0: इसमें हमेशा इवेंट सिग्नेचर हैश (event signature hash) होता है। उपरोक्त उदाहरण में, इवेंट
NewUser(uint32,string)का हैशkeccak256("NewUser(uint32,string)")है, जो0x37ecc4388271ab7af2220881c1f2f70fbea71e6b1635107f9daffa0fab84d5b3के बराबर है। - topic1: इसमें इंडेक्स किया गया
user_idपैरामीटर शामिल होता है। - Data field: इसमें सभी गैर-इंडेक्स किए गए (non-indexed) पैरामीटर (Hex में) होते हैं।
Cairo keys ऐरे के साथ एक समान पैटर्न का पालन करता है। तुलना के लिए, Starknet Sepolia पर इस transaction को देखें जो कई keys के साथ एक इवेंट दिखाता है:

keys[0](सफेद तीर द्वारा हाईलाइट किया गया) इवेंट सिलेक्टर हैश (Event8Indexed) रखता है।- ऐरे में बाद के तत्व (उदा.,
keys[1],keys[2], …keys[10]), जो हरे तीर के साथ दिखाए गए हैं, इवेंट के इंडेक्स किए गए (#[key]) फ़ील्ड का प्रतिनिधित्व करते हैं। - गैर-इंडेक्स किए गए (Non-indexed) पैरामीटर को
dataफ़ील्ड में अलग से संग्रहीत किया जाता है, जैसा कि बैंगनी बॉक्स में दर्शाया गया है।
ऊपर दी गई छवि के आधार पर, KEYS अनुभाग में कुल दस (10) इंडेक्स किए गए पैरामीटर शामिल हैं। यह Solidity पर Cairo के एक प्रमुख लाभ पर प्रकाश डालता है: जहाँ Solidity में इंडेक्स किए गए पैरामीटर अधिकतम तीन (topic1-topic3) तक सीमित होते हैं, जिसमें topic0 हमेशा इवेंट सिग्नेचर के लिए आरक्षित (reserved) होता है, वहीं Cairo पचास (50) तक इंडेक्स किए गए पैरामीटर की अनुमति देता है। यह Solidity में anonymous events जैसे वर्कअराउंड की आवश्यकता को समाप्त करता है (जिसमें 4 तक इंडेक्स किए गए पैरामीटर हो सकते हैं लेकिन इवेंट सिग्नेचर खो जाता है)।
Solidity की तरह, Cairo में गैर-इंडेक्स किए गए (non-indexed) फ़ील्ड्स को data ऐरे के माध्यम से मैन्युअल रूप से खोजने की आवश्यकता होती है, जबकि इंडेक्स किए गए फ़ील्ड्स (जिन्हें #[key] के साथ चिह्नित किया गया है) को Starknet SDKs का उपयोग करके कुशलतापूर्वक फ़िल्टर किया जा सकता है।
Events आंतरिक (Internally) रूप से कैसे काम करते हैं
Cairo में Starknet का इवेंट सिस्टम मुख्य रूप से दो traits के इर्द-गिर्द घूमता है:
Event: इवेंट सीरियलाइज़ेशन (serialization) और डीसीरियलाइज़ेशन (deserialization) को संभालता है।EventEmitter: कॉन्ट्रैक्ट फ़ंक्शंस के अंदरself.emit(...)का उपयोग करके एमिशन (emission) क्षमता प्रदान करता है।
Event Trait
Event ट्रेट (trait) ऐसे तरीके (methods) प्रदान करता है जो events को सीरियलाइज़ करते हैं, events को डीसीरियलाइज़ करते हैं, और इवेंट फ़िल्टरिंग और इंडेक्सिंग के लिए उपयोग किया जाने वाला एक आंतरिक (internal) इवेंट टाइप आइडेंटिफ़ायर उत्पन्न करते हैं। #[derive(starknet::Event)] के साथ चिह्नित किसी भी struct या enum को इन प्रमुख तरीकों के लिए स्वचालित रूप से इम्प्लीमेंटेशन मिल जाते हैं:
| Method | Purpose |
|---|---|
append_keys_and_data |
इंडेक्स किए गए (#[key]) और गैर-इंडेक्स (non-indexed) फ़ील्ड को अलग-अलग keys और data ऐरे में विभाजित करके इवेंट को सीरियलाइज़ करता है |
deserialize(ref keys, ref data) -> Option<T> |
उत्सर्जित (emitted) ट्रांज़ैक्शन रसीद डेटा से मूल (original) इवेंट का पुनर्निर्माण करता है; यदि अमान्य (invalid) है तो None लौटाता है |
event_type_name |
फ़िल्टरिंग और इंडेक्सिंग के लिए इवेंट सिलेक्टर की गणना करने हेतु उपयोग किया जाने वाला आंतरिक आइडेंटिफ़ायर |
ध्यान दें कि आप इन विधियों (methods) को मैन्युअल रूप से लागू नहीं करते हैं। वे स्वचालित रूप से तब उत्पन्न होते हैं जब आप #[derive(starknet::Event)] का उपयोग करते हैं, जो इवेंट डेटा को सीरियलाइज़ करने और उसका पुनर्निर्माण (reconstruct) करने के लिए आवश्यक हर चीज़ के साथ कॉन्ट्रैक्ट में Events को सेट करता है।
Event Serialization को समझना
Event ट्रेट (trait) स्वचालित रूप से इवेंट सीरियलाइज़ेशन को संभालता है, लेकिन जब events में जटिल फ़ील्ड प्रकार (complex field types) जैसे कि arrays, नेस्टेड structs शामिल होते हैं, तो यह अतिरिक्त सीरियलाइज़ेशन सहायता के लिए Serde ट्रेट पर निर्भर करता है।
Serde ट्रेट जटिल Cairo प्रकारों को felt252 मानों के अनुक्रम (sequence) में परिवर्तित करता है जो Cairo VM के साथ संगत (compatible) है। Cairo में, felt252 एकमात्र प्रिमिटिव (primitive) प्रकार है जिसे Cairo VM समझता है, इसलिए 252 बिट्स से बड़े किसी भी मान को felt252 मानों की सूची में तोड़ा जाना चाहिए।
Event ट्रेट द्वारा स्वचालित रूप से संभाले जाने वाले प्रकार (Types) हैं:
- Simple types:
u8,u16,u32,u64,u128,bool,felt252,ContractAddress ByteArray: मैन्युअलSerdeडिराइवेशन (derivation) के बिना स्वचालित रूप से सीरियलाइज़ किया जाता है
जैसा कि पहले के अध्यायों में बताया गया है, ByteArray एक Cairo प्रकार (type) है जो स्ट्रिंग्स (strings) का प्रतिनिधित्व करता है। यह तीन फ़ील्ड्स वाला एक struct है:
data: Array<felt252>: इसमें स्ट्रिंग डेटा के 31-बाइट चंक्स (chunks) होते हैं*pending_word: felt252: पूर्ण 31-बाइट चंक्स (30 बाइट्स तक) के साथdataऐरे को भरने के बाद बचे हुए बाइट्सpending_word_len: u32:pending_wordमें बाइट्स की संख्या
Cairo पहले पूरे 31-बाइट चंक्स को data ऐरे में पैक करता है, फिर बचे हुए बाइट्स को pending_word में डाल देता है। उदाहरण के लिए, “serah” को इस तरह सीरियलाइज़ किया जाता है:
data:[](खाली ऐरे - 5-बाइट स्ट्रिंग के लिए किसी 31-बाइट चंक्स की आवश्यकता नहीं है)pending_word:0x7365726168(इसमें हेक्स प्रारूप (hex format) में वास्तविक स्ट्रिंग बाइट्स शामिल हैं)pending_word_len:0x5(कुल 5 बाइट्स)
चूँकि ByteArray का आमतौर पर उपयोग किया जाता है और यह Cairo की मानक लाइब्रेरी (standard library) का हिस्सा है, इसलिए Event ट्रेट में इसके लिए स्वचालित सीरियलाइज़ेशन समर्थन शामिल है।
यह स्वचालित सीरियलाइज़ेशन ही हमें मैन्युअल रूप से Serde प्राप्त किए (deriving) बिना events में ByteArray का उपयोग करने की अनुमति देता है। जब हम लेख में आगे वास्तविक ट्रांज़ैक्शन डेटा की जाँच करेंगे तो हम विस्तृत ByteArray सीरियलाइज़ेशन ब्रेकडाउन देखेंगे।
जटिल प्रकार (Complex types) जिनके लिए मैन्युअल Serde डिराइवेशन (derivation) की आवश्यकता होती है:
- Custom structs: नेस्टेड डेटा जैसे यूज़र-डिफ़ाइंड स्ट्रक्चर
- Arrays:
Array<u32>,Array<ByteArray>, आदि। - डेटा के साथ यूज़र-डिफ़ाइंड enums
जब किसी इवेंट में इन जटिल प्रकारों के फ़ील्ड शामिल होते हैं, तो उन फ़ील्ड प्रकारों को Serde प्राप्त (derive) करना चाहिए ताकि एमिशन (emission) के दौरान Event ट्रेट उन्हें सीरियलाइज़ कर सके। इसके बिना, कंपाइलर events को सही ढंग से प्रोसेस नहीं कर सकता। इसका एक व्यावहारिक उदाहरण लेख के “Handling Complex Event Field Types” अनुभाग में दिखाया जाएगा।
EventEmitter Trait
EventEmitter ट्रेट events को इसके माध्यम से उत्सर्जित (emit) करने में सक्षम बनाता है:
self.emit(EventStruct { ... });
कॉन्ट्रैक्ट निष्पादन (execution) के दौरान, यह Event ट्रेट का उपयोग करके इवेंट को सीरियलाइज़ करता है और परिणाम को ट्रांज़ैक्शन रसीद में संग्रहीत करता है। कस्टम struct फ़ील्ड वाले events के लिए, उन structs को अलग से Serde प्राप्त (derive) करना होगा ताकि Event ट्रेट उन्हें ठीक से सीरियलाइज़ कर सके।
निम्नलिखित आरेख (diagram) इवेंट सीरियलाइज़ेशन वर्कफ़्लो को दर्शाता है, जिसमें यह दिखाया गया है कि किन इवेंट प्रकारों को स्वचालित रूप से सीरियलाइज़ किया जा सकता है और किनके एमिशन से पहले अतिरिक्त Serde समर्थन की आवश्यकता होती है:

बुनियादी बातों को कवर करने के बाद, अगला अनुभाग (section) यह पता लगाता है कि नेस्टेड या जटिल प्रकार (complex types) शामिल होने पर इवेंट स्ट्रक्चर कैसे कार्य करते हैं।
Complex Event Field Types को संभालना (Handling)
यहाँ अतिरिक्त फ़ील्ड प्रकारों के साथ अपडेटेड UserRegistered इवेंट दिया गया है। UserMetadata एक कस्टम struct है जो यूज़र एन्वायरनमेंट डेटा (डिवाइस का प्रकार और स्थान की जानकारी) रखता है। UserRegistered इवेंट के भीतर एक नेस्टेड struct होने के नाते, इसे उचित सीरियलाइज़ेशन की आवश्यकता होती है:
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
#[key]
pub user_id: u32,
pub username: ByteArray,
pub metadata: UserMetadata,
pub tag_count: u32,
pub timestamp: u64,
}
#[derive(Drop, Serde)]
pub struct UserMetadata {
pub device_type: ByteArray,
pub ip_region: ByteArray,
}
चूँकि UserMetadata एक जटिल प्रकार है, इसे Serde प्राप्त (derive) करना चाहिए ताकि इसे felt252 मानों में सही ढंग से सीरियलाइज़ किया जा सके।
यह Cairo VM की बाधाओं पर हमारे पहले के नोट से जुड़ता है: सफल इवेंट एमिशन के लिए जटिल प्रकारों (complex types) को ठीक से सीरियलाइज़ किया जाना चाहिए।
UserMetadata struct में Serde के बिना, कोड संकलित (compile) होने में विफल रहता है, जैसा कि नीचे दिखाया गया है:

UserMetadata को Serde की आवश्यकता है क्योंकि यह एक कस्टम struct है। UserRegistered इवेंट struct को केवल starknet::Event की आवश्यकता होती है (Event ट्रेट बुनियादी (basic) प्रकारों को स्वचालित रूप से संभालता है लेकिन जटिल फ़ील्ड प्रकारों के लिए Serde को सौंपता है (delegates)।)
साथ ही, ध्यान दें कि जटिल प्रकार के इंडेक्स किए गए फ़ील्ड (#[key]) इवेंट के keys ऐरे में हैश किए गए (hashed) मानों के रूप में संग्रहीत किए जाते हैं और उन्हें ट्रांज़ैक्शन लॉग से सीधे पुनर्प्राप्त (recover) नहीं किया जा सकता है।
वर्तमान UserRegistered इवेंट (अनुशंसित दृष्टिकोण/recommended approach):
यह डिज़ाइन user_id (एक u32 प्रिमिटिव प्रकार) का उपयोग इंडेक्स किए गए फ़ील्ड के रूप में करता है, जो कुशल क्वेरींग के लिए ट्रांज़ैक्शन लॉग में पठनीय (readable) रहता है:
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
#[key]
pub user_id: u32, // Primitive type (stays readable)
pub username: ByteArray, // Non-indexed (in data array)
pub metadata: UserMetadata, // Non-indexed (in data array)
pub tag_count: u32, // Non-indexed (in data array)
pub timestamp: u64, // Non-indexed (in data array)
}
ट्रांज़ैक्शन रसीद (Transaction receipt):
{
"keys": [
"0x...event_selector", // key[0] is always the event selector.
"0x7b" // user_id = 123 (readable as hex)
],
"data": [
"username_serialized",
"metadata_serialized",
"tag_count_serialized",
"timestamp_serialized"
]
}
इस दृष्टिकोण के साथ, आप उन events के लिए आसानी से क्वेरी कर सकते हैं जहाँ user_id = 123 है क्योंकि मान 0x7b keys ऐरे (keys[1]) में सीधे पठनीय है।
यदि हमने इंडेक्स किए गए फ़ील्ड के रूप में जटिल UserMetadata struct का उपयोग किया, तो यह क्वेरींग में चुनौतियाँ पैदा करेगा:
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
#[key]
pub metadata: UserMetadata, // Complex struct as indexed field - BAD!
pub user_id: u32,
pub username: ByteArray,
}
रसीद में आप क्या देखेंगे:
{
"keys": [
"0x...event_selector",
"0xa1b2c3d4e5f67890..." // Hashed UserMetadata - unreadable!
],
"data": ["0x7b", "username_serialized"]
}
संपूर्ण UserMetadata keys ऐरे में एक अपठनीय (unreadable) हैश बन जाता है। हम device_type या ip_region द्वारा यूज़र्स के लिए क्वेरी नहीं कर सकते क्योंकि ये मान हैश के भीतर छिपे होते हैं। यही कारण है कि जब हमें events को कुशलतापूर्वक फ़िल्टर करने की आवश्यकता होती है तो u32 जैसे प्रिमिटिव प्रकार इंडेक्स किए गए फ़ील्ड के लिए बेहतर काम करते हैं। इसलिए यदि आप इंडेक्स किए गए फ़ील्ड द्वारा events को क्वेरी करने की योजना बनाते हैं, तो u32, felt252, या ContractAddress जैसे प्रिमिटिव प्रकारों का उपयोग करना सबसे अच्छा है।
#[flat] attribute का उपयोग करना
#[flat] एट्रिब्यूट यह बदलता है कि ट्रांज़ैक्शन लॉग में इवेंट वेरिएंट्स को कैसे नाम दिया जाता है और पहचाना जाता है। इसका उपयोग नेस्टेड इवेंट enums को फ़्लैट (flatten) करने के लिए किया जाता है ताकि विशिष्ट इवेंट की क्वेरी और फ़िल्टरिंग आसान हो सके।
यह एट्रिब्यूट नेस्टेड enum स्ट्रक्चर्स को संबोधित (address) करता है, न कि जटिल फ़ील्ड प्रकारों को। यह जटिल इंडेक्सिंग (complex indexing) से एक अलग अवधारणा है जिस पर हमने अभी चर्चा की है।
#[flat]एट्रिब्यूट इवेंट नेमिंग हायरार्की (naming hierarchy) को फ़्लैट करता है, न कि स्वयं डेटा स्ट्रक्चर को।
जब Event enum में इवेंट वैरिएंट पर उपयोग किया जाता है, तो यह इवेंट सिलेक्टर हैश गणना को बाहरी enum नाम के बजाय आंतरिक (inner) वैरिएंट नाम का उपयोग करने के लिए बदल देता है।
Outer Enums, Inner Enum और Inner Variants
#[flat] कैसे काम करता है, यह समझने के लिए, हमें enum स्ट्रक्चर के इन तीन स्तरों के बीच अंतर करने की आवश्यकता है:
// OUTER enum (the main Event enum)
pub enum Event {
UserRegistered: UserRegistered,
#[flat]
UserDataUpdated: UserDataUpdated, // <- This references the INNER enum
}
// INNER enum (nested inside the outer enum structure)
pub enum UserDataUpdated {
DeviceType: UpdatedDeviceType, // <- These are the inner variants
IpRegion: UpdatedIpRegion, // <- These are the inner variants
}
- Outer enum: मुख्य
Eventenum जिसमें कॉन्ट्रैक्ट के लिए सभी संभावित events होते हैं - Inner enum:
UserDataUpdatedenum जिसमें विशिष्ट वैरिएंट (DeviceTypeऔरIpRegion) होते हैं - Inner variant:
UserDataUpdatedenum के भीतर व्यक्तिगत enum वैरिएंट (DeviceTypeऔरIpRegion), प्रत्येक अपने स्वयं के इवेंट struct को संदर्भित (reference) करते हैं
संपूर्ण उदाहरण (Complete Example)
निम्नलिखित उदाहरण कई इवेंट प्रकारों वाले कॉन्ट्रैक्ट को दिखाता है: एक साधारण struct इवेंट (UserRegistered) और एक नेस्टेड enum इवेंट (UserDataUpdated) जिसमें दो वैरिएंट होते हैं:
// Main event enum that holds all possible events this contract can emit
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
UserRegistered: UserRegistered,
#[flat] // NEWLY ADDED (flattens nested event enum)
UserDataUpdated: UserDataUpdated,
}
// event for user registration
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
#[key]
pub user_id: u32, // Indexed user ID for filtering
pub username: ByteArray, // Username as event data
pub metadata: UserMetadata, // User metadata struct
}
// Nested event enum containing different types of user data updates
#[derive(Drop, starknet::Event)]
pub enum UserDataUpdated {
DeviceType: UpdatedDeviceType, // Device type change event
IpRegion: UpdatedIpRegion, // IP region change event
}
// Event for device type updates
#[derive(Drop, starknet::Event)]
pub struct UpdatedDeviceType {
#[key]
pub user_id: u32, // Indexed user ID
pub new_device_type: ByteArray, // New device type value
}
// Event for IP region updates
#[derive(Drop, starknet::Event)]
pub struct UpdatedIpRegion {
#[key]
pub user_id: u32, // Indexed user ID
pub new_ip_region: ByteArray, // New IP region value
}
// User metadata structure containing device and location info
#[derive(Drop, Serde)]
pub struct UserMetadata {
pub device_type: ByteArray, // User's device type
pub ip_region: ByteArray, // User's IP region
}
ध्यान दें कि मुख्य Event enum में UserDataUpdated (नेस्टेड enum) वैरिएंट पर #[flat] एट्रिब्यूट कैसे लागू किया जाता है, यह वही है जो बदलता है कि ट्रांज़ैक्शन लॉग में आंतरिक वैरिएंट (DeviceType और IpRegion) कैसे दिखाई देते हैं।
याद रखें, इवेंट सिलेक्टर हैश (ट्रांज़ैक्शन रसीद के keys[0] में संग्रहीत) की गणना starknetKeccak("EventName") का उपयोग करके की जाती है।
#[flat] एट्रिब्यूट के बिना, इवेंट सिलेक्टर हैश बाहरी enum नाम से प्राप्त होता है: starknetKeccak("UserDataUpdated")। इसका मतलब है कि सभी enum वैरिएंट (DeviceType और IpRegion) समान इवेंट सिलेक्टर शेयर करते हैं, इसलिए आप विशिष्ट वैरिएंट के लिए क्वेरी नहीं कर सकते, आप केवल सामान्य तौर पर "UserDataUpdated" events के लिए क्वेरी कर सकते हैं।
{
"keys": ["0x...hash_of_UserDataUpdated"], // Same selector for all variants*
"data": [...],
"from_address": "0x..."
}
लेकिन जब हम #[flat] का उपयोग करते हैं, तो इवेंट सिलेक्टर हैश की गणना आंतरिक वैरिएंट नाम से की जाती है: starknetKeccak("DeviceType") / starknetKeccak("IpRegion"), इसलिए सटीक फ़िल्टरिंग और क्वेरींग के लिए DeviceType और IpRegion में से प्रत्येक को अपना स्वयं का सिलेक्टर हैश मिलता है।
// DeviceType event
{
"keys": ["0x...hash_of_DeviceType"], // Unique selector
"data": [...],
"from_address": "0x..."
}
// IpRegion event
{
"keys": ["0x...hash_of_IpRegion"], // Different unique selector
"data": [...],
"from_address": "0x..."
}
#[flat] एट्रिब्यूट केवल इवेंट नेमिंग और सिलेक्टर गणना (computation) को प्रभावित करता है, वास्तविक डेटा स्ट्रक्चर, फ़ील्ड्स और सीरियलाइज़ेशन अपरिवर्तित (unchanged) रहते हैं। यह नेस्टेड इवेंट enums के साथ काम करते समय इवेंट फ़िल्टरिंग और लॉग इंस्पेक्शन को बहुत आसान बनाता है।
कंपोनेंट events को मानक इवेंट स्ट्रक्चर से मेल खाना सुनिश्चित करने के लिए OpenZeppelin कंपोनेंट लाइब्रेरीज़ में आमतौर पर #[flat] एट्रिब्यूट का उपयोग किया जाता है
उदाहरण के लिए, ERC20 और Ownable कंपोनेंट्स का उपयोग करते समय, #[flat] events से कंपोनेंट आईडी (ID) उपसर्ग (prefix) को हटा देता है, इसलिए ERC20 के Transfer और Approval events, Ownable के OwnershipTransferred इवेंट के साथ, पहली की (key) के रूप में अपने स्वयं के सिलेक्टर हैश के साथ दिखाई देते हैं, ठीक वैसे ही जैसे वे स्टैंडअलोन कॉन्ट्रैक्ट्स में दिखाई देंगे। (कंपोनेंट्स को अध्याय 13 में विस्तार से समझाया गया है - अभी के लिए, उन्हें पुन: प्रयोज्य (reusable) कॉन्ट्रैक्ट मॉड्यूल के रूप में सोचें।)
ध्यान दें कि इवेंट enums में enum वैरिएंट के रूप में उपयोग किए जाने वाले structs को starknet::Event प्राप्त (derive) करना चाहिए क्योंकि enum स्ट्रक्चर में उपयोग किए जाने पर वे स्वयं इवेंट प्रकार बन जाते हैं।
Event Logs की टेस्टिंग
एक नया Scarb प्रोजेक्ट scarb new testinglog स्कैफ़ोल्ड (scaffold) करें और अपने टेस्ट रनर के रूप में ‘Starknet Foundry (default)’ चुनें:

इवेंट लॉग्स का परीक्षण करने के लिए, नीचे दिए गए इस UserManager कॉन्ट्रैक्ट पर विचार करें जो यूज़र्स को स्वयं को रजिस्टर करने की अनुमति देता है और ट्रैक करता है कि कितने यूज़र्स ने रजिस्टर किया है।
कॉन्ट्रैक्ट यूज़र्स को विशिष्ट (unique) आईडी असाइन करने के लिए एक काउंटर का उपयोग करता है और उनकी जानकारी को Map में संग्रहीत करता है। जब कोई यूज़र रजिस्टर करता है, तो कॉन्ट्रैक्ट एक UserRegistered इवेंट उत्सर्जित करता है जिसे बाहरी एप्लिकेशन क्वेरी कर सकते हैं। #[key] एट्रिब्यूट और UserMetadata struct को कैसे संग्रहीत किया जाता है, इस पर ध्यान दें।
संपूर्ण कोड को कॉपी करें और अपनी src/lib.cairo फ़ाइल में पेस्ट करें:
// Interface defining the functions our UserManager contract will implement
#[starknet::interface]
pub trait IUserManager<TContractState> {
fn register_user(ref self: TContractState, username: ByteArray);
fn get_user_count(self: @TContractState) -> u32;
}
// Struct to store user information (derives Store to enable storage in contract)
#[derive(Drop, Serde, starknet::Store)]
pub struct UserMetadata {
pub user_id: u32,
pub username: ByteArray
}
// Event emitted when a new user registers (user_id is marked as key for indexing)
#[derive(Drop, starknet::Event)]
pub struct UserRegistered {
#[key]
pub user_id: u32,
pub username: ByteArray,
pub timestamp: u64,
}
#[starknet::contract]
pub mod UserManager {
use super::{UserRegistered, UserMetadata, IUserManager};
use starknet::{
get_block_timestamp, ContractAddress, get_caller_address,
storage::{Map, StoragePathEntry, StoragePointerReadAccess, StoragePointerWriteAccess}
};
#[storage]
struct Storage {
user_counter: u32, // Tracks total number of registered users
users: Map<ContractAddress, UserMetadata> // Maps user addresses to their metadata
}
// Main event enum that holds all possible events this contract can emit
#[event]
#[derive(Drop, starknet::Event)]
pub enum Event {
UserRegistered: UserRegistered,
}
#[abi(embed_v0)]
impl UserManagerImpl of IUserManager<ContractState> {
fn register_user(ref self: ContractState, username: ByteArray) {
// Get current user count and increment for new user ID
let current_counter = self.user_counter.read();
let user_id = current_counter + 1;
// Create user metadata with new ID and provided username
let metadata = UserMetadata {
user_id,
username: username.clone()
};
// Update counter and store user data mapped to caller's address
self.user_counter.write(user_id);
self.users.entry(get_caller_address()).write(metadata);
// Emit event with user details and current timestamp
self.emit(UserRegistered {
user_id,
username,
timestamp: get_block_timestamp(),
});
}
fn get_user_count(self: @ContractState) -> u32 {
// Return the current number of registered users
self.user_counter.read()
}
}
}
IUserManager ट्रेट दो फ़ंक्शंस को परिभाषित करता है; रजिस्ट्रेशन के लिए register_user और पंजीकृत यूज़र्स की कुल संख्या की जांच करने के लिए get_user_count।
-
UserMetadatastruct यूज़र की जानकारी (ID और यूज़रनेम) संग्रहीत करता है और इसे कॉन्ट्रैक्ट स्टोरेज में सहेजा जा सकता है। यहstarknet::Storeप्राप्त (derive) करता है क्योंकि यहMap<ContractAddress, UserMetadata>के भीतर कॉन्ट्रैक्ट स्टोरेज में संग्रहीत होता है।कोई भी कस्टम struct जिसे कॉन्ट्रैक्ट स्टोरेज से पढ़ने या उसमें लिखने की आवश्यकता है, उसे
Storeट्रेट को लागू करना होगा, जिसे#[derive(starknet::Store)]स्वचालित रूप से उत्पन्न करता है। -
UserRegisteredइवेंट struct रजिस्ट्रेशन विवरण को लॉग करता है।user_idफ़ील्ड को#[key]के साथ चिह्नित किया गया है, जो इसे क्वेरी में कुशल फ़िल्टरिंग के लिए इंडेक्स बनाता है।
जब register_user को कॉल किया जाता है, तो कॉन्ट्रैक्ट:
- नई यूज़र आईडी उत्पन्न करने के लिए यूज़र काउंटर को इंक्रीमेंट करता है
- यूज़र का मेटाडेटा बनाता और संग्रहीत करता है
- यूज़र आईडी, यूज़रनेम और वर्तमान ब्लॉक टाइमस्टैम्प (timestamp) के साथ एक
UserRegisteredइवेंट उत्सर्जित करता है
अपनी प्रोजेक्ट डायरेक्टरी cd testinglog पर नेविगेट करें और अपना प्रोजेक्ट बनाने के लिए scarb build चलाएँ:

Starknet Foundry का उपयोग करके events का परीक्षण करने के कई तरीके हैं। आप assert_emitted विधि (method) का उपयोग करके यह दावा (assert) करने के लिए परीक्षण कर सकते हैं कि कोई इवेंट उत्सर्जित किया गया था या नहीं, या इवेंट एमिशन की कमी का परीक्षण करने के लिए assert_not_emitted का उपयोग कर सकते हैं। आप events का सीधे निरीक्षण करके मैन्युअल रूप से भी परीक्षण कर सकते हैं।
मैन्युअल इवेंट परीक्षण के लिए, आप सभी उत्सर्जित events की जाँच करने के बजाय किसी विशिष्ट कॉन्ट्रैक्ट से events को फ़िल्टर करना चाह सकते हैं। Events स्ट्रक्चर पर emitted_by विधि आपको events को किसी विशेष पते (address) से आने वाले events तक सीमित करने की अनुमति देती है।
assert_emitted विधि और events के परीक्षण के लिए मैन्युअल विधि दोनों पर चर्चा की जाएगी।
Test 1: इन-बिल्ट (built-in) assert_emitted का उपयोग करना
नीचे दिया गया परीक्षण (test) सत्यापित करता है कि यूज़र को रजिस्टर करने से अपेक्षित (expected) डेटा के साथ सही UserRegistered इवेंट उत्सर्जित होता है। tests/test_contract.cairo पर नेविगेट करें, इस परीक्षण को कॉपी करें और इसमें पेस्ट करें:
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait, spy_events, EventSpyTrait, IsEmitted, Event, EventSpyAssertionsTrait};
use testinglog::{IUserManagerDispatcher, UserManager, UserRegistered, IUserManagerDispatcherTrait};
use starknet::{ContractAddress, get_block_timestamp};
fn deploy_contract(name: ByteArray) -> ContractAddress {
let contract = declare(name).unwrap().contract_class();
let (contract_address, _) = contract.deploy(@ArrayTrait::new()).unwrap();
contract_address
}
#[test]
fn test_registration_event_emission() {
// Deploy the UserManager contract
let contract_address = deploy_contract("UserManager");
// Create a dispatcher to interact with the deployed contract
let dispatcher = IUserManagerDispatcher { contract_address };
// Start spying on events before the function call
let mut spy = spy_events();
// Register a user - this should emit a UserRegistered event
dispatcher.register_user("serah");
// Verify that the expected event was emitted with correct data
spy.assert_emitted(
@array![
(
contract_address, // Event should come from our contract
UserManager::Event::UserRegistered(
UserRegistered {
user_id: 1, // First user gets ID 1
username: "serah", // Username matches what we passed
timestamp: get_block_timestamp() // Timestamp should be current block time
}
)
)
]
);
}
Imports Starknet Foundry और हमारी कॉन्ट्रैक्ट परिभाषाओं से आवश्यक परीक्षण उपकरण (testing tools) लाते हैं।
snforge_std से, हम कॉन्ट्रैक्ट क्लास लोड करने के लिए declare आयात (import) करते हैं, साथ ही कॉन्ट्रैक्ट डिप्लॉयमेंट के लिए ContractClassTrait और DeclareResultTrait जैसे संबंधित traits भी।
use snforge_std::{declare, ContractClassTrait, DeclareResultTrait, spy_events, EventSpyTrait,IsEmitted, Event, EventSpyAssertionsTrait};
इवेंट परीक्षण कार्यक्षमता (Event testing functionality) spy_events से आती है जो हमारा इवेंट जासूस (spy) बनाता है, जासूस इंटरैक्शन के लिए EventSpyTrait, और EventSpyAssertionsTrait जो assert_emitted जैसे एज़र्शन मेथड (assertion methods) जोड़ता है। हम इवेंट हैंडलिंग संचालन के लिए IsEmitted और Event प्रकारों (types) को भी आयात करते हैं।
हमारे testinglog मॉड्यूल से, हम कॉन्ट्रैक्ट फ़ंक्शंस को कॉल करने के लिए ऑटो-जेनरेटेड IUserManagerDispatcher और इसके ट्रेट, हमारी इवेंट परिभाषाओं (event definitions) वाले UserManager कॉन्ट्रैक्ट मॉड्यूल, और विशिष्ट UserRegistered इवेंट struct जिसका हम परीक्षण कर रहे हैं, को आयात करते हैं।
use testinglog::{IUserManagerDispatcher, UserManager, UserRegistered, IUserManagerDispatcherTrait};
starknet कोर से, हम कॉन्ट्रैक्ट पतों (contract addresses) को संभालने के लिए ContractAddress और वर्तमान ब्लॉक टाइमस्टैम्प प्राप्त करने के लिए get_block_timestamp आयात करते हैं।
use starknet::{ContractAddress, get_block_timestamp};
test_registration_event_emission() spy.assert_emitted() के साथ सरलीकृत दृष्टिकोण का उपयोग करता है।
deploy_contract("UserManager")एक सहायक फ़ंक्शन है जोUserManagerकॉन्ट्रैक्ट को घोषित (declare) और डिप्लॉय करता है, तथा इसका एड्रेस लौटाता हैIUserManagerDispatcher { contract_address }डिप्लॉय किए गए कॉन्ट्रैक्ट के साथ इंटरैक्ट करने के लिए एक डिस्पैचर (dispatcher) बनाता हैspy_events()एक्शन को ट्रिगर करने से पहले इवेंट स्पाईइंग (event spying) को इनिशियलाइज़ करता है
डिस्पैचर के माध्यम से register_user("serah") को कॉल करने के बाद, spy.assert_emitted() यह सत्यापित करने के लिए जांच करता है कि अपेक्षित UserRegistered इवेंट सही डेटा (user_id: 1, username: “serah”, और वर्तमान टाइमस्टैम्प) के साथ उत्सर्जित किया गया था। एज़र्शन (assertion) उस कॉन्ट्रैक्ट एड्रेस जिसने इवेंट को उत्सर्जित किया है और इवेंट डेटा स्ट्रक्चर दोनों की जांच करता है।
scarb test चलाएँ, आप देखेंगे कि परीक्षण (test) पास हो गया है, जो इस बात की पुष्टि करता है कि हमारी इवेंट टेस्टिंग सही ढंग से काम कर रही है।
Test 2: मैन्युअल विधि (manual method) का उपयोग करना
test_event_structure() यह सुनिश्चित करने के लिए परीक्षण करता है कि register_user फ़ंक्शन सही ढंग से काम करता है और अपेक्षित UserRegistered इवेंट उत्सर्जित करता है।
#[test]
fn test_event_structure() {
// Deploy the UserManager contract
let contract_address = deploy_contract("UserManager");
// Create a dispatcher to interact with the deployed contract
let dispatcher = IUserManagerDispatcher { contract_address };
// Start event spy to capture all emitted events
let mut spy = spy_events();
// Register a user which should emit a UserRegistered event
dispatcher.register_user("serah");
// Retrieve all captured events for analysis
let events = spy.get_events();
assert(events.events.len() == 1, 'There should be one event');
// Create the expected event structure for comparison
let expected_event = UserManager::Event::UserRegistered(
UserRegistered {
user_id: 1,
username: "serah",
timestamp: get_block_timestamp()
}
);
// Check if the expected event was actually emitted
assert!(events.is_emitted(contract_address, @expected_event));
// Create array of expected events for exact comparison
let expected_events: Array<(ContractAddress, Event)> = array![
(contract_address, expected_event.into()),
];
assert!(events.events == expected_events);
// Extract and examine the raw event data
let (from, event) = events.events.at(0);
assert(from == @contract_address, 'Emitted from wrong address');
// Verify event keys structure (event selector + indexed fields)
assert(event.keys.len() == 2, 'There should be two keys');
assert(event.keys.at(0) == @selector!("UserRegistered"), 'Wrong event name');
}
जब हम register_user() को कॉल करते हैं, तो यह spy.get_events() का उपयोग करके सभी कैप्चर किए गए events को पुनर्प्राप्त (retrieve) करता है और जाँच (checks) करता है:
events.is_emitted()का उपयोग करके यह पुष्टि करना कि अपेक्षित इवेंट उत्सर्जित किया गया था, और- कॉन्ट्रैक्ट एड्रेस, की काउंट (key count) (keys में ठीक 2 तत्व होने चाहिए: इवेंट सिलेक्टर + इंडेक्स किया गया
user_id), और इवेंट सिलेक्टर सहित कच्चे (raw) इवेंट स्ट्रक्चर की जांच करना।
register_user() यूज़र डेटा युक्त एक UserRegistered इवेंट उत्सर्जित करता है।
यह मैन्युअल विधि विशिष्ट इवेंट गुणों (properties) के परीक्षण की अनुमति देती है जिन्हें स्वचालित एज़र्शन (automated assertions) कवर नहीं कर सकते हैं।
इस दूसरे परीक्षण को उसी फ़ाइल tests/test_contract.cairo में पेस्ट करें, ताकि फ़ाइल में पहले और दूसरे दोनों परीक्षण शामिल हों। फिर scarb test का उपयोग करके प्रोजेक्ट का परीक्षण करने के लिए आगे बढ़ें।
आपका टर्मिनल आउटपुट दिखाना चाहिए कि परीक्षण (tests) पास हो गए हैं

Raw Event डेटा देखना
कच्चे (raw) इवेंट डेटा को देखने के लिए, आप Voyager पर इस डिप्लॉय किए गए UserManager कॉन्ट्रैक्ट का निरीक्षण (inspect) कर सकते हैं। register_user को कॉल करने से उत्सर्जित इवेंट लॉग देखने के लिए “Events” टैब पर क्लिक करें, जैसा कि नीचे दिखाया गया है:

जैसा कि पहले उल्लेख किया गया है, एक सीरियलाइज़ किया गया ByteArray एक struct है जिसमें [data, pending_word, pending_word_len] शामिल हैं, जिनमें से प्रत्येक को felt252 के रूप में संग्रहीत किया गया है। यही कारण है कि “serah” ने data[0-2] पर कब्जा कर लिया है जैसा कि ऊपर की छवि में देखा गया है।
data(data[0]): खाली ऐरे[0x0]क्योंकि “serah” (5 बाइट्स) को किसी 31-बाइट चंक्स की आवश्यकता नहीं हैpending_word(data[1]):0x7365726168में वास्तविक स्ट्रिंग बाइट्स होते हैंpending_word_len(data[2]):0x5(कुल 5 बाइट्स)- data[3]:
0x68c6c625हेक्स (hex) में टाइमस्टैम्प को इंगित करता है।
यह कच्चा (raw) दृश्य ठीक से दिखाता है कि Cairo डेटा को कैसे सीरियलाइज़ करता है; मूल डेटा स्ट्रक्चर के पुनर्निर्माण को संभव बनाते हुए सब कुछ felt252 मानों (यहाँ हेक्साडेसिमल के रूप में प्रदर्शित) के अनुक्रम (sequence) में परिवर्तित हो जाता है।
निम्नलिखित अनुभाग (sections) Starknet में इवेंट डेटा पुनर्प्राप्ति (retrieval) और प्रोसेसिंग के तीन बुनियादी दृष्टिकोण दिखाते हैं।
ऑन-चेन (On-chain) और ऑफ़-चेन (Off-chain) Events को Query और Monitor करना
इवेंट स्ट्रक्चर को समझना सिर्फ एक हिस्सा है। व्यवहार (practice) में, आप तत्काल ट्रांज़ैक्शन प्रतिक्रिया (feedback), रियल-टाइम मॉनिटरिंग या ऐतिहासिक विश्लेषण चाह सकते हैं। प्रत्येक के लिए एक अलग दृष्टिकोण की आवश्यकता होती है।
Event Logs को पार्स (Parsing) करना
इस न्यूनतम (minimal) TypeScript उदाहरण पर विचार करें जो स्पष्ट करता है कि Starknet स्मार्ट कॉन्ट्रैक्ट ट्रांज़ैक्शन से events को कैसे पार्स किया जाए जब आपको अपने स्वयं के ट्रांज़ैक्शन से तत्काल प्रतिक्रिया (feedback) की आवश्यकता हो।
- इस repository को क्लोन करें, और starknet-event-parsing डायरेक्टरी में cd करें:
git clone https://github.com/Sayrarh/starknet-event-parsing.git
cd starknet-event-parsing
- यदि आपने yarn इंस्टॉल नहीं किया है, तो पहले इसे
npm install -g yarnका उपयोग करके इंस्टॉल करें - डिपेंडेंसी इंस्टॉल करने के लिए
yarn installचलाएँ, फिरyarn add dotenvका उपयोग करके dotenv इंस्टॉल करें - रूट डायरेक्टरी में एक
.envफ़ाइल बनाएँ:
ACCOUNT_ADDRESS=0x...
PK=0x...
ALCHEMY_API_KEY=your_alchemy_api_key_here
- Alchemy से अपने वास्तविक अकाउंट एड्रेस, प्राइवेट की (private key) और API की (API key) से बदलें।
- जिस ERC-20 कॉन्ट्रैक्ट के लिए आप
Transferevents को पार्स करना चाहते हैं, उसका एड्रेस निर्दिष्ट (specify) करने के लिए मुख्य स्क्रिप्ट (src/event.ts) को संपादित करें, या किसी अन्य कॉन्ट्रैक्ट एड्रेस (यहाँ Sepolia पर STRK टोकन का उपयोग किया गया है), और प्राप्तकर्ता (recipient) का एड्रेस भी:
const contractAddress = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
const recipientAddress = "0x0207d7324a20d6A080C7EF6237D289fD57F4fb11187A64f597d4099a720FE6C5";
सुनिश्चित करें कि आपका अकाउंट ऑन-चेन सक्रिय (active) है और ट्रांज़ैक्शन शुल्क (fees) के लिए इसमें STRK टोकन हैं।
yarn devका उपयोग करके स्क्रिप्ट चलाएँ। स्क्रिप्ट यह करेगी:- Sepolia पर आपके Starknet अकाउंट से कनेक्ट करेगी
- निर्दिष्ट (specified) प्राप्तकर्ता पते (recipient address) पर 1 STRK टोकन का ट्रांसफर निष्पादित (execute) करेगी
- ट्रांज़ैक्शन कन्फर्मेशन की प्रतीक्षा करेगी
- उस ट्रांज़ैक्शन के दौरान उत्सर्जित सभी events को निकालती है और प्रदर्शित करती है

यह Starknet पर किसी भी ERC20 टोकन कॉन्ट्रैक्ट से Transfer events को पार्स करने में मदद करता है।
आप विभिन्न कॉन्ट्रैक्ट्स और परिदृश्यों (scenarios) के लिए स्क्रिप्ट को कस्टमाइज़ भी कर सकते हैं:
await eventLogic(
"0x... your contract address",
"your_function_name",
[arg1, arg2,...]
);
Events को सुनना (Listening)
यह तब काम आता है जब आपको कॉन्ट्रैक्ट गतिविधि (activity) की रियल-टाइम मॉनिटरिंग की आवश्यकता होती है। src/event.ts को निम्नलिखित कोड उदाहरण से बदलें जो हर बार ERC-20 टोकन द्वारा Transfer इवेंट उत्सर्जित करने पर कॉलबैक (callback) को ट्रिगर करता है:
// Import necessary Starknet.js components for RPC interaction
import { RpcProvider} from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function listenToTransfers() {
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
// initialize provider for Sepolia testnet with Alchemy
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_8/${alchemyApiKey}`,
});
// Contract address to monitor for events
const contractAddress = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
// Track the last processed block to avoid re-processing events
let lastBlock = 0;
async function checkForEvents() {
// Get the current block number from the network
const currentBlock = await provider.getBlockNumber();
// Only check for new events if there are new blocks
if (currentBlock > lastBlock) {
// Query for Transfer events between the last processed block and current block
const events = await provider.getEvents({
address: contractAddress, // Only events from our target contract
keys: [["0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9"]], // Transfer event selector (keccak hash)
from_block: { block_number: lastBlock + 1 }, // Start from next unprocessed block
to_block: { block_number: currentBlock }, // Query up to current block
chunk_size: 100 // Process events in batches of 100
});
// Process each detected Transfer event
events.events.forEach(event => {
console.log("Transfer event detected!", event);
});
// Update last processed block to current block
lastBlock = currentBlock;
}
}
// Set up polling: check for new events every 10 seconds
setInterval(checkForEvents, 10000);
// Run initial check immediately
checkForEvents();
}
// Start the event listener
listenToTransfers();
जब आप yarn dev चलाते हैं, तो जब तक आप Ctrl+C नहीं दबाते, तब तक आप अपने टर्मिनल आउटपुट में अंतराल (intervals) पर नए ट्रांज़ैक्शन देखेंगे।

Range द्वारा Events को फ़िल्टर करना
जब आपको ऐतिहासिक डेटा विश्लेषण (historical data analysis) और क्वेरी की आवश्यकता होती है, तो आप एक विशिष्ट ब्लॉक रेंज (block range) के भीतर ऐतिहासिक events को क्वेरी करने के लिए Starknet.js से provider.getEvents() का उपयोग कर सकते हैं।
src/event.ts को फिर से इस निम्नलिखित कोड उदाहरण से बदलें जो निर्दिष्ट कॉन्ट्रैक्ट से Transfer events के लिए ब्लॉक 8000 से 9000 (कुल 1000 ब्लॉक) खोजता है:
import { RpcProvider } from "starknet";
import * as dotenv from "dotenv";
dotenv.config();
async function filterTransferEvents() {
const alchemyApiKey = process.env.ALCHEMY_API_KEY;
// initialize provider for Sepolia testnet with Alchemy
const provider = new RpcProvider({
nodeUrl: `https://starknet-sepolia.g.alchemy.com/starknet/version/rpc/v0_8/${alchemyApiKey}`,
});
// Target contract address to query for Transfer events
const contractAddress = "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d";
const transferSelector = "0x99cd8bde557814842a3121e8ddfd433a539b8c9f14bf31ebf108d12e6196e9";
// Query for Transfer events within a specific block range
const events = await provider.getEvents({
address: contractAddress, // Only events from our target contract
keys: [[transferSelector]], // Filter for Transfer events only
from_block: { block_number: 8000 }, // Start searching from block 8000
to_block: { block_number: 9000 }, // Search up to block 9000 (1000 block range)
chunk_size: 100 // Process events in batches of 100
});
// Display total number of Transfer events found
console.log(`Found ${events.events.length} Transfer events`);
// Process and display details for each Transfer event
events.events.forEach((event, index) => {
console.log(`\n--- Transfer Event ${index + 1} ---`);
console.log("From:", event.keys[1]);
console.log("To:", event.keys[2]);
console.log("Amount (hex):", event.data[0]);
console.log("Amount (decimal):", parseInt(event.data[0], 16));
console.log("Block:", event.block_number);
console.log("Transaction:", event.transaction_hash);
});
}
// Execute the event filtering function
filterTransferEvents();
yarn dev चलाएँ और आपको निर्दिष्ट (specified) ब्लॉक रेंज के भीतर फ़िल्टर किए गए events मिलेंगे। फ़िल्टरिंग इन पैरामीटर्स का उपयोग करती है:
contractAddress: events के लिए क्वेरी करने के लिए विशिष्ट कॉन्ट्रैक्टtransferSelector: इवेंट सिग्नेचर हैश जोTransferevents की पहचान करता हैkeys: events को प्रकार (type) द्वारा फ़िल्टर करता है; केवलTransferevents लौटाए जाते हैंfrom_block/to_block: खोजने के लिए ब्लॉक रेंज को परिभाषित करता हैchunk_size: भारी रिस्पॉन्स (overwhelming responses) से बचने के लिए पेजिनेशन (pagination) को नियंत्रित करता है
फिर जानकारी निकालने के लिए इवेंट डेटा को डिकोड किया जाता है; सेंडर एड्रेस (keys[1]), प्राप्तकर्ता (recipient) एड्रेस (keys[2]), और ट्रांसफर की गई राशि (data[0])।
क्या events में वेरिएबल (variable) नाम Solidity की तरह वैकल्पिक (optional) हैं?
जैसी कि उम्मीद थी, Cairo events में वेरिएबल नाम वैकल्पिक नहीं हैं। जहाँ Solidity anonymous इवेंट पैरामीटर्स की अनुमति देता है, वहीं Cairo में सभी पैरामीटर्स के लिए इवेंट struct परिभाषाओं (definitions) में स्पष्ट (explicit) फ़ील्ड नामों की आवश्यकता होती है।
क्या events पैरेंट (parent) कॉन्ट्रैक्ट्स और इंटरफेस के माध्यम से इनहेरिट (inherit) किए जा सकते हैं?
Cairo इवेंट इनहेरिटेंस का समर्थन नहीं करता है। इसके बजाय, सभी कॉन्ट्रैक्ट्स में events का पुन: उपयोग (reuse) करने के लिए, आप कंपोनेंट्स का उपयोग करते हैं। कंपोनेंट्स अपने स्वयं के events को परिभाषित करते हैं, और जब आप अपने कॉन्ट्रैक्ट में एक कंपोनेंट शामिल करते हैं, तो आप #[flat] एट्रिब्यूट का उपयोग करके अपने कॉन्ट्रैक्ट के #[event] enum में कंपोनेंट के इवेंट प्रकार को संदर्भित (reference) करते हैं। यह कई कॉन्ट्रैक्ट्स को प्रत्येक कॉन्ट्रैक्ट में events को फिर से परिभाषित करने की आवश्यकता के बिना समान कंपोनेंट का उपयोग करके समान events को उत्सर्जित (emit) करने की अनुमति देता है।
Solidity और Cairo में events के बीच मुख्य अंतरों को ताज़ा (refresh) करने के लिए, यहाँ एक तालिका दी गई है जो एक स्पष्ट तुलना दिखाती है:
Events: Cairo और Solidity के बीच मुख्य अंतर
| पहलू (Aspect) | Cairo | Solidity |
|---|---|---|
| वेरिएबल नाम (Variable Names) | सभी पैरामीटर्स के लिए आवश्यक | वैकल्पिक (anonymous params की अनुमति है) |
| इंडेक्स किए गए पैरामीटर्स (Indexed Parameters) | #[key] एट्रिब्यूट (50 इंडेक्स किए गए पैरामीटर्स तक) |
indexed कीवर्ड (अधिकतम 3, या anonymous events के लिए 4) |
| कुल पैरामीटर्स (Total Parameters) | कोई सख्त सीमा नहीं (व्यावहारिक बाधाएँ) | कुल 17 तर्क (arguments) (ऐरे 2 के रूप में गिने जाते हैं) |
| इनहेरिटेंस (Inheritance) | कोई इनहेरिटेंस नहीं - कंपोनेंट एम्बेडिंग का उपयोग करें | पूर्ण इनहेरिटेंस समर्थित |
| इवेंट घोषणा (Event Declaration) | #[derive(starknet::Event)] struct |
event EventName(...) |
| इवेंट एमिशन (Event Emission) | self.emit(Event::EventName { ... }) |
emit EventName(...) |
| नेस्टेड (Nested) Events | फ़्लैटनिंग के लिए #[flat] एट्रिब्यूट |
समर्थित नहीं |
निष्कर्ष (Conclusion)
Cairo events को Solidity की तुलना में अधिक स्पष्ट (explicit) स्ट्रक्चर की आवश्यकता होती है और ये सख्त प्रकार परिभाषाओं (strict type definitions) और रचना पैटर्न (composition patterns) को लागू करते हैं। Cairo में, events एक साथ काम करने वाले तीन traits पर निर्भर करते हैं:
Serdeजटिल फ़ील्ड्स केfelt252मानों में सीरियलाइज़ेशन को संभालता है।Eventरसीद (receipt) के लिए keys और data ऐरे तैयार करता है।EventEmitterसंरचित (structured) इवेंट उत्सर्जित करता है।
नेस्टेड या गैर-प्रिमिटिव प्रकारों वाले Structs को संकलित (compile) करने के लिए Serde प्राप्त (derive) करना चाहिए। #[key] के साथ चिह्नित इंडेक्स किए गए फ़ील्ड फ़िल्टरिंग के लिए अलग से संग्रहीत किए जाते हैं, प्रभावी क्वेरी के लिए u32, felt252, या ContractAddress जैसे प्रिमिटिव प्रकारों पर #[key] का उपयोग करें, क्योंकि जटिल प्रकार हैश हो जाते हैं और अपठनीय (unreadable) हो जाते हैं। बेहतर क्वेरी ग्रैन्युलैरिटी (granularity) के लिए अलग-अलग इवेंट सिलेक्टर्स को सक्षम करते हुए, नेमिंग हायरार्की (naming hierarchy) को फ़्लैट (flatten) करने के लिए नेस्टेड इवेंट enums पर #[flat] एट्रिब्यूट लागू होता है।
यह लेख Starknet पर Cairo प्रोग्रामिंग (Cairo Programming on Starknet) पर एक ट्यूटोरियल श्रृंखला का हिस्सा है।