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
}
}