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

Handler Syntax

Handlers are the functions inside recv blocks that process incoming messages. Each handler matches a specific message variant and implements its logic.

A handler consists of a pattern, optional return type, and body:

VariantName { fields } -> u256 {
// handler body
0
}

For handlers that don’t return a value:

VariantName { fields } {
// handler body, implicitly returns ()
}

Handlers use pattern matching to destructure message fields:

Extract fields by their names:

recv TokenMsg {
Transfer { to, amount } -> bool {
// 'to' and 'amount' are available as local variables
true
}
}

Give fields different local names:

recv TokenMsg {
Transfer { to: recipient, amount: value } -> bool {
// Use 'recipient' and 'value' instead of 'to' and 'amount'
true
}
}

Use _ to ignore specific fields:

recv TokenMsg {
Transfer { to, amount: _ } -> bool {
// Only use 'to', ignore the amount
true
}
}

Use .. to ignore all remaining fields:

recv TokenMsg {
TransferFrom { from, .. } -> bool {
// Only use 'from', ignore 'to' and 'amount'
true
}
}

For variants without parameters, omit the braces:

recv TokenMsg {
TotalSupply -> u256 {
1000000
}
}

The return type must match the message variant’s declaration:

msg Query {
#[selector = 0x70a08231]
BalanceOf { account: u256 } -> u256,
}
recv Query {
BalanceOf { account } -> u256 {
// Must return u256
get_balance(account)
}
}

Handlers without a return type implicitly return ():

msg Commands {
#[selector = 0x42842e0e]
SafeTransfer { from: u256, to: u256, token_id: u256 },
}
recv Commands {
SafeTransfer { from, to, token_id } {
// No return type means () is returned
}
}

Handler bodies contain the implementation logic. They can use all standard Fe expressions and statements.

recv TokenMsg {
TotalSupply -> u256 {
1000000
}
BalanceOf { account } -> u256 {
if account == 0 {
0
} else {
100
}
}
}

Use return for early exits:

recv TokenMsg {
Transfer { to, amount } -> bool {
if amount == 0 {
return false
}
if to == 0 {
return false
}
true
}
}

Handlers typically delegate to helper functions:

fn validate_transfer(to: u256, amount: u256) -> bool {
to != 0 && amount > 0
}
fn execute_transfer(to: u256, amount: u256) -> bool uses (mut store: TokenStorage) {
// transfer logic using storage effect
true
}
recv TokenMsg {
Transfer { to, amount } -> bool uses (mut store) {
if !validate_transfer(to, amount) {
return false
}
execute_transfer(to, amount)
}
}

Handlers access contract state through effects:

pub struct TokenStorage {
pub balances: Map<u256, u256>,
pub total_supply: u256,
}
fn get_balance(account: u256) -> u256 uses (store: TokenStorage) {
store.balances.get(account)
}
fn add_balance(account: u256, amount: u256) uses (mut store: TokenStorage) {
let current = store.balances.get(account)
store.balances.set(account, current + amount)
}
contract Token {
mut store: TokenStorage,
recv TokenMsg {
BalanceOf { account } -> u256 uses (store) {
get_balance(account)
}
Transfer { to, amount } -> bool uses (mut store) {
add_balance(to, amount)
true
}
}
}

Handlers can access transaction context using built-in functions:

recv TokenMsg {
Transfer { to, amount } -> bool {
let sender = caller() // Get the message sender
do_transfer(sender, to, amount)
}
}

Common context functions:

  • caller() - The address that called this contract
  • self_address() - The contract’s own address
  • block_number() - Current block number
  • block_timestamp() - Current block timestamp
PatternDescription
{ field }Extract field with same name
{ field: name }Extract field with new name
{ field: _ }Ignore specific field
{ .., field }Extract one, ignore rest
-> Type { }Handler with return type
{ }Handler returning ()