Skip to main content
You’ll build simple-token step-by-step alongside the instructor, then prove what you learned by extending my-token on your own.
How it works. Each chapter has two tracks running in parallel: a Guided track (copy-paste into simple-token) and an Exercise track (build the same thing yourself in my-token). Tests verify your implementation; reveal solutions inline whenever you get stuck.

The two tracks

Guided track — simple-token

Copy-paste alongside the chapter. A stripped-down version of the GEN token standard. You’ll see every line before you write it.

Exercise track — my-token

Implement the same functionality yourself. Starter and solution tags in git; tests in my-token-tests tell you when you’re right.

The chapters

#ChapterWhat it teaches
Environment SetupNative macOS toolchain (recommended) or devcontainer fallback.
Project SetupClone my-token, init simple-token via gen genie init-contract.
1First Build, First TestAnatomy of a contract — root component, #[deploy], #[installation_request], ABI-driven typed clients, push + deploy + install.
2Adding FunctionalityThe simple-token holder — transfer + receive, storage, and the access modifiers (#[owner], #[private], #[public], #[view]) that make inter-component calls safe.
3Everything but the Kitchen Sync.local (sync, same entity) vs .remote (async, anywhere). The lever for choosing where parallelism lives.
4To Panic is HumanWhy panic! doesn’t save Alice — atomicity is local, not global. Result::Err + compensation patterns. await vs spawn.
5aBorrowed Code · Make It CompileContract refs, instances, the dependency plumbing. Install simple-token’s holder into your components until it compiles.
5bBorrowed Code · Make It RunDefault routing, the cross-contract transfer() call, and the test (plus the ContractRefError fix).
6Final ExercisesSafe Swap (peer-to-peer with cancel + safety) and a Central Limit Order Book. Build from scratch with everything you learned.

The shape of a GEN contract

pub mod root {                              // every contract has a root
    pub struct XTokenRoot;
    impl XTokenRoot {
        #[deploy]                           // called at deploy
        pub async fn deploy() -> Result<Self> { Ok(Self) }

        #[installation_request]             // called when components ask to install
        pub async fn installation_request(&mut self, _t: XTokenComponents) -> Result<()> { Ok(()) }
    }
}

pub mod holder {                            // non-root components
    pub struct SimpleTokenHolder { pub wallet_balance: AmountInSubunits }

    impl SimpleTokenHolder {
        #[owner]                            // only the entity's owner
        pub async fn transfer(&mut self, amount: AmountInSubunits, recipient: Holder) -> Result<()> { /* ... */ }

        #[private]                          // only same-contract components
        pub fn receive(&mut self, amount: AmountInSubunits) -> Result<()> { /* ... */ }

        #[view]                             // anyone, read-only, free
        pub fn get_balance(&self) -> Result<AmountInSubunits> { Ok(self.wallet_balance) }

        #[install]
        pub async fn install(_iv: ()) -> Result<Self> { Ok(Self { wallet_balance: AmountInSubunits::new(0) }) }
    }
}
That’s the whole vocabulary you’ll use across all six chapters. The rest is business logic.

The four access modifiers

ModifierWho can call
#[owner]Only the entity where the component is installed.
#[private]Any component in the same contract.
#[public]Anyone.
#[view]Anyone, read-only, free.
These four are the permission rails. Get them right and most safety properties fall out of the type system.

Sync vs async, in one rule

  • .local — sync; only works between components on the same entity; no entity-lock yield.
  • .remote — async; works regardless of entity; yields the lock, produces a deferred result.
You pick locality at design time. Co-locate for atomic reads; distribute for throughput.

What you’ll need

  • An Apple Silicon Mac (recommended) or any machine with Docker for the devcontainer path.
  • Familiarity with Rust at a reading level — async, traits, Result.
  • A GitHub account for cloning the workshop repo.

Claim to fame

Mainstream payment processors handle around 30,000 transactions per second at peak. Most L1s top out somewhere between a few thousand and ~100K TPS in the optimistic case, and far less once transactions actually conflict, because their execution model serializes contended writes. The Grid’s design point sits well above that, and the reason is architectural:
PropertyNumberWhy it matters
Throughput1,000,000 activations/sec (design target)Parallelism is per-entity, not per-block. Conflicts are bounded to the entity scope you chose — throughput becomes a property of your contract design, not of the chain you happen to be on.
Block time~10 ms (~500 ms finality)A write is one round-trip in tens of milliseconds; reads don’t touch the activation pipeline at all.
CostLow, quoted in stablecoinNo “wait for gas to come down” UX. Per-call economics survive going from one transaction to ten million of them.
Two structural choices buy this:
  • No singletons. The same contract code can be instantiated many times — each instance gets its own state and execution. Throughput scales with deployments, not against them.
  • Compose, don’t replicate. Your swap or vesting contract installs the canonical token contract’s holder onto itself instead of reimplementing it. Your contract participates in deployed state; logic runs where the state lives.
You’ll exercise both choices through the chapters below.
Throughput and latency are design targets the architecture is engineered to hit sustainably, not guarantees about a specific deployment.

Start here

Environment Setup

Step 1 · Install the toolchain — native macOS binaries (recommended) or devcontainer.

Project Setup

Step 2 · Clone my-token and initialize simple-token from the CLI.