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

Multi-Entity Messaging

A minimal two-entity system with deterministic CREATE2 addresses, deploy, typed sends, and from Entity(args) authentication. Source: contracts/det_guardian.cam, contracts/det_locker.cam, and contracts/det_messaging.yaml.

Project

name: det-messaging
target: evm
output_dir: build/
sources:
  - det_guardian.cam
  - det_locker.cam
cambrian-transpiler --project contracts/det_messaging.yaml

Both entities declare identity m_id: u64, so Guardian.address(id) and Locker.address(id) are stable CREATE2 predictions. The auto-generated CambrianFactory deploys them.

Architecture

Guardian(id)                     Locker(id)
+------------------+             +------------------+
| spawnLocker(id)  | --deploy--> | (new instance)   |
| ping(locker_id)  | ----~>----> | acceptPing()     |
|                  |             |   from Guardian(m_id)
+------------------+             +------------------+

Matching identity values: a locker with m_id = 7 only accepts acceptPing from Guardian(7).

Guardian

entity Guardian {
    identity m_id: u64

    routes {
        constructor() => []

        ping(locker_id: u64) => [
            acceptPing() ~> Locker.address(locker_id)
        ]

        spawnLocker(locker_id: u64) => [
            deploy Locker(locker_id)
        ]

        getPings() -> u64 => [
            return(m_pings_sent)
        ]
    }

    m_pings_sent: u64 {
        in constructor() => 0
        in ping(_) => m_pings_sent + 1
    }
}
  • deploy Locker(locker_id) — factory CREATE2; address equals Locker.address(locker_id).
  • acceptPing() ~> Locker.address(…) — typed send to the predicted address (no stored handle required).

Locker

entity Locker {
    identity m_id: u64

    routes {
        constructor() => []

        acceptPing()
            from Guardian(m_id)
            => []

        getPingCount() -> u64 => [
            return(m_ping_count)
        ]
    }

    m_ping_count: u64 {
        in constructor() => 0
        in acceptPing() => m_ping_count + 1
    }
}

from Guardian(m_id) lowers to require(msg.sender == CREATE2(Guardian, m_id)) on EVM — sender type and identity in one clause.

Patterns

PatternExample
Shared identity keySame m_id on both entities
Predict without storingLocker.address(locker_id)
Child deploydeploy Locker(locker_id)
Authenticate peerfrom Guardian(m_id)

Larger systems use the same idioms: Governor holds Address<ERC20Votes> / Address<TimelockController> and Uniswap’s factory does deploy UniswapV2Pair(t0, t1) then records UniswapV2Pair.address(t0, t1). See Governor and Uniswap V2.

Background: Identity Members, Deterministic Addresses, Multi-File Projects.