Entities
An entity is the core organizational unit in Cambrian: a named unit of
persistent state plus the routes that read and update it. Every Cambrian
program contains at least one entity. Backends differ in how an entity is
hosted (on-chain instance, Lean world model, in-process library), but the
.cam shape is the same.
Declaration
An entity is declared with the entity keyword, a name, and a brace-delimited
body (names are ordinary identifiers; PascalCase is conventional, not required):
entity Token {
// type aliases, records, enums, constants, macros
// routes { ... }
// member declarations (same level as routes)
}
What an Entity Contains
An entity body can include any of the following, in any order:
| Element | Purpose | Required |
|---|---|---|
| Type aliases | Named synonyms for types | No |
| Records | Product types (structs) | No |
| Enums | Sum types (tagged unions) | No |
| Constants | Compile-time fixed values | No |
| Macros | State-aware helper expressions | No |
routes { } | Named entry points | No |
| Members | State variables with transforms | No |
A useful entity has at least one route and typically at least one member:
entity Counter {
routes {
increment() => []
}
m_count: u64 {
in increment() => m_count + 1
}
}
What an entity is at runtime
Across targets, an entity is an independent unit that:
- Owns its own persistent state (member declarations in the entity body).
- Exposes behaviour through its
routes. - Can send messages to other entities with
~>.
On EVM it is typically deployed at an address; on Lean it appears as state and route transitions in a world model.
Entity-Level Definitions
Type Aliases
entity Token {
type Amount = U256
type AccountId = address
}
Records
entity Marketplace {
record Listing {
seller: address,
price: U256,
active: bool
}
}
Enums
entity Governance {
enum ProposalStatus {
Pending,
Approved,
Rejected
}
}
Constants
entity Vault {
const MIN_DEPOSIT: U256 = 1_000_000
const MAX_SIGNERS: u64 = 10
}
Macros
entity Wallet {
macro is_owner() -> bool = {
msg::sender == m_owner
}
macro require_funded(amount: U256) -> bool = {
m_balance >= amount
}
m_owner: address {}
m_balance: U256 {}
}
Multiple Entities
A .cam file can define multiple entities. This is the usual approach when
several stateful units communicate:
entity Broker {
routes {
#[factory_only]
init setup(shop: Address<Shop>) => []
place_order(item_id: u64, quantity: u64) => [
fulfill(item_id, quantity) ~> m_shop
]
}
m_shop: Address<Shop> {
in setup(shop) => shop
}
}
entity Shop {
routes {
#[factory_only]
init setup(ledger: Address<Ledger>) => []
fulfill(item_id: u64, quantity: u64) => [
log_sale(item_id, quantity, msg::sender) ~> m_ledger
]
}
m_ledger: Address<Ledger> {
in setup(ledger) => ledger
}
}
entity Ledger {
routes {
log_sale(item_id: u64, quantity: u64, buyer: address) => []
}
m_sales: u64 {
in log_sale(item_id, quantity, buyer) => m_sales + 1
}
m_units: HashMap<u64, u64> {
in log_sale(item_id, quantity, buyer) =>
m_units.insert(item_id, m_units[item_id] + quantity)
}
}
Typing a destination as Address<E> (rather than plain address) records
which entity it points to; the compiler then reports a send to a route that
E does not declare. A named send needs such a typed destination — a plain
address only takes value transfers.
Entity vs. Pure Functions
Code that does not need state access should be placed in pure functions outside any entity. Pure functions are available to all entities in the same file:
pure fn compute_fee(amount: U256, bps: u64) -> U256 {
amount * (bps as U256) / 10000
}
entity Token {
routes {
transfer(to: address, amount: U256) => []
}
m_fees: U256 {
in transfer(to, amount) => m_fees + compute_fee(amount, 30)
}
// ...
}
See Pure Functions for details.