Route Actions
Route actions define what happens when a route is invoked. They appear inside
the action list after the => arrow. Actions are the imperative side of an
entity — they send messages, deploy instances, bind intermediates, and produce
return values.
Action List
The action list is enclosed in square brackets. Multiple actions are separated by commas:
transfer(to: address, amount: U256) => [
let fee = compute_fee(amount);
let net = amount - fee;
Transfer(net) ~> to,
Transfer(fee) ~> m_treasury
]
An empty action list is written as []:
increment() => []
Action Types
| Action | Syntax | Chapter |
|---|---|---|
| Send message | Message(args) ~> dest | Send Messages |
| Plain transfer | ~> dest with { value: amount } | Send Messages |
| Deploy | deploy Entity with { ... } | Deploy |
| Emit event | emit Event(args) | Emit Events |
| Conditional | if cond => [actions] else [actions] | Conditional Actions |
| Return | return(value) | Return Values |
| Call | call route(args) | Call |
| Let binding | let x = expr; | See below |
Let Bindings in Actions
The let keyword introduces a named intermediate value within the action list.
Let bindings are terminated by a semicolon:
swap(amount_in: U256) => [
let fee = amount_in * 3 / 1000;
let amount_out = calculate_output(amount_in - fee);
Transfer(amount_out) ~> msg::sender,
CollectFee(fee) ~> m_fee_collector
]
Let bindings are evaluated in order. Later bindings and actions can reference earlier bindings.
Execution Order
Actions within a route execute in declaration order:
- All
letbindings are evaluated. - Sends and deploys are queued.
- Conditional actions branch based on their guard.
- Member transforms execute atomically with the route.
The combination of actions and member transforms defines the complete effect of a route invocation.
Actions and State
Actions have read access to member values (the pre-transform values). State mutations happen through member transforms, not through actions. This separation is a core design principle of Cambrian.
entity Counter {
routes {
increment_and_notify(observer: address) => [
// m_count here is the *pre-increment* value
Notify(m_count) ~> observer
]
}
m_count: u64 {
in increment_and_notify(_) => m_count + 1
}
}
To access the post-transform value within the same route, use temporal references. See Temporal References.
Complete Example
entity DEX {
routes {
swap(token_in: address, amount_in: U256, min_out: U256)
where amount_in > 0 : throw 100
=> [
let reserve_in = m_reserves[token_in];
let reserve_out = m_reserves[m_paired_token[token_in]];
let amount_out = (amount_in * reserve_out) / (reserve_in + amount_in);
if amount_out < min_out => [
throw SlippageExceeded()
] else [
Token::transfer(msg::sender, amount_out) ~> m_paired_token[token_in]
]
]
}
}