#[contract] codebase, multiple installable component roles — and handles deployment, installation, access control, and type-safe calls within and across entities.
Genie is an early preview. The API surface will evolve.
Prerequisite. This reference assumes you are familiar with how The Grid structures execution and state. If entities, components, and activations are new, read these first:
- Entities and accounts — what an entity is and how it holds components
- Components — installable logic and storage, same-entity vs cross-entity interaction
- Activations — how work is submitted, queued, and routed to components
Contracts and the entity model
The entity and component model describes how code and state are structured on The Grid.- Entity — an addressable container and the unit of sequential execution. Activations arrive at an entity, enter its queue, and are processed one at a time; each is routed to a component inside the entity. See Entities and accounts.
- Component — immutable logic and independent state installed inside an entity. Each component owns its storage; many components can coexist on one entity. See Components.
- Logic immutability — Installing a component into an entity does not let the entity owner tamper with it. The instance runs only its verified on-chain code and responds through its published ABI; every activation is handled according to that logic and its access rules, not at the installer’s discretion.
- Contract — one verified codebase that defines one or more component types (for example root, issuer, holder). Deploy creates the root instance; install creates other component types on an entity.
- Co-location boundary — components on the same entity can call each other synchronously within a single activation on that entity’s queue. Components on different entities do not share storage; they interact only by sending activations between entities.
#[deploy] instantiates the root component on an entity; #[install] adds non-root types. The manifest’s component_type_index identifies each type when you push and install from the CLI.
Actor model
In FIRE, the actor model is the foundation for distributing execution and state. On The Grid, the entity is the actor: it owns the activation queue and processes work sequentially. Components are the immutable logic and independent state modules that live inside an entity and run when an activation reaches them.Core principles
- Message passing and sequential handling — Entities communicate asynchronously by sending activations to one another. Each entity maintains its own queue and processes incoming activations one at a time, in order, dispatching each to the targeted component inside it.
- Independent execution units — Each entity runs on its own queue. Work on one entity does not block unrelated entities elsewhere in the system.
- Immediate state commit — When an activation completes on an entity, the affected components’ state changes commit immediately. Local state stays consistent without waiting for cross-entity consensus.
- State isolation — Each component maintains its own storage. During an activation, a component may update its own state and trigger new activations to other entities; it never directly mutates another component’s storage.
Operational considerations
Distributing state and execution trades synchronous, all-or-nothing transactions for parallel throughput. The async consequences are familiar from other actor and message-passing systems. Two show up most often:- No transactional revert — Each activation is its own commit boundary. A failure downstream does not roll back work already committed upstream; coordinate across steps explicitly instead of assuming one global transaction.
- Stale reads and interleaving — As in ordinary async code, state can change between sending a request and handling the response. Design multi-step flows with continuations and idempotent handlers where retries are possible.
Key FIRE concepts
FIRE is how you scale safely on The Grid. Scale requires asynchrony — throughput comes from distributed execution across entities, not from serializing the whole network. Mitigate async complexity with developer-defined guarantees and explicit design patterns rather than assuming one global transaction. Unlock parallel scalability by structuring work so independent entities and components run concurrently without a single hot spot. The principles below spell that out and map each one to Genie:
Used together, these principles are how applications built with FIRE are planned for scalability: independent entities and components handle work in parallel — cross-entity
remote calls and async continuations where work should be distributed, flow control and error handling shaped by component relations, and entity-level atomicity through co-location when a step must stay local.
Feature snapshot
Common types
Quick start
Define a contract
Contract crates target RISC-V and use#![no_std] instead of the full standard library. If the contract uses heap types (String, Vec, and so on), also add extern crate alloc at the top of src/lib.rs:
Use the component
Macro reference
Genie uses procedural macros to define component boundaries, roles, and communication patterns. Entry-point attributes (#[view], #[owner], #[public], #[private]) also generate async variants for remote client calls.
Contract definition
Component lifecycle
Access control
Read-only methods
Attribute usage patterns
Read-only versus mutable methods
Storage API
StorageKey — a byte key for keyed storage (StorageKey::from(...)). Keyed methods namespace values by type plus key, so you can store multiple values of the same type.
StorageGuard<T> — a load–modify–save wrapper returned by load_guarded. Mutate through it like a reference; changes persist when it is dropped. Do not hold it across .await.
Member storage (automatic field persistence)
Components can define struct fields that are automatically persisted to storage. When a component struct has fields:- On load. The entire struct is loaded from storage using
Storage::load_self<T>(). - On save. After mutable method execution, the entire struct is saved using
Storage::save_self<T>(&object).
&self, &mut self, and field access, like ordinary Rust.
Runtime and communication API
Remote activation patterns
Remote calls from generated clients return aComponentCallFuture. Three ways to run them:
Await
Wait for the result in the same method. The execution yields until the remote call completes.async.
Spawn
Queue the call withgenie::spawn and continue local work. All queued spawns are sent as activations when execution yields.
Spawn with callbacks
Attach a#[private] handler. The runtime invokes it in a later activation when the call completes or fails.
.on_success or .on_result for other callback shapes.
Component communication patterns
Local versus remote calls
Generated clients expose.local and .remote routers on each component role.
Typed role references
Method parameters can use generated refs instead of rawGvmComponentId. Import from super::self_refs:
GvmComponentId and need a typed ref (or the reverse), use .try_into()?.
Calling root from a non-root component
Non-root components call root methods viaSelf::root_client():
Error handling
Fallible component methods returngenie::Result<T> (alias for Result<T, GenieError>). Returning Err reports failure to the caller; it does not revert storage already written in the same activation.
Validation
Useensure! for early returns — with a message string or a typed error that implements Into<GenieError>.
Component errors
Define one error enum per component with#[error_type]. Return typed errors via .into().
Rollback on failed remote calls
Remote calls can fail after local state has already changed. Update local state first, then revert manually if the remote leg fails:Cross-contract errors
Usetry_as() to match a callee’s typed error after a failed remote call.
Cross-contract composition
A contract can takeGvmContract handles to already-deployed contracts, install their components, and call them through generated clients.
Two-token pool example
A pool that trades token A for token B needs one holder per token contract — two holders because there are two different tokens. Root installs pool holders on its own entity at#[deploy] (the liquidity reserves):
#[install]. Root passes the token contracts and its pool holder IDs through InstallData:
Generated infrastructure
The#[contract] macro automatically generates:
- Contract client —
{Contract}Clientwith a router per component role (.local,.remote,.callbacks): - Installers —
{Contract}Installerfor installing non-root components: - Components enum — one variant per non-root role (for example
SimpleCounterContractComponents::Controller). Passed to the root’s#[installation_request]handler so it knows which component is being installed: - Callback router — typed refs for
#[private]methods, wired viaSelf::callbacks()in spawn handlers: - Role refs —
super::self_refs::{Holder, Issuer, ...}for type-safe same-contract method parameters. Self::root_client()— on non-root components, call root methods without constructing a client manually:
