Deterministic Addresses
On EVM, every entity in the project gets a predictable CREATE2 address
derived from its identity arguments. Cross-entity sends, from checks, and
deploy all share that same prediction — no hand-rolled factory.
name: det-messaging
target: evm
output_dir: build/
sources:
- det_guardian.cam
- det_locker.cam
CREATE2 and CambrianFactory
The EVM backend emits a project-local CambrianFactory. Addresses are
derived from:
- the entity’s deploy bytecode (constructor args = identity fields),
- a per-deployer factory salt of
0, - the fixed factory address known at generation time.
Because the factory address itself is deterministic, every entity can compute every other entity’s address from identity fields alone.
Authors never write a custom factory.
Entity.address(args) and addressOf
Prefer the dot form. An addressOf(…) spelling exists as well; both lower
to the same CREATE2 expression:
acceptPing() ~> Locker.address(locker_id)
Arity equals the identity-member count (Identity Members).
Singletons use Entity.address() with no arguments.
Use Entity.address(…) anywhere you need a predicted address: send
destinations, member initializers, from comparisons.
from Entity(args)
On EVM, sender authentication compares msg.sender to the
same CREATE2 expression:
acceptPing()
from Guardian(m_id)
=> []
Here Locker only accepts pings from the Guardian instance whose
identity matches this locker’s m_id. from Entity(args) arity must match
the target entity’s identity-member count (V33).
deploy Entity(…)
deploy goes through CambrianFactory:
- CREATE2-deploy with identity args as constructor arguments (address
matches
Entity.address(…)), forwarding any attached value. - Call a generated, factory-guarded
initialize(…)for non-identity constructor parameters.
spawnLocker(locker_id: u64) => [
deploy Locker(locker_id)
]
createPair(tokenA: address, tokenB: address) => [
deploy UniswapV2Pair(min_addr(tokenA, tokenB),
max_addr(tokenA, tokenB))
]
Only the factory owner (bootstrap / tests) or addresses already deployed
by this factory may call deployX. EOAs cannot occupy identity CREATE2
slots directly; child deploys go through entity routes the factory already
knows. initialize() requires msg.sender == _factory and rejects
re-initialization.
Summary
| Expression | Role |
|---|---|
Entity.address(args) | Predict CREATE2 address from identity |
addressOf(…) | Equivalent spelling; same CREATE2 |
from Entity(args) | msg.sender == that CREATE2 address |
deploy Entity(args) | Factory CREATE2 + guarded initialize |
Keep identity, addressing, authentication, and deployment aligned — one argument list, one address, everywhere.