Introduction
Contract Events enable powerful on-chain communication between smart contracts through an event-driven architecture. Contracts can emit events with custom data, while other contracts can register persistent listeners that automatically execute when specific events are triggered.
This creates a decentralized pub/sub (publish-subscribe) pattern directly on the blockchain, enabling real-time contract-to-contract signaling without requiring direct coupling between contracts.
How It Works
Contract events operate through two main mechanisms:
1. Emitting Events
Any contract can emit an event using the emit_event() function:
entry call_event() {
emit_event(42, ["hello", "world!"]);
return 0
}Signature:
emit_event(event_id: u64, data: any[])event_id: A numeric identifier for the event type (chosen by the contract developer)data: An array of values to pass to listeners (can be any serializable types)
2. Listening to Events
Contracts can register persistent listeners for events emitted by other contracts:
fn on_contract_event(a: string, b: string) -> u64 {
assert(a == "hello");
assert(b == "world!");
println(a + " " + b + " !");
return 0
}
hook constructor() -> u64 {
let contract_hash = Hash::from_hex("CONTRACT_HASH");
let contract = Contract::new(contract_hash).expect("load contract");
contract.listen_event(42, on_contract_event, 500);
return 0
}Signature:
listen_event(event_id: u64, callback: fn, max_gas: u64) -> boolevent_id: The event identifier to listen forcallback: The function to execute when the event is emittedmax_gas: Maximum gas allocated for the callback execution
Execution Model
Event listener callbacks are executed automatically after the emitting contract’s transaction completes successfully, within the same block but as a separate transaction.
Execution Flow
- Contract A emits an event with
emit_event(event_id, data) - Contract A’s transaction completes successfully
- The blockchain identifies all registered listeners for that event from Contract A
- Each listener callback is executed as a separate transaction in the same block
- The callback receives the emitted data as parameters
Transaction Context
- The contract caller for the listener callback is the same hash as the transaction that initiated the event (or the scheduled execution hash)
- This allows listener contracts to identify the originating transaction
- Callbacks execute with their allocated
max_gaslimit
Listener Characteristics
Persistence
- Event listeners are stored on-chain permanently
- Once registered, they cannot be unregistered or modified
- Listeners survive as long as the contract exists or until they are executed.
You can still register the listener again from the same callback execution to ensure it remains active for future events.
Uniqueness
- One listener per contract/event key: Each contract can register only one listener for a specific
event_idfrom a specific contract - Attempting to register a second listener for the same event from the same contract will fail or overwrite the existing one
Permissions
- No permission restrictions: Any contract can listen to events from any other contract
- This creates an open ecosystem for cross-contract communication
Use Cases
1. Decentralized Oracle Networks
Contracts can listen to oracle price feed events:
fn on_price_update(asset: string, price: u64) -> u64 {
let storage = Storage::new();
storage.store("price_" + asset, price);
storage.store("last_update", Block::current().timestamp());
return 0
}
hook constructor() -> u64 {
let oracle_hash = Hash::from_hex("ORACLE_CONTRACT_HASH");
let oracle = Contract::new(oracle_hash).expect("load oracle");
oracle.listen_event(1, on_price_update, 1000);
return 0
}2. Automated Market Makers (AMM)
Listen to liquidity pool events to update aggregated data:
fn on_swap_executed(token_in: Hash, token_out: Hash, amount: u64) -> u64 {
let storage = Storage::new();
let total_volume = storage.load("total_volume").unwrap_or(0);
storage.store("total_volume", total_volume + amount);
// Emit analytics event
emit_event(100, [token_in, token_out, amount]);
return 0
}3. DAO Proposal Execution
Execute actions when governance proposals pass:
fn on_proposal_passed(proposal_id: u64, action: string) -> u64 {
let storage = Storage::new();
if action == "upgrade" {
storage.store("pending_upgrade", proposal_id);
emit_event(200, ["upgrade_scheduled", proposal_id]);
}
return 0
}4. NFT Marketplaces
Track NFT sales and update marketplace statistics:
fn on_nft_sold(token_id: u64, price: u64, buyer: Address) -> u64 {
let storage = Storage::new();
// Update floor price
let current_floor = storage.load("floor_price").unwrap_or(price);
if price < current_floor {
storage.store("floor_price", price);
}
// Track total sales
let total_sales = storage.load("total_sales").unwrap_or(0);
storage.store("total_sales", total_sales + 1);
return 0
}5. Gaming Leaderboards
Automatically update leaderboards when game events occur:
fn on_game_completed(player: Address, score: u64) -> u64 {
let storage = Storage::new();
let current_high_score = storage.load("high_score").unwrap_or(0);
if score > current_high_score {
storage.store("high_score", score);
storage.store("high_score_holder", player);
// Notify achievement contract
emit_event(300, [player, score]);
}
return 0
}6. Event Chains & Cascading Logic
Events can trigger other events, creating complex workflows:
fn on_deposit_detected(user: Address, amount: u64) -> u64 {
let storage = Storage::new();
// Update user balance
let balance = storage.load(user).unwrap_or(0);
storage.store(user, balance + amount);
// Check if user reached milestone
if balance + amount >= 1000000 {
emit_event(500, [user, "milestone_reached"]);
}
return 0
}Limitations
- Cannot unregister: Once a listener is registered, it persists indefinitely
- One listener per key: Only one callback can be registered per contract/event combination
- Gas limits: Callbacks must complete within their allocated gas
- Same block execution: All listeners execute in the same block as the event, which may impact block processing time
Conclusion
Contract Events provide a powerful mechanism for building complex, decentralized applications with loosely coupled components. By enabling contracts to signal state changes and react to external events automatically, they unlock new patterns for composability, scalability, and modularity in blockchain development.
Whether you’re building DeFi protocols, NFT marketplaces, gaming systems, or DAOs, contract events enable sophisticated on-chain coordination while maintaining clean separation of concerns.