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

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:

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

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

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

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

Each #[indexed] field becomes a topic:

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

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

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

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

struct Swap {
#[indexed]
sender: u256,
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

To emit ERC20-compatible events:

// ERC20 Transfer event
// Solidity: event Transfer(address indexed from, address indexed to, uint256 value)
struct Transfer {
#[indexed]
from: u256, // address as u256
#[indexed]
to: u256, // address as u256
value: u256,
}
// ERC20 Approval event
// Solidity: event Approval(address indexed owner, address indexed spender, uint256 value)
struct Approval {
#[indexed]
owner: u256,
#[indexed]
spender: u256,
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(uint256 indexed from, uint256 indexed to, uint256 value)
Signature string: "Transfer(uint256,uint256,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)
struct Transfer {
#[indexed]
from: u256,
#[indexed]
to: u256,
value: u256, // Use 'value' to match Solidity field name
}

The struct field names don’t affect ABI encoding. Only the types and their order matter for the signature.

Remember the EVM constraints:

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

Large values in indexed fields are hashed:

  • Values ≤ 32 bytes: stored directly
  • Values > 32 bytes: keccak256 hash stored

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
struct Transfer {
#[indexed]
from: u256, // First in signature
#[indexed]
to: u256, // Second in signature
amount: u256, // Third in signature
}
// Signature: Transfer(uint256,uint256,uint256)

Include signatures in your documentation:

/// Transfer event
/// Signature: Transfer(uint256,uint256,uint256)
/// Topic 0: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
struct Transfer {
#[indexed]
from: u256,
#[indexed]
to: u256,
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.