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

Send Messages (~>)

The ~> action is how Cambrian expresses outbound calls and value transfers. On EVM, typed sends lower to external calls; plain transfers lower to native currency sends.

Named (typed) send

Invoke a route on a destination whose type is known:

transfer(to, amount) ~> m_token

The message is written as a plain route call, route(args) — not Entity::route(args). The destination must be typed — an Address<Entity> member or parameter, or a predicted Entity.address(...) — so the compiler knows which entity’s route is meant. The target entity must be in the project or declared extern entity (E22 otherwise), and must declare the route. A named send (or a var capture) to a plain address is rejected (V23); plain address destinations only take value transfers.

Capturing a return value

var bal = balanceOf(who) ~> m_token;

This is a synchronous call: the callee runs, and bal binds the returned value for later actions, transforms, or a per-phase where. As with any named send, m_token must be typed (Address<Token> for a project or extern entity Token that declares balanceOf). Captures are the usual reason to introduce phases; unphased routes already flush storage updates before external calls (checks-effects-interactions).

Plain value transfer

~> recipient with { value: amount }

Transfers native currency without calling a function on the destination.

Message options (with)

On EVM the meaningful option is value — wei attached to the CALL / CREATE:

start() ~> m_worker with { value: amount }
~> msg::sender with { value: amount }

Destinations

notify(data) ~> m_observer
~> to with { value: amount }
~> msg::sender with { value: amount }
ping() ~> Token.address(token_id)

Multiple sends

distribute(amount: U256) => [
    let share = amount / 3;
    ~> m_partner_a with { value: share }
    ~> m_partner_b with { value: share }
    ~> m_partner_c with { value: amount - share * 2 }
]

Example

entity Observer {
    routes {
        on_transfer(sender: address, to: address, amount: U256) => []
    }

    m_count: u64 {
        in on_transfer(sender, to, amount) => m_count + 1
    }
}

entity Token {
    error InsufficientBalance(available: U256, requested: U256);

    routes {
        #[factory_only]
        init setup(holder: address, supply: U256, observer: Address<Observer>) => []

        transfer(to: address, amount: U256)
            where m_balances[msg::sender] >= amount : throw InsufficientBalance(m_balances[msg::sender], amount)
            && to != msg::sender : throw 101
        => [
            on_transfer(msg::sender, to, amount) ~> m_observer
        ]
    }

    m_balances: HashMap<address, U256> {
        in setup(holder, supply, _) => m_balances.insert(holder, supply)
        in transfer(to, amount) => {
            let sender = msg::sender;
            m_balances
                .insert(sender, m_balances[sender] - amount)
                .insert(to, m_balances[to] + amount)
        }
    }

    m_observer: Address<Observer> {
        in setup(_, _, observer) => observer
    }
}