Skip to content
Pre-Release: Fe is under active development. This documentation covers the upcoming release. Follow progress on GitHub

Emitting Events

Events are emitted using a Log effect, which records data to the blockchain that external systems can observe. This section covers how to emit events in your Fe contracts.

Emit an event through a Log effect:

struct Transfer {
#[indexed]
from: u256,
#[indexed]
to: u256,
amount: u256,
}
fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) {
log.emit(Transfer { from, to, amount })
}

The emit method takes an event struct instance and records it to the blockchain.

Events are typically emitted within message handlers:

pub struct TokenStorage {
pub balances: Map<u256, u256>,
}
struct Transfer {
#[indexed]
from: u256,
#[indexed]
to: u256,
amount: u256,
}
fn do_transfer(from: u256, to: u256, amount: u256)
-> bool uses (mut store: TokenStorage, mut log: Log)
{
let from_bal = store.balances.get(from)
if from_bal < amount {
return false
}
store.balances.set(from, from_bal - amount)
let to_bal = store.balances.get(to)
store.balances.set(to, to_bal + amount)
// Emit event after successful state change
log.emit(Transfer { from, to, amount })
true
}

A critical pattern: emit events after state changes succeed, not before:

fn transfer(from: u256, to: u256, amount: u256)
-> bool uses (mut store: TokenStorage, mut log: Log)
{
// 1. Validate
let from_bal = store.balances.get(from)
if from_bal < amount {
return false
}
// 2. Update state
store.balances.set(from, from_bal - amount)
store.balances.set(to, store.balances.get(to) + amount)
// 3. Emit event (state change succeeded)
log.emit(Transfer { from, to, amount })
true
}

This ensures events reflect actual state changes.

In contracts, declare storage and log as contract fields, then access them via uses:

pub struct TokenStorage {
pub balances: Map<u256, u256>,
}
struct TransferEvent {
#[indexed]
from: u256,
#[indexed]
to: u256,
amount: u256,
}
msg TokenMsg {
#[selector = 0xa9059cbb]
Transfer { to: u256, amount: u256 } -> bool,
}
contract Token {
mut store: TokenStorage,
mut log: Log,
recv TokenMsg {
Transfer { to, amount } -> bool uses (mut store, mut log) {
do_transfer(caller(), to, amount)
}
}
}

Emit different event types from the same handler:

struct Transfer {
#[indexed]
from: u256,
#[indexed]
to: u256,
amount: u256,
}
struct Approval {
#[indexed]
owner: u256,
#[indexed]
spender: u256,
amount: u256,
}
fn transfer_from(spender: u256, from: u256, to: u256, amount: u256)
-> bool uses (mut store: TokenStorage, mut log: Log)
{
// Check and update allowance
let allowed = store.allowances.get(from).get(spender)
if allowed < amount {
return false
}
store.allowances.get(from).set(spender, allowed - amount)
// Perform transfer
let from_bal = store.balances.get(from)
store.balances.set(from, from_bal - amount)
store.balances.set(to, store.balances.get(to) + amount)
// Emit both events
log.emit(Approval {
owner: from,
spender,
amount: allowed - amount,
})
log.emit(Transfer { from, to, amount })
true
}

Emit when persistent state changes:

fn mint(to: u256, amount: u256) uses (mut store: TokenStorage, mut log: Log) {
store.balances.set(to, store.balances.get(to) + amount)
store.total_supply = store.total_supply + amount
log.emit(Transfer { from: 0, to, amount })
}
fn burn(from: u256, amount: u256) uses (mut store: TokenStorage, mut log: Log) {
store.balances.set(from, store.balances.get(from) - amount)
store.total_supply = store.total_supply - amount
log.emit(Transfer { from, to: 0, amount })
}

Emit for ownership and configuration changes:

struct OwnershipTransferred {
#[indexed]
previous_owner: u256,
#[indexed]
new_owner: u256,
}
fn transfer_ownership(new_owner: u256)
uses (mut admin: AdminStorage, mut log: Log)
{
let previous = admin.owner
admin.owner = new_owner
log.emit(OwnershipTransferred {
previous_owner: previous,
new_owner,
})
}

Occasionally emit for important queries (use sparingly):

struct BalanceChecked {
#[indexed]
account: u256,
balance: u256,
}
// Usually not needed - avoid unless there's a specific reason

Create helper functions for common events:

fn emit_transfer(from: u256, to: u256, amount: u256) uses (mut log: Log) {
log.emit(Transfer { from, to, amount })
}
fn emit_approval(owner: u256, spender: u256, amount: u256) uses (mut log: Log) {
log.emit(Approval { owner, spender, amount })
}
fn transfer(from: u256, to: u256, amount: u256)
-> bool uses (mut store: TokenStorage, mut log: Log)
{
// ... transfer logic ...
emit_transfer(from, to, amount)
true
}

Only emit when something meaningful happens:

fn set_approval(owner: u256, spender: u256, new_amount: u256)
uses (mut store: TokenStorage, mut log: Log)
{
let current = store.allowances.get(owner).get(spender)
// Only emit if value actually changes
if current != new_amount {
store.allowances.get(owner).set(spender, new_amount)
log.emit(Approval { owner, spender, amount: new_amount })
}
}
PatternDescription
log.emit(Event { ... })Emit an event
Emit after state changeEnsures event reflects actual changes
Multiple eventsSame handler can emit different types
Helper functionsCentralize event emission
Conditional emitOnly emit on meaningful changes

Events are your contract’s public record. Emit them consistently after successful state changes to enable reliable off-chain indexing.