Skip to content
This guide targets Fe 26.4. Fe is not production-ready. Install Fe 26.4

ABI Compatibility

Fe events compile to standard EVM logs, ensuring compatibility with existing Ethereum tooling. Understanding this mapping helps you design events that work seamlessly with indexers, explorers, and frontend libraries.

Every EVM log has two parts:

  1. Topics: Up to 4 indexed values (32 bytes each)
  2. Data: ABI-encoded non-indexed fields
Log Entry
├── topics[0]: Event signature hash (keccak256)
├── topics[1]: First indexed field
├── topics[2]: Second indexed field
├── topics[3]: Third indexed field
└── data: ABI-encoded remaining fields

Topic 0 is always the keccak256 hash of the event signature:

#[event]
struct Transfer {
#[indexed]
from: Address,
#[indexed]
to: Address,
amount: u256,
}

The signature is: Transfer(address,address,uint256)

Topic 0 becomes: keccak256("Transfer(address,address,uint256)")

This matches Solidity’s event encoding, ensuring tools recognize your events.

Each #[indexed] field becomes a topic:

#[event]
struct Transfer {
#[indexed]
from: Address, // → topics[1]
#[indexed]
to: Address, // → topics[2]
amount: u256, // → data
}

When emitting Transfer { from: Address { inner: 0x123 }, to: Address { inner: 0x456 }, amount: 1000 }:

ComponentValue
topics[0]keccak256("Transfer(address,address,uint256)")
topics[1]0x123 (from)
topics[2]0x456 (to)
dataABI-encoded 1000

Fields without #[indexed] are ABI-encoded into the data section:

#[event]
struct Swap {
#[indexed]
sender: Address,
amount_in: u256, // → data
amount_out: u256, // → data
timestamp: u256, // → data
}

The data section contains: abi.encode(amount_in, amount_out, timestamp)

Fe types map to Solidity/ABI types:

Fe TypeABI TypeNotes
u256uint256Direct mapping
u128uint128Direct mapping
u64uint64Direct mapping
u32uint32Direct mapping
u16uint16Direct mapping
u8uint8Direct mapping
i256int256Direct mapping
boolboolDirect mapping
AddressaddressUse for address fields, including indexed ones

To emit ERC20-compatible events:

// ERC20 Transfer event
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
#[event]
struct Transfer {
#[indexed]
from: Address, // Solidity address
#[indexed]
to: Address, // Solidity address
value: u256,
}
// ERC20 Approval event
// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value)
#[event]
struct Approval {
#[indexed]
owner: Address,
#[indexed]
spender: Address,
value: u256,
}

These produce logs that standard ERC20 tools can parse.

// Filter for Transfer events from a specific address
const filter = contract.filters.Transfer(fromAddress, null);
const logs = await contract.queryFilter(filter);
// Parse a Transfer event
const event = contract.interface.parseLog(log);
console.log(event.args.from, event.args.to, event.args.value);
// Get past Transfer events
const events = await contract.getPastEvents('Transfer', {
filter: { from: fromAddress },
fromBlock: 0,
toBlock: 'latest'
});

Fe events work with The Graph for indexing:

type Transfer @entity {
id: ID!
from: Bytes!
to: Bytes!
value: BigInt!
}

Calculate event signatures the same way as Solidity:

Event: Transfer(address indexed from, address indexed to, uint256 value)
Signature string: "Transfer(address,address,uint256)"
Topic 0: keccak256(signature string)

Note: The signature includes all parameter types, not just indexed ones.

To emit events compatible with existing Solidity contracts:

// Match Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
#[event]
struct Transfer {
#[indexed]
from: Address,
#[indexed]
to: Address,
value: u256, // Use 'value' to match Solidity field name
}

The struct field names don’t affect ABI encoding. The event struct name, field types, and their order determine the signature. A struct named TransferEvent has a different topic from Transfer.

Remember the EVM constraints:

ConstraintLimit
Max topics4 (including signature)
Max indexed fields3
Topic size32 bytes each

Fe adds its own limit on top of these: an event can have at most 16 non-indexed fields. The EVM itself does not limit the size of the data section.

Fe rejects unsupported indexed dynamic fields. Put dynamic values in the data section, or compute and index a fixed-size hash explicitly. Do not assume a Solidity-style automatic hash is generated for every indexed type.

Match established conventions:

// ERC20 standard names
struct Transfer { ... }
struct Approval { ... }
// ERC721 standard names
struct Transfer { ... } // Same name, different context
struct ApprovalForAll { ... }

Field order affects the signature:

// Order: indexed fields first, then data fields
#[event]
struct Transfer {
#[indexed]
from: Address, // First in signature
#[indexed]
to: Address, // Second in signature
amount: u256, // Third in signature
}
// Signature: Transfer(address,address,uint256)

Include signatures in your documentation:

/// Transfer event
/// Signature: Transfer(address,address,uint256)
/// Topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
#[event]
struct Transfer {
#[indexed]
from: Address,
#[indexed]
to: Address,
amount: u256,
}
ConceptDescription
topics[0]keccak256 of event signature
topics[1-3]Indexed field values
dataABI-encoded non-indexed fields
SignatureEventName(type1,type2,...)
Max indexed3 fields

Fe events are fully ABI-compatible with Ethereum tooling. Design your events to match established standards when implementing common interfaces like ERC20 or ERC721.