Skip to main content
genie is the Rust SDK for building component-based contracts on the Grid Virtual Machine (GVM). It provides the high-level abstractions and tools needed to create composable, role-based contracts that run natively on RISC-V. Genie enables the component-role architecture, where contracts are decomposed into logical roles (Root, Issuer, Holder, and so on) that can be installed independently and communicate through type-safe interfaces.
Genie is an early preview. The API surface will evolve.

Key architecture concepts

Components and roles

  • Components are installable units that implement contract interfaces.
  • Roles define specific capabilities within a contract (for example minting, holding balances).
  • Contracts specify role-based contracts using declarative interfaces.
  • Entities are independent units that own component instances.
  • Component communication happens synchronously when components are on the same entity, or asynchronously when they are on different entities.
For the conceptual model behind these terms, see Components and Entities and accounts.

Benefits

  • Composability. Mix and match components from different contracts.
  • Sharded performance. Each entity runs independently with async cross-entity calls.
  • Type safety. Generated clients ensure compile-time correctness.
  • Upgradability. Components can be versioned and upgraded independently.

Feature snapshot (v0.1)

AreaComponents
Contract definition#[contract], component-based modules
ImplementationDirect component implementation with automatic infrastructure generation
Installation#[installation_request], component-based setup with auto-generated installers
CommunicationGenerated clients with local/remote routers, automatic async method generation
StorageStorage::save(), Storage::load(), Storage::load_guarded(), type-safe persistence
RuntimeAsync::spawn(), async task management, cross-component coordination
Attributes#[installation_request], #[install], #[owner], #[private], #[view], #[public], #[entry]

Quick start

[dependencies]
genie = { git = "https://github.com/gen-bc/gen-framework-preview", tag = "v0.14.0" }

1. Define a contract

use genie::contract;

#[contract]
pub mod simple_counter_contract {
    /// Root component for counter metadata
    pub mod root {
        use genie::{Result, deploy, installation_request, view};

        /// Component struct with member fields for automatic storage management.
        /// Fields are loaded before method execution and saved after mutable methods.
        pub struct CounterRoot {
            count: u64,
        }

        impl CounterRoot {
            /// Validates installation requests for counter components.
            #[installation_request]
            pub async fn installation_request(
                &mut self,
                _component_type: SimpleCounterContractComponents,
            ) -> Result<()> {
                Ok(())
            }

            /// Deploy initializes the component and returns Self.
            /// Fields are automatically saved to storage after deployment.
            #[deploy]
            pub async fn deploy() -> Result<Self> {
                Ok(Self { count: 0 })
            }

            #[view]
            pub fn get_count(&self) -> Result<u64> {
                Ok(self.count)
            }
        }
    }

    /// Controller component for counter operations
    pub mod controller {
        use genie::{Result, install, owner};

        pub struct CounterController {
            count: u64,
        };

        impl CounterController {
            #[owner]
            pub fn increment(&mut self) -> Result<()> {
                // Access root component for state modification
                self.count += 1;
                Ok(())
            }

            /// Install receives the return value from installation_request.
            /// Initialize all struct fields and return Self.
            #[install]
            pub async fn install(
                _installation_return_value: (),
            ) -> Result<Self> {
                Ok(Self { count: 0 })
            }
        }
    }
}

2. Use the component

use genie::{Component, Result};

// Use generated client for type-safe calls
let client = SimpleCounterContractClient::new(component_id);

// Local calls (same entity), synchronous
client.controller.local.increment()?;
let count = client.root.local.get_count()?;

// Remote calls (cross-entity), asynchronous
client.controller.remote.increment().await?;
let count = client.root.remote.get_count().await?;

// Router-scoped call configuration.
// Use `.with_delegated_owner(holder_id)` when the routed-to component should
// receive delegated Owner authority scoped to `holder_id` for just this call.
// `holder_id` is the permission source and scope target; the router's
// destination component is the grantee that gets to act on that holder's behalf.
client.controller
    .remote
    .with_delegated_owner(holder_id)
    .increment()
    .await?;

// Install components using generated installer
let installer = SimpleCounterContractClient::installer();
let controller_id = installer.controller.install(
    root_id,
    entity_id,
    owner_component_id
).await?;

Macro reference

Genie uses procedural macros to define component boundaries, roles, and communication patterns.

Contract definition

MacroPurpose
#[contract]Defines a contract module containing multiple components
mod XDeclares a component module within a contract

Component lifecycle

MacroPurposeExample
#[deploy]Root deployment method called when deploying the root component (must be async)#[deploy] pub async fn deploy() -> Result<()>
#[install]Internal component method called during component installation (must be async)#[install] pub async fn install() -> Result<()>

Access control

MacroPurposeExample
#[entry]Public entry point accessible from external calls, generates async version#[entry] fn transfer() -> Result<()>
#[owner]Restricts access to component owner, generates async version#[owner] fn mint() -> Result<()>
#[public]Public entry point callable by any caller, generates async version#[public] fn get_info() -> Result<Info>
#[private]Internal contract calls only, generates async version#[private] fn internal_update() -> Result<()>

Read-only methods

MacroPurposeExample
#[view]Read-only method (no state changes), adds #[public] + #[read_only], generates async version#[view] fn get_balance() -> Result<u64>
#[read_only]Marks method as read-only (no storage mutations)#[read_only] fn calculate() -> Result<u64>

Attribute usage patterns

Permission-based access control

// Entry point accessible from external calls
#[entry]
fn public_function() -> Result<()> { ... }

// Owner access (component owner restriction)
#[owner]
fn mint_tokens(amount: u64) -> Result<()> { ... }

// Public access (any caller allowed)
#[public]
fn get_public_info() -> Result<String> { ... }

Read-only versus mutable methods

// View methods: public read-only, auto-generates async version
#[view]
fn get_balance() -> Result<u64> {
    Storage::load::<u64>()
}

// Read-only calculation (no storage access needed)
#[read_only]
fn calculate_fee(amount: u64) -> Result<u64> {
    Ok(amount * 3 / 1000) // 0.3% fee
}

// Public access for any caller
#[public]
fn update_metadata(new_name: String) -> Result<()> {
    Storage::save(new_name)?;
    Ok(())
}

Internal contract methods

// Private methods for inter-component communication
#[private]
fn receive_tokens(amount: u64, from: GenComponentId) -> Result<()> {
    let mut balance = Storage::load_guarded::<u64>()?;
    *balance += amount;
    Ok(())
}

// Root deployment (root module only)
#[deploy]
async fn deploy(metadata: TokenMetadata) -> Result<()> {
    Storage::save(metadata)?;
    Ok(())
}

// Component installation (non-root modules)
#[install]
async fn setup(config: ComponentConfig) -> Result<()> {
    Storage::save(config)?;
    Storage::save(0u64)?; // Initial balance
    Ok(())
}

Storage API

FunctionPurpose
Storage::save<T>(value)Persist value using type-derived key
Storage::save_by_key<K, T>(key, value)Persist with explicit key
Storage::load<T>()Load owned copy of stored value
Storage::load_by_key<K, T>(key)Load with explicit key
Storage::load_zero_copy<T>()Load by shared reference (Rc) without cloning payload
Storage::load_zero_copy_by_key<K, T>(key)Zero-copy load with explicit key
Storage::load_guarded<T>()Mutable reference that auto-saves on drop
Storage::load_guarded_by_key<K, T>(key)Keyed variant with auto-save
Storage::delete<T>()Remove value by type
Storage::delete_by_key<K>(key)Remove by explicit key
Storage::exists<T>()Check existence by type
Storage::exists_by_key<K>(key)Check existence by key
Storage::save_self<T>(object)Persist entire component struct (used by generated code)
Storage::load_self<T>()Load entire component struct (used by generated code)

Member storage (automatic field persistence)

Components can define struct fields that are automatically persisted to storage. When a component struct has fields:
  1. On load. The entire struct is loaded from storage using Storage::load_self<T>().
  2. On save. After mutable method execution, the entire struct is saved using Storage::save_self<T>(object).
This whole-struct storage approach is more efficient than field-by-field storage, reducing the number of storage operations and ensuring atomic state updates.
pub struct FungibleTokenHolder {
    /// Tracks the holder's token balance, automatically persisted.
    token_store: TokenStore,
    /// Stores metadata received during installation, automatically persisted.
    holder_metadata: HolderMetaData,
}

impl FungibleTokenHolder {
    /// Install returns Self with initialized fields.
    /// The framework automatically saves all fields after installation.
    #[install]
    pub async fn install(metadata: Metadata) -> Result<Self> {
        Ok(Self {
            token_store: TokenStore::new(),
            holder_metadata: HolderMetaData::new(metadata),
        })
    }

    /// Methods can access fields directly via &self or &mut self.
    /// Mutable methods (&mut self) trigger automatic field saving on completion.
    #[view]
    pub fn balance(&self) -> Result<AmountInSubunits> {
        Ok(self.token_store.balance())
    }

    #[private]
    pub fn receive(&mut self, amount: AmountInSubunits) -> Result<()> {
        self.token_store.credit(amount)
        // Fields are automatically saved after this method returns
    }
}
Member storage uses a reserved key (_genie_reserved_key_self) to store the entire component struct atomically. This whole-struct approach ensures atomic state updates and avoids collisions with user-defined storage keys.

Runtime and communication API

FunctionPurpose
Async::spawn(future)Execute async task on runtime
Async::spawn_to_sequence(future, sequence_id)Execute async task in a specific sequence
Async::join_sequence(future, sequence_id)Join a future to an existing sequence
Component::get_caller()Get current call context information
Component::is_same_entity(component_id)Check if component is on same entity
Component::get_owner_component()Get the owner component of current context
Component::create_new_entity()Create a new entity on the blockchain
Component::change_owner(component_id, new_owner)Change component ownership
Component::delete_component(component_id)Delete a component from the system

Component communication patterns

Local versus remote calls

let client = ContractClient::new(target_component);

// Local calls (same entity), synchronous
client.holder.local.transfer_same_entity(amount, recipient)?;

// Remote calls (cross-entity), asynchronous with confirmation
client.holder.remote.transfer_with_confirmation(amount, recipient).await?;

// Fire-and-forget remote calls
let future = client.holder.remote.transfer(amount, recipient);
Async::spawn(future);

Installation flow

// 1. Deploy root component first
let root_client = ContractClient::new(root_component_id);

// 2. Install user components using the generated installer pattern
let installer = ContractClient::installer();

let issuer_component = installer.issuer.install(
    root_id,
    installed_on_entity,
    owner
).await?;

let holder_component = installer.holder.install(
    root_id,
    installed_on_entity,
    owner
).await?;

Error handling

// Components should use structured error types
#[derive(Error, Debug)]
pub enum TokenError {
    #[error("Insufficient balance: {balance}, required: {required}")]
    InsufficientBalance { balance: u64, required: u64 },
    #[error("Transfer to self not allowed")]
    SelfTransfer,
}

// Use Result<T> returns for fallible operations
fn transfer(amount: u64, to: GenComponentId) -> Result<()> {
    let balance = Storage::load::<u64>()?;
    if balance < amount {
        return Err(TokenError::InsufficientBalance {
            balance,
            required: amount
        }.into());
    }
    // ... transfer logic
    Ok(())
}

Generated infrastructure

The #[contract] macro automatically generates:
  1. Client with local/remote routers:
    let client = ContractClient::new(component_id);
    
    // Local calls (synchronous)
    client.holder.local.transfer_same_entity(100, recipient)?;
    
    // Remote calls (asynchronous with confirmation)
    client.holder.remote.transfer_with_confirmation(100, recipient).await?;
    
  2. Component installers:
    let installer = ContractClient::installer();
    let holder_id = installer.holder.install(root_id, entity_id, owner_id).await?;
    
  3. Contract traits for external implementations:
    impl HolderComponentContract for MyCustomHolder {
        fn balance() -> Result<u128> { /* custom implementation */ }
        // ... other required methods
    }
    
  4. Component traits for dynamic component creation:
    let holder: Box<dyn ContractHolderComponent> = get_holder_component(component_id);
    

See also