Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

ActionSyntaxChapter
Send messageMessage(args) ~> destSend Messages
Plain transfer~> dest with { value: amount }Send Messages
Deploydeploy Entity with { ... }Deploy
Emit eventemit Event(args)Emit Events
Conditionalif cond => [actions] else [actions]Conditional Actions
Returnreturn(value)Return Values
Callcall route(args)Call
Let bindinglet 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:

  1. All let bindings are evaluated.
  2. Sends and deploys are queued.
  3. Conditional actions branch based on their guard.
  4. 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]
            ]
        ]
    }
}