Events in Cairo smart contracts allow you to emit and record data on the Starknet blockchain. They are essential for tracking important state changes and providing transparency to users and other contracts. Events are also useful when building interfaces, to be notified about important state changes. To use events in your contract:
  1. Create event structs that derive the starknet::Event trait
  2. Define an Event enum in the contract, annotated with #[event], where each variant is linked to an event struct
  3. Emit events with the emit function
You can make events searchable by adding the #[key] attribute to specific fields, which indexes them for efficient querying later. Events variant names and structs are recommended to be named consistently, even if it create some redundancy when emitting events. Here’s a practical example of a contract that emits events when incrementing a counter:
#[starknet::interface]
trait ICounterEvents<TContractState> {
    fn increment(ref self: TContractState);
    fn get_count(self: @TContractState) -> u128;
}

// Event structs defined outside the contract for better organization
#[derive(Drop, starknet::Event)]
enum CounterEvent {
    Incremented: Incremented,
    Reset: Reset,
}

#[derive(Drop, starknet::Event)]
struct Incremented {
    #[key]
    user: ContractAddress,
    new_count: u128,
    timestamp: u64,
}

#[derive(Drop, starknet::Event)]
struct Reset {
    #[key]
    user: ContractAddress,
    old_count: u128,
    timestamp: u64,
}

#[starknet::contract]
mod CounterEvents {
    use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
    use super::{ICounterEvents, CounterEvent, Incremented, Reset};

    #[storage]
    struct Storage {
        count: u128,
    }

    #[event]
    #[derive(Drop, starknet::Event)]
    enum Event {
        Incremented: Incremented,
        Reset: Reset,
    }

    #[abi(embed_v0)]
    impl CounterEvents of super::ICounterEvents<ContractState> {
        fn increment(ref self: ContractState) {
            let current_count = self.count.read();
            let new_count = current_count + 1;
            self.count.write(new_count);

            // Emit the Incremented event
            self.emit(CounterEvent::Incremented(Incremented {
                user: starknet::get_caller_address(),
                new_count,
                timestamp: starknet::get_block_timestamp(),
            }));
        }

        fn get_count(self: @ContractState) -> u128 {
            self.count.read()
        }
    }

    // Internal function to reset counter
    fn reset_counter(ref self: ContractState) {
        let old_count = self.count.read();
        self.count.write(0);

        // Emit the Reset event
        self.emit(CounterEvent::Reset(Reset {
            user: starknet::get_caller_address(),
            old_count,
            timestamp: starknet::get_block_timestamp(),
        }));
    }
}
For better code organization, especially in larger contracts, you can define event structs outside of the contract module, as shown in the example here. While this allows you to group related events in separate modules or files, remember that you must still include all event variants in the contract’s Event enum.