# Dojo Documentation
> Dojo | A Toolchain for Building Provable Games and Applications
## Brand Assets
Download official logos and assets for Dojo and its ecosystem.
Right-click any image and select "Save Image As" to download.
### Core Dojo Brand
The Dojo logo represents the complete framework and toolchain for building provable games and applications.
For an overview of how the tools work together, see the [Dojo Overview](/overview).
Full Logo
Icon + Wordmark
{" "}
Icon Only
Square format
Wordmark
Text only
### Toolchain
The Dojo toolchain consists of specialized tools for building, testing, and deploying provable applications.
#### Katana
Katana is a blazing-fast sequencer for local development and testing.
[Read more about Katana](/toolchain/katana)
#### Torii
Torii automatically generates GraphQL and gRPC APIs for real-time state updates.
[Read more about Torii](/toolchain/torii)
#### Sozo
Sozo is the command-line interface for managing Dojo projects and deployments.
[Read more about Sozo](/toolchain/sozo)
#### Saya
Saya provides proving infrastructure for Dojo applications.
[Read more about Saya](/toolchain/saya)
#### Slot
Slot is a managed-service execution layer for deploying and scaling provable applications.
[Read more about Slot](https://github.com/cartridge-gg/slot)
### Ecosystem Libraries
#### Origami
Origami provides a collection of Cairo utilities and primitives for building Dojo applications.
[Read more about Origami](/libraries/origami)
#### Alexandria
Alexandria is a comprehensive library collection for Cairo development.
[Read more about Alexandria](/libraries/alexandria)
### SDK Platform Icons
Dojo provides client SDKs for multiple platforms and game engines.
JavaScript
{" "}
Rust
{" "}
C/C++
{" "}
Unity
{" "}
Godot
{" "}
Bevy
{" "}
Telegram
Discord
[Explore all SDKs](/client/sdk)
### Brand Guidelines
#### Colors
The Dojo brand uses a distinctive color palette:
* **Primary Accent**: `#ee2d3f` (Red)
* **Background**: `#0c0c0c` (Black)
* **Dark Background**: `#121212` (Dark Gray)
#### Usage
When using Dojo brand assets:
* **Do** maintain clear space around logos equal to the height of the icon
* **Do** use appropriate contrast for backgrounds
* **Do** maintain original aspect ratios
* **Don't** modify colors or proportions
* **Don't** add effects, shadows, or distortions
* **Don't** combine with other brand elements
#### Typography
The Dojo documentation uses **Open Sans** as the primary font family.
### Partner Logos
{" "}
{" "}
### Contact
For brand partnership inquiries or additional asset requests, please reach out through our [Discord community](https://discord.gg/dojoengine).
## FAQ
### Provable Games
#### What is an onchain game?
Onchain games are games that exist entirely on a public blockchain network; all states and logic are onchain.
Clients (like web browsers) do not exist on the chain but exist purely to interact with and interpret the onchain state.
#### What is a provable game?
Thanks to the magic of zero-knowledge proofs, we can ensure a game is fair by verifying a zk proof created off-chain.
But what does that entail?
Consider a game of chess.
We aim for an experience where players trust each other's moves.
In a straightforward approach — and given the simple rules of chess — if this were in a blockchain environment, every move would be a transaction on the blockchain.
This is costly.
We just want to know the winner, not every move.
With zk proofs and client communications, players can establish a state channel, sharing moves off-chain and ensuring their validity.
At the end, a zk proof can be submitted to the blockchain to confirm the game's fairness.
This constitutes a provable game.
#### What is an autonomous world?
An autonomous world is one that exists entirely onchain.
It's not controlled by any single entity but is instead governed by the rules set within that world.
[Dive deeper into the topic here](/theory/autonomous-worlds).
### Dojo Development
#### Why Dojo?
Dojo was created to solve problems the founders faced when building onchain games in Cairo.
It standardizes the process of building such games and provides a suite of tools to make it easier.
#### What is Cairo?
Cairo is an open-source programming language invented by StarkWare.
It's a Turing-complete low-level language designed to compile to the Cairo Virtual Machine.
Learn more about it here: [Cairo](https://www.cairo-lang.org/).
#### Can I deploy Dojo to Starknet?
Yes!
Dojo can run on any StarknetVM including the public blockchains.
Within the Dojo toolchain exists [Katana](/toolchain/katana) which is a gaming specific sequencer, which is perfectly suited to Dojo games.
#### Can Dojo do client side proofs?
The ability to execute Dojo programs in the browser is entirely plausible and is on our roadmap.
If you are a specalist in this, jump into the Github and help out!
#### Who maintains Dojo?
Dojo is an open-source initiative, licensed under Apache 2.0, dedicated to promoting and advancing the concept of Autonomous Worlds (AWs).
It is developement is led by [Cartridge](https://cartridge.gg/), with significant contributions from [Realms](https://realms.world/), [BibliothecaDAO](https://bibliothecadao.xyz/) and [many more](https://github.com/orgs/dojoengine/people).
#### Where is the Dojo roadmap?
Dojo is rapidly evolving.
You can find open issues on the [Dojo Github](https://github.com/dojoengine/dojo/issues) and join the [Discord](https://discord.gg/invite/dojoengine) to get involved.
If you have ideas for the project, please open an issue.
#### How do I get involved?
Check out our [Github](https://github.com/dojoengine/dojo/blob/main/CONTRIBUTING.md), [Twitter](https://x.com/ohayo_dojo), or [Discord](https://discord.gg/invite/dojoengine).
### Known Limitations
#### Starknet contract size limit
Starknet enforces a maximum size for contract classes.
If your Dojo contract grows too large (many systems or complex logic in a single contract), deployment will fail with a size limit error.
The mitigation is to split logic across multiple contracts or use [Dojo libraries](/libraries/origami) to separate reusable logic from your contracts via library calls.
#### Models inside models
A `#[dojo::model]` struct cannot be used as a field inside another model.
Use a plain struct deriving `Introspect` instead.
See [Model Composition](/framework/models#model-composition) for details.
import { LinkCard } from "../components/LinkCard";
## Installing Dojo
Dojo's installation process was designed to be idiomatically consistent with its dependencies, Rust and Cairo.
You can get up-and-running in just a few steps, and be building provable games in no time.
:::warning
Windows is not natively supported.
We recommend [WSL2](https://learn.microsoft.com/en-us/windows/wsl/install) for Windows development.
:::
### Quick Start
For existing Cairo developers, the fastest way to get started is with our `asdf` installer.
Simply run the following command in your terminal to install the Dojo toolchain.
```bash
curl -L https://install.dojoengine.org | bash
```
This script will install the `asdf` plugins for Sozo, Katana, and Torii, install their latest versions, and set them for global use.
Skip to [Verification](#verification) to test your installation.
:::note
This script requires `asdf` to be installed.
See [here](https://asdf-vm.com/guide/getting-started.html) for installation instructions.
:::
### Detailed Installation
#### System Requirements
Before installing Dojo, ensure your system meets these requirements:
* **Operating System**: macOS, Linux, or Windows with WSL2
* **Memory**: At least 4GB RAM (8GB recommended)
* **Storage**: 2GB free disk space for toolchains and dependencies
* **Network**: Stable internet connection for downloading binaries
* **Dependencies**: `curl`, `jq`, and `git` (usually pre-installed on most systems)
### Prerequisites
Before installing Dojo, you'll need these foundational tools:
##### 1. Install Rust with `rustup`
Rust is required for Dojo's infrastructure tools (Katana and Torii).
**Minimum version**: 1.85.0
```bash
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
```
This installs Rust's executables, including the [`cargo`](https://doc.rust-lang.org/cargo/) package manager, in the `~/.cargo` directory.
**Verify installation:**
```bash
cargo --version
# Should output: cargo 1.85.0 (or later)
```
For more information, see the [Rust installation guide](https://doc.rust-lang.org/book/ch01-01-installation.html).
##### 2. Install Cairo with `starkup`
Dojo is written in [Cairo](https://www.cairo-lang.org/), StarkWare's language for creating provable programs.
**Minimum version**: 2.10.1
```bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.starkup.sh | sh
```
This installs Cairo's toolchain, including the [`scarb`](https://docs.swmansion.com/scarb/) package manager and build tool, in the `~/.asdf` directory.
:::note
`asdf` does not automatically add itself to your PATH and must be configured manually.
See more information about configuring `asdf` [here](https://asdf-vm.com/guide/getting-started.html#_2-configure-asdf).
:::
**Verify installation:**
```bash
scarb --version
# Should output: scarb 2.10.1 (or later)
```
For more information, see the [starkup documentation](https://github.com/software-mansion/starkup).
### Installing Dojo
The Dojo framework consists of a Cairo framework and three Rust-based tools: `katana`, `torii`, and `sozo`.
:::note
The Cairo framework is managed by Scarb as a compile-time dependency.
See the [Dojo configuration](/framework/configuration) documentation for more info.
:::
#### Installing with `asdf`
[`asdf`](https://asdf-vm.com/) is a popular version manager, supporting programmatic management of dependencies.
`asdf` is the preferred way to install Dojo.
:::tip
You can use our `asdf` installer to install the toolchain in a single command:
```bash
curl -L https://install.dojoengine.org | bash
```
:::
:::steps
##### Install asdf
Follow the [asdf installation instructions](https://asdf-vm.com/guide/getting-started.html) for your platform.
##### Add the asdf plugins
```bash
asdf plugin add katana https://github.com/dojoengine/asdf-katana.git
asdf plugin add torii https://github.com/dojoengine/asdf-torii.git
asdf plugin add sozo https://github.com/dojoengine/asdf-sozo.git
```
##### Install the Dojo toolchain
```bash
# Install the latest version
asdf install {sozo,katana,torii} latest
# Install specific versions
asdf install {sozo,katana,torii} 1.6.0
# List available versions
asdf list all {sozo,katana,torii}
```
##### Set the version
```bash
# Set version locally (in project .tool-versions)
asdf set {sozo,katana,torii} latest
# Set version globally (in user home .tool-versions)
asdf set --home {sozo,katana,torii} latest
```
##### Using `.tool-versions`
`asdf` supports programmatic package management through the `.tool-versions` file.
```toml
# path/to/project/.tool-versions
scarb 2.12.2
sozo 1.7.1
katana 1.7.0
torii 1.8.3
starknet-foundry 0.43.1
```
Simply run `asdf install` in the project root to install all dependencies.
If needed, `asdf` will download the correct version of all dependencies.
:::
#### Installing with `dojoup`
[`dojoup`](https://install.dojoengine.org) is an alternative way to install and manage Dojo installations.
Dojoup is version-aware and will automatically install compatible versions of `katana`, `torii`, and `sozo`.
:::warning
Dojoup is deprecated and will be removed in the future.
Use the `asdf` installer instead.
:::
::::steps
##### Install `dojoup`
```bash
curl -L https://raw.githubusercontent.com/dojoengine/dojo/main/dojoup/dojoup-install | bash
```
This command installs `dojoup` in the `~/.dojo` directory and adds it to your shell profile.
:::info
You need to restart your terminal for the `dojoup` command to be available.
:::
##### Install the Dojo toolchain
In a new terminal window, run:
```bash
dojoup install
```
This installs `katana`, `torii`, and `sozo` in the `~/.dojo` directory.
##### Advanced Usage
```bash
# Install a specific version
dojoup install 1.6.0
# Get help
dojoup --help
```
::::
### Verification
After installation, verify that all tools are working correctly:
#### Check Dojo Tools
```bash
# Check Sozo (CLI tool)
sozo --version
# Should output: sozo 1.6.0 (or later)
# Check Katana (sequencer)
katana --version
# Should output: katana 1.6.0 (or later)
# Check Torii (indexer)
torii --version
# Should output: torii 1.6.0 (or later)
```
#### Test Your Installation
Create a simple test project to verify everything works:
```bash
# Clone the dojo-starter repository
git clone https://github.com/dojoengine/dojo-starter
# Navigate to the project
cd dojo-starter
# Build the project
sozo build
# Test successful if no errors appear
```
### Claude Code Skills
Dojo provides official [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) skills that give Claude relevant context for working with models, systems, testing, deployment, and more.
For an overview of available skills, see the [Dojo Overview](/overview#ai-assisted-development).
To install the skills, run the following in your terminal:
```bash
npx skills add dojoengine/book
```
### Troubleshooting
#### Common Installation Issues
##### `dojoup: command not found`
**Problem**: After installing dojoup, the command is not found.
**Solution**:
1. Restart your terminal completely
2. If still not working, manually source the environment: `source ~/.dojo/env`
3. Check that `~/.dojo/bin` is in your PATH: `echo $PATH | grep -o ~/.dojo`
##### `starkup` installation fails
**Problem**: `starkup` fails to install or configure properly.
**Solution**:
1. Ensure `asdf` is properly installed
2. Add `~/.asdf/shims` to your PATH: `export PATH="$HOME/.asdf/shims:$PATH"`
3. Or, add it via your shell profile (`.bashrc`, `.zshrc`, etc.)
##### Version compatibility issues
**Problem**: Incompatible versions between Rust, Cairo, or Dojo components.
**Solution**:
1. Check current versions:
```bash
cargo --version # Should be 1.85.0+
scarb --version # Should be 2.10.1+
sozo --version # Should be 1.6.0+
```
2. Update if needed:
```bash
rustup update # Update Rust
asdf install scarb latest # Update Cairo/Scarb
dojoup install # Update Dojo
```
##### Build failures in test project
**Problem**: `sozo build` fails in the test project.
**Solution**:
1. Ensure all dependencies are installed
2. Try cleaning and rebuilding: `sozo clean && sozo build`
3. Check for [version mismatches](https://github.com/dojoengine/dojo/blob/main/versions.json) between your installed tools
#### Getting Additional Help
If you continue to experience issues:
1. Check the [Dojo GitHub Issues](https://github.com/dojoengine/dojo/issues) for known problems
2. Join our [Discord](https://discord.gg/invite/dojoengine) for community support
3. Review the [Dojo Book](https://book.dojoengine.org/) for comprehensive documentation
> **Note**: Version numbers in this guide are current as of the latest stable release.
> For the most current version numbers, check the [Dojo releases page](https://github.com/dojoengine/dojo/releases).
### Code Editor Support
#### Visual Studio Code / Cursor
Install the [Cairo 1.0](https://marketplace.visualstudio.com/items?itemName=starkware.cairo1) extension for:
* Syntax highlighting
* Error detection
* Auto-completion
* Code formatting
### Getting Help
If you run into problems getting started with Dojo, join our [Discord](https://discord.gg/invite/dojoengine) and ask for help.
### Next Steps
With Dojo installed, you're ready to start building:
import { LinkCard } from "../components/LinkCard";
import Dojo from "../public/dojo-logo.svg?react";
Dojo is a cutting-edge Entity-Component-System (ECS) framework and toolchain for building provable
applications in Cairo, versatile enough to support everything from simple
NFT services to the most complex onchain games.
Dojo lets you skip the hassle of managing intricate contract patterns,
writing complex indexers, or setting up detailed query systems — letting you
focus on bringing your ideas to life quickly and efficiently.

### Dojo ECS Framework
Instead of writing raw contracts, Dojo enables a modern [Entity-Component-System](https://github.com/SanderMertens/ecs-faq) approach to building interactive applications.
Write like you would a database schema and have it reflected onchain and synced with your client.
[Read more about the framework](/framework)
### Katana High-Performance Sequencer
Katana is a high-performance sequencer you can spin up with a single command.
Blazing fast and easy to use, Katana gets you up and running quickly.
[Read more about Katana](/toolchain/katana)
### Torii Indexer for Real-Time State Updates
Torii is an indexer that automatically generates GraphQL and gRPC APIs for real-time state updates.
It is designed to make it easy to query and subscribe to onchain state changes.
[Read more about Torii](/toolchain/torii)
### Sozo CLI for Smart Contract Management
Sozo is a CLI tool for planning and managing smart contract deployments, upgrades, and maintenance.
It is designed to make it easy to manage your onchain application's lifecycle.
[Read more about Sozo](/toolchain/sozo)
### Multiplatform Client SDKs
Dojo provides client SDKs for interacting with your onchain state from platforms like Telegram and Discord, as well as game engines like Godot, Unity, and Unreal.
These libraries make it easy to connect your onchain state to your favorite frontend.
[Read more about the SDKs](/client/sdk)
### Slot Deployment Infrastructure
Cartridge offers Slot, a managed-service execution layer that lets you easily deploy and scale your provable applications.
[Read more about Slot](https://github.com/cartridge-gg/slot)
### Autonomous Worlds
Dojo is purpose-built for Autonomous Worlds — persistent, permissionless onchain environments governed entirely by their own rules.
Every model, system, and event lives on the blockchain, enabling players and builders to interact with a world that no single party controls.
[Learn more about Autonomous Worlds](/theory/autonomous-worlds)
### AI-Assisted Development
Dojo provides AI skills for use with AI coding assistants.
Install all Dojo skills:
```bash
npx skills add dojoengine/book
```
Available skills: `dojo-model`, `dojo-system`, `dojo-test`, `dojo-client`, `dojo-config`, `dojo-deploy`, `dojo-init`, `dojo-migrate`, `dojo-token`, `dojo-world`, `dojo-indexer`, `dojo-review`
[Browse all skills](https://github.com/dojoengine/book/tree/main/skills)
For detailed installation instructions, see the [Installation Guide](/installation#claude-code-skills).

## Building Games on Starknet: The Complete Stack
::authors
Building a game on Starknet isn't just about writing Cairo. You need a full stack: an execution environment, state management, client synchronization, and player onboarding. Here's how the pieces fit together.
### The Challenge
Traditional game development has decades of tooling. Engines like Unity and Unreal handle rendering, physics, networking, and asset management out of the box.
Onchain games are different. The blockchain is your backend, but you still need:
* A way to model game state
* Fast, cheap execution for player actions
* Clients that stay in sync with onchain state
* Wallets that don't interrupt gameplay
Dojo solves this by providing a complete framework purpose-built for onchain games.
### Layer 1: The Framework
Dojo brings familiar game development patterns to Cairo:
**Entity Component System (ECS)**
Games think in entities (players, items, tiles) and components (position, health, inventory). Dojo's ECS maps directly to onchain storage while generating typed clients automatically.
```cairo
#[derive(Component)]
struct Position {
x: u32,
y: u32,
}
#[derive(Component)]
struct Health {
current: u32,
max: u32,
}
```
**World Contract**
Every Dojo game deploys a World contract that manages all state. Systems (your game logic) read and write through the World, keeping everything consistent.
**Typed Clients**
Dojo generates TypeScript and Unity clients from your contracts. No manual ABI parsing. State updates flow automatically from chain to client.
### Layer 2: Execution
Games can't run on Starknet mainnet directly — transaction costs and latency would kill the experience. Instead, games run on dedicated execution layers.
[Cartridge Slot](https://cartridge.gg) provides application-specific rollups for Dojo games:
* Sub-second transaction finality
* Negligible costs per action
* Full Starknet compatibility
* Proofs that settle back to L1
This means your game logic executes fast and cheap while inheriting Starknet's security guarantees.
### Layer 3: Indexing
Onchain state is authoritative but slow to query. Torii, Dojo's indexer, maintains a synchronized database of your World state.
Your client queries Torii for fast reads while writes go directly to the chain. When state changes onchain, Torii updates and pushes to connected clients via GraphQL subscriptions.
```typescript
// Subscribe to position changes
const positions = await client.getEntities({
model: "Position",
where: { player_id: playerId }
});
```
### Layer 4: Player Identity
The final piece is onboarding. Players shouldn't need to understand gas, transactions, or seed phrases.
[Cartridge Controller](https://cartridge.gg) handles this:
* Email/social login that creates a Starknet account
* Session keys for seamless gameplay (no popups per action)
* Account abstraction for gas sponsorship
* Cross-game identity and inventory
From the player's perspective, they're just logging into a game. The blockchain complexity is invisible.
### Putting It Together
Here's the full stack for a Dojo game:
```
┌─────────────────────────────────────────┐
│ Game Client (React/Unity) │
├─────────────────────────────────────────┤
│ Cartridge Controller (Identity + Wallet)│
├─────────────────────────────────────────┤
│ Torii (Indexer + Subscriptions) │
├─────────────────────────────────────────┤
│ Cartridge Slot (Execution Layer) │
├─────────────────────────────────────────┤
│ Dojo World Contract (Game Logic) │
├─────────────────────────────────────────┤
│ Starknet (Settlement) │
└─────────────────────────────────────────┘
```
Each layer handles a specific concern. Developers focus on game design while the infrastructure handles the hard parts.
### Getting Started
The fastest path to a running game:
1. **Install Dojo**: `curl -L https://install.dojoengine.org | bash`
2. **Scaffold a project**: `sozo init my-game`
3. **Deploy locally**: `katana` for a local devnet, then `sozo build && sozo migrate`
4. **Generate client**: Dojo outputs TypeScript bindings automatically
From zero to playable prototype in an afternoon.
### What's Next
The stack keeps improving. Faster provers mean quicker settlement. Better indexers mean richer queries. More client SDKs mean more platforms.
Games shipping today on Dojo — like [Eternum](https://realms.world), [Loot Survivor](https://lootsurvivor.io), and others in the [Cartridge ecosystem](https://cartridge.gg) — are stress-testing this infrastructure and pushing it forward.
The goal: make building onchain games as straightforward as building traditional ones, while preserving everything that makes them special.
***
*Ready to build? Check out the [Dojo documentation](https://book.dojoengine.org), join the [Discord](https://discord.gg/dojoengine), or explore games on [Cartridge](https://cartridge.gg).*
## Building World Model Agents for Onchain Games
Most game agents react. They see a state, they pick an action. It works—until it doesn't. When the game gets complex, when opponents adapt, when you need to think three moves ahead, reactive agents hit a wall.
World model agents think differently. They build an internal model of how the game works, then *imagine* what happens if they take different actions. They simulate futures before committing to one. This is closer to how humans actually play games—we don't just react to the board, we picture what happens next.
And here's the twist: onchain games are uniquely suited for this approach. The state is fully observable, transitions are deterministic, and historical data is permanently indexed. You don't have to guess how the game engine works—you can learn it from data that can't lie.
This article is technical. We'll cover the theory, the architecture, and actual implementation patterns. By the end, you'll understand how to build agents that don't just play games—they understand them.
### What World Models Actually Are
Let's be precise about terminology. A world model is a learned function that predicts future states given current state and actions.
More formally:
```
s_{t+1} = f(s_t, a_t)
```
Where `s_t` is the state at time t, `a_t` is the action taken, and `f` is your world model. Simple in principle, complicated in practice.
#### The Classic Frame: Ha & Schmidhuber's Architecture
The 2018 "World Models" paper by Ha and Schmidhuber laid out an architecture that's become the standard reference point. It has three components:
1. **Vision Model (V)**: Compresses raw observations into a compact latent representation
2. **Memory Model (M)**: An RNN (they used MDN-RNN) that predicts future latent states
3. **Controller (C)**: A small neural network that selects actions given the latent state
The key insight was training inside the "dream"—the controller can be trained entirely within the world model's imagination, without touching the real environment. This is massively more sample-efficient than reinforcement learning directly on the environment.
#### Why Latent Spaces Matter
You might wonder: why not just predict raw states directly? For visual games, that means predicting pixels. This works but it's wasteful. Most pixels in a game frame are irrelevant noise—background textures, UI elements, decorative particles.
A latent space compresses the observation to what matters strategically. In the learned representation, a "dangerous enemy nearby" and "safe path ahead" might be just a few dimensions, not thousands of pixels.
For onchain games, raw observations aren't pixels—they're structured data from Torii. But the principle still applies. A game might have hundreds of entity properties, but the strategically relevant state might be much smaller. Your world model learns that compression automatically.
```python
# Raw game state from Torii: hundreds of fields
raw_state = {
"player": {"x": 45, "y": 120, "health": 80, "mana": 35, ...},
"enemies": [...], # Could be dozens
"items": [...],
"terrain": [...],
"events": [...],
# ... many more
}
# Latent representation: 64-dimensional vector
# Captures: "medium health, near enemy cluster, low resources"
latent = encoder(raw_state) # shape: (64,)
```
The encoder learns which aspects of `raw_state` actually affect what happens next. Everything else gets discarded.
#### Determinism Changes Everything
Here's where onchain games diverge from the scenarios most world model research tackles. Traditional game AI deals with:
* Partially observable states (fog of war, hidden information)
* Stochastic transitions (random enemy spawns, dice rolls)
* Complex physics that are hard to model exactly
Onchain games, by design, have:
* **Fully observable state**: Everything is on-chain. You can query every player's position, every item's location, every variable in every contract.
* **Deterministic transitions**: Given the same state and action, you get the same next state. Always. (Even "random" events use verifiable randomness with known seeds.)
* **Perfect historical record**: Torii indexes every state change. You have complete ground truth for training.
This means your world model can be *exact* for the game mechanics. Not a statistical approximation—an exact function. The only uncertainty is what other players will do.
```python
# In a stochastic environment (traditional games):
# Model must learn P(s_{t+1} | s_t, a_t) - a probability distribution
# In a deterministic onchain game:
# Model learns s_{t+1} = f(s_t, a_t) - a direct mapping
# Uncertainty only in opponent actions
```
This changes the architecture. You don't need probabilistic world models with variance estimation. You need accurate state prediction, plus opponent modeling as a separate concern.
### The Architecture: Perception → World Model → Planning → Action
Let's break down the complete pipeline for a world model agent on Dojo.
#### 1. Perception: Torii → Structured State
Your agent starts by querying Torii. Here's a practical Python setup:
```python
import httpx
from dataclasses import dataclass
from typing import List, Optional
import json
TORII_URL = "https://api.cartridge.gg/x/your-game/torii/graphql"
@dataclass
class PlayerState:
address: str
position_x: int
position_y: int
health: int
resources: dict
inventory: List[str]
@dataclass
class GameState:
tick: int
players: List[PlayerState]
world_entities: List[dict]
async def fetch_game_state(client: httpx.AsyncClient) -> GameState:
"""Fetch complete game state from Torii."""
query = """
query GetWorld {
playerModels(first: 100) {
edges {
node {
address
position_x
position_y
health
resources
inventory
}
}
}
worldModels(first: 1) {
edges {
node {
current_tick
}
}
}
entityModels(first: 500) {
edges {
node {
entity_id
entity_type
position_x
position_y
properties
}
}
}
}
"""
response = await client.post(
TORII_URL,
json={"query": query}
)
data = response.json()["data"]
players = [
PlayerState(
address=edge["node"]["address"],
position_x=edge["node"]["position_x"],
position_y=edge["node"]["position_y"],
health=edge["node"]["health"],
resources=json.loads(edge["node"]["resources"]),
inventory=json.loads(edge["node"]["inventory"])
)
for edge in data["playerModels"]["edges"]
]
tick = data["worldModels"]["edges"][0]["node"]["current_tick"]
entities = [edge["node"] for edge in data["entityModels"]["edges"]]
return GameState(tick=tick, players=players, world_entities=entities)
```
Notice we're pulling *everything*—not just our own player. World models need to understand the complete game state to predict what happens when we act.
#### 2. State Encoding: Structured Data → Latent Vector
The world model operates on fixed-size vectors, not variable-length GraphQL responses. We need an encoder:
```python
import torch
import torch.nn as nn
from typing import Tuple
class GameStateEncoder(nn.Module):
"""Encode variable-size game state into fixed latent vector."""
def __init__(
self,
player_dim: int = 32,
entity_dim: int = 16,
latent_dim: int = 128,
max_players: int = 50,
max_entities: int = 200
):
super().__init__()
self.max_players = max_players
self.max_entities = max_entities
# Player encoder
self.player_encoder = nn.Sequential(
nn.Linear(10, 64), # position, health, resources
nn.ReLU(),
nn.Linear(64, player_dim)
)
# Entity encoder
self.entity_encoder = nn.Sequential(
nn.Linear(8, 32),
nn.ReLU(),
nn.Linear(32, entity_dim)
)
# Attention pooling for variable-length sequences
self.player_attention = nn.MultiheadAttention(
embed_dim=player_dim,
num_heads=4,
batch_first=True
)
self.entity_attention = nn.MultiheadAttention(
embed_dim=entity_dim,
num_heads=4,
batch_first=True
)
# Final projection
self.project = nn.Sequential(
nn.Linear(player_dim + entity_dim, 256),
nn.ReLU(),
nn.Linear(256, latent_dim)
)
def forward(
self,
player_features: torch.Tensor, # (batch, n_players, 10)
entity_features: torch.Tensor, # (batch, n_entities, 8)
player_mask: torch.Tensor,
entity_mask: torch.Tensor
) -> torch.Tensor:
# Encode each player/entity
players_encoded = self.player_encoder(player_features)
entities_encoded = self.entity_encoder(entity_features)
# Attention pooling with masks
players_pooled, _ = self.player_attention(
players_encoded, players_encoded, players_encoded,
key_padding_mask=~player_mask
)
entities_pooled, _ = self.entity_attention(
entities_encoded, entities_encoded, entities_encoded,
key_padding_mask=~entity_mask
)
# Mean pool over sequence
player_repr = (players_pooled * player_mask.unsqueeze(-1)).sum(1)
player_repr = player_repr / player_mask.sum(1, keepdim=True).clamp(min=1)
entity_repr = (entities_pooled * entity_mask.unsqueeze(-1)).sum(1)
entity_repr = entity_repr / entity_mask.sum(1, keepdim=True).clamp(min=1)
# Combine and project
combined = torch.cat([player_repr, entity_repr], dim=-1)
return self.project(combined)
def preprocess_game_state(state: GameState, my_address: str) -> Tuple[torch.Tensor, torch.Tensor]:
"""Convert GameState to tensors for the encoder."""
# Player features: relative position to me, health, resources
my_player = next(p for p in state.players if p.address == my_address)
player_features = []
for p in state.players:
features = [
p.position_x - my_player.position_x, # Relative x
p.position_y - my_player.position_y, # Relative y
p.health / 100.0, # Normalized health
p.resources.get("wood", 0) / 1000.0,
p.resources.get("stone", 0) / 1000.0,
p.resources.get("gold", 0) / 1000.0,
1.0 if p.address == my_address else 0.0, # Is self
len(p.inventory) / 10.0, # Inventory fullness
# ... more features
]
player_features.append(features)
# Pad to fixed size
while len(player_features) < 50:
player_features.append([0.0] * 10)
# Entity features: type, position, properties
entity_features = []
for e in state.world_entities:
features = [
e["position_x"] - my_player.position_x,
e["position_y"] - my_player.position_y,
hash(e["entity_type"]) % 100 / 100.0, # Type embedding
# ... extract relevant properties
]
entity_features.append(features)
while len(entity_features) < 200:
entity_features.append([0.0] * 8)
return (
torch.tensor(player_features[:50], dtype=torch.float32),
torch.tensor(entity_features[:200], dtype=torch.float32)
)
```
This encoder handles variable numbers of players and entities through attention pooling—a technique borrowed from transformer architectures. It's more sophisticated than simple concatenation but handles the variability gracefully.
#### 3. The World Model: Predicting Future States
Now the core: a model that predicts what happens when actions are taken.
```python
class WorldModel(nn.Module):
"""
Deterministic world model for onchain games.
Predicts next latent state given current state and action.
"""
def __init__(
self,
latent_dim: int = 128,
action_dim: int = 16, # One-hot or embedded action
hidden_dim: int = 256
):
super().__init__()
# Action embedding
self.action_embed = nn.Embedding(20, action_dim) # 20 possible actions
# Transition model
self.transition = nn.Sequential(
nn.Linear(latent_dim + action_dim, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, hidden_dim),
nn.LayerNorm(hidden_dim),
nn.ReLU(),
nn.Linear(hidden_dim, latent_dim)
)
# Reward predictor (auxiliary task)
self.reward_head = nn.Sequential(
nn.Linear(latent_dim, 64),
nn.ReLU(),
nn.Linear(64, 1)
)
# Done predictor (game over / death)
self.done_head = nn.Sequential(
nn.Linear(latent_dim, 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.Sigmoid()
)
def forward(
self,
state: torch.Tensor, # (batch, latent_dim)
action: torch.Tensor # (batch,) action indices
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""Predict next state, reward, and done probability."""
action_emb = self.action_embed(action)
combined = torch.cat([state, action_emb], dim=-1)
next_state = self.transition(combined)
reward = self.reward_head(next_state)
done = self.done_head(next_state)
return next_state, reward.squeeze(-1), done.squeeze(-1)
def rollout(
self,
initial_state: torch.Tensor,
action_sequence: torch.Tensor, # (batch, horizon)
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
"""
Simulate a sequence of actions.
Returns trajectory of states, rewards, and done flags.
"""
batch_size, horizon = action_sequence.shape
states = [initial_state]
rewards = []
dones = []
current_state = initial_state
for t in range(horizon):
next_state, reward, done = self(current_state, action_sequence[:, t])
states.append(next_state)
rewards.append(reward)
dones.append(done)
current_state = next_state
return (
torch.stack(states, dim=1), # (batch, horizon+1, latent_dim)
torch.stack(rewards, dim=1), # (batch, horizon)
torch.stack(dones, dim=1) # (batch, horizon)
)
```
The `rollout` method is where the magic happens. Given an initial state and a sequence of planned actions, the model imagines the entire trajectory. This lets us evaluate plans before executing them.
#### 4. Training: Learning from Torii History
Here's the beautiful part of onchain games: you have perfect training data. Torii stores every state transition that ever happened.
```python
async def fetch_historical_transitions(
client: httpx.AsyncClient,
limit: int = 10000
) -> List[dict]:
"""
Fetch historical state transitions from Torii.
Each transition is a (state, action, next_state, reward) tuple.
"""
query = """
query GetHistory($limit: Int!) {
eventMessagesHistorical(
first: $limit,
orderBy: { field: TIMESTAMP, direction: DESC }
) {
edges {
node {
keys
data
transactionHash
createdAt
}
}
}
}
"""
response = await client.post(
TORII_URL,
json={"query": query, "variables": {"limit": limit}}
)
events = response.json()["data"]["eventMessagesHistorical"]["edges"]
# Parse events into transitions
transitions = []
for event in events:
parsed = parse_game_event(event["node"])
if parsed:
transitions.append(parsed)
return transitions
def parse_game_event(event: dict) -> Optional[dict]:
"""
Parse raw event into a structured transition.
This is game-specific—adapt to your game's events.
"""
keys = event["keys"]
data = event["data"]
# Example: "PlayerMoved" event
if "PlayerMoved" in keys[0]:
return {
"event_type": "move",
"player": keys[1],
"from_x": int(data[0], 16),
"from_y": int(data[1], 16),
"to_x": int(data[2], 16),
"to_y": int(data[3], 16),
"timestamp": event["createdAt"]
}
# Example: "ResourceHarvested" event
if "ResourceHarvested" in keys[0]:
return {
"event_type": "harvest",
"player": keys[1],
"resource": data[0],
"amount": int(data[1], 16),
"timestamp": event["createdAt"]
}
return None
class TransitionDataset(torch.utils.data.Dataset):
"""Dataset of (state, action, next_state, reward) tuples."""
def __init__(self, transitions: List[dict], encoder: GameStateEncoder):
self.transitions = transitions
self.encoder = encoder
self.data = self._process_transitions()
def _process_transitions(self):
# Group events by timestamp to reconstruct state snapshots
# This is the tricky part—you need to reconstruct
# what the full game state was at each transition
processed = []
for i, transition in enumerate(self.transitions[:-1]):
# Get state before and after
state_before = self._reconstruct_state(i)
state_after = self._reconstruct_state(i + 1)
action = self._extract_action(transition)
reward = self._compute_reward(transition)
processed.append({
"state": state_before,
"action": action,
"next_state": state_after,
"reward": reward
})
return processed
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
item = self.data[idx]
return {
"state": torch.tensor(item["state"], dtype=torch.float32),
"action": torch.tensor(item["action"], dtype=torch.long),
"next_state": torch.tensor(item["next_state"], dtype=torch.float32),
"reward": torch.tensor(item["reward"], dtype=torch.float32)
}
def train_world_model(
model: WorldModel,
dataset: TransitionDataset,
epochs: int = 100,
batch_size: int = 64,
lr: float = 1e-4
):
"""Train the world model on historical transitions."""
dataloader = torch.utils.data.DataLoader(
dataset,
batch_size=batch_size,
shuffle=True
)
optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
state_loss_fn = nn.MSELoss()
reward_loss_fn = nn.MSELoss()
for epoch in range(epochs):
total_loss = 0.0
for batch in dataloader:
optimizer.zero_grad()
pred_next, pred_reward, pred_done = model(
batch["state"],
batch["action"]
)
# State prediction loss
state_loss = state_loss_fn(pred_next, batch["next_state"])
# Reward prediction loss
reward_loss = reward_loss_fn(pred_reward, batch["reward"])
# Combined loss
loss = state_loss + 0.1 * reward_loss
loss.backward()
optimizer.step()
total_loss += loss.item()
if epoch % 10 == 0:
avg_loss = total_loss / len(dataloader)
print(f"Epoch {epoch}: Loss = {avg_loss:.4f}")
```
The training loop is standard supervised learning. The key insight is that *Torii gives you perfect supervision*. Every state transition that occurred in the game's history is recorded and queryable. You're not estimating—you're learning from ground truth.
#### 5. Planning: Imagining Futures
With a trained world model, your agent can simulate futures before acting. The simplest approach is random shooting:
```python
class PlanningAgent:
"""
Agent that uses world model for planning.
Evaluates many action sequences and picks the best.
"""
def __init__(
self,
encoder: GameStateEncoder,
world_model: WorldModel,
n_candidates: int = 256,
horizon: int = 10
):
self.encoder = encoder
self.world_model = world_model
self.n_candidates = n_candidates
self.horizon = horizon
self.n_actions = 20 # Number of possible actions
def plan(self, current_state: torch.Tensor) -> int:
"""
Find the best action by simulating many possible futures.
Returns the first action of the best trajectory.
"""
# Generate random action sequences
action_sequences = torch.randint(
0, self.n_actions,
(self.n_candidates, self.horizon)
)
# Expand state for all candidates
states = current_state.unsqueeze(0).expand(self.n_candidates, -1)
# Simulate all candidates
with torch.no_grad():
_, rewards, dones = self.world_model.rollout(
states,
action_sequences
)
# Compute trajectory values (sum of discounted rewards)
discount = 0.99
discounts = discount ** torch.arange(self.horizon)
# Mask rewards after done
done_mask = torch.cumprod(1 - dones, dim=1)
masked_rewards = rewards * done_mask
trajectory_values = (masked_rewards * discounts).sum(dim=1)
# Pick best trajectory
best_idx = trajectory_values.argmax()
best_action = action_sequences[best_idx, 0].item()
return best_action
async def act(self, game_state: GameState, my_address: str) -> int:
"""Full pipeline: observe → encode → plan → return action."""
# Encode current state
player_features, entity_features = preprocess_game_state(
game_state,
my_address
)
latent = self.encoder(
player_features.unsqueeze(0),
entity_features.unsqueeze(0),
torch.ones(1, 50, dtype=torch.bool),
torch.ones(1, 200, dtype=torch.bool)
)
# Plan best action
action = self.plan(latent.squeeze(0))
return action
```
Random shooting works surprisingly well for short horizons. For longer planning, you'd want something smarter:
* **Cross-Entropy Method (CEM)**: Iteratively refine the action distribution toward higher rewards
* **Model Predictive Control (MPC)**: Re-plan every step as new observations arrive
* **Monte Carlo Tree Search (MCTS)**: Build a search tree, especially powerful for discrete action games
```python
def cem_planning(
world_model: WorldModel,
initial_state: torch.Tensor,
n_iterations: int = 5,
n_candidates: int = 256,
n_elite: int = 32,
horizon: int = 10,
n_actions: int = 20
) -> int:
"""
Cross-Entropy Method planning.
Iteratively improves action distribution toward high-reward sequences.
"""
# Initialize uniform action distribution
action_means = torch.ones(horizon, n_actions) / n_actions
for iteration in range(n_iterations):
# Sample action sequences from current distribution
action_probs = action_means.unsqueeze(0).expand(n_candidates, -1, -1)
action_sequences = torch.multinomial(
action_probs.reshape(-1, n_actions),
num_samples=1
).reshape(n_candidates, horizon)
# Evaluate all sequences
states = initial_state.unsqueeze(0).expand(n_candidates, -1)
with torch.no_grad():
_, rewards, dones = world_model.rollout(states, action_sequences)
# Compute trajectory values
discount = 0.99
discounts = discount ** torch.arange(horizon)
done_mask = torch.cumprod(1 - dones, dim=1)
values = (rewards * done_mask * discounts).sum(dim=1)
# Select elite trajectories
elite_indices = values.topk(n_elite).indices
elite_sequences = action_sequences[elite_indices]
# Update action distribution toward elite actions
for t in range(horizon):
counts = torch.zeros(n_actions)
for seq in elite_sequences:
counts[seq[t]] += 1
action_means[t] = counts / n_elite
# Add small uniform noise to prevent collapse
action_means = 0.9 * action_means + 0.1 / n_actions
# Return most likely first action
return action_means[0].argmax().item()
```
CEM typically finds better plans than random shooting because it focuses sampling on promising regions of action space.
#### 6. Action Execution: Controller CLI
Once planning selects an action, execute it via Controller:
```python
import subprocess
import json
ACTION_MAPPING = {
0: {"entrypoint": "move", "calldata_fn": lambda: ["1", "0"]}, # Move right
1: {"entrypoint": "move", "calldata_fn": lambda: ["-1", "0"]}, # Move left
2: {"entrypoint": "move", "calldata_fn": lambda: ["0", "1"]}, # Move up
3: {"entrypoint": "move", "calldata_fn": lambda: ["0", "-1"]}, # Move down
4: {"entrypoint": "harvest", "calldata_fn": lambda: []},
5: {"entrypoint": "attack", "calldata_fn": lambda: []},
# ... more actions
}
GAME_CONTRACT = "0x..." # Your game's contract address
RPC_URL = "https://api.cartridge.gg/x/starknet/sepolia"
def execute_action(action_id: int) -> dict:
"""Execute a planned action via Controller CLI."""
if action_id not in ACTION_MAPPING:
raise ValueError(f"Unknown action: {action_id}")
action = ACTION_MAPPING[action_id]
calldata = action["calldata_fn"]()
cmd = [
"controller", "execute",
"--contract", GAME_CONTRACT,
"--entrypoint", action["entrypoint"],
"--calldata", ",".join(calldata) if calldata else "",
"--rpc-url", RPC_URL,
"--json"
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"Execution failed: {result.stderr}")
return json.loads(result.stdout)
```
### Handling Opponents: The Multi-Agent Problem
So far we've modeled the world as if our agent acts alone. Real games have opponents. Their actions affect our state too.
There are several approaches:
#### 1. Opponent Modeling as Distribution
Treat opponent actions as a learned distribution:
```python
class OpponentModel(nn.Module):
"""Predict probability distribution over opponent's next action."""
def __init__(self, latent_dim: int, n_actions: int):
super().__init__()
self.net = nn.Sequential(
nn.Linear(latent_dim, 128),
nn.ReLU(),
nn.Linear(128, n_actions),
nn.Softmax(dim=-1)
)
def forward(self, state: torch.Tensor) -> torch.Tensor:
return self.net(state)
```
Train this on historical opponent behavior from Torii. Then sample from it during planning:
```python
def plan_with_opponents(
world_model: WorldModel,
opponent_model: OpponentModel,
initial_state: torch.Tensor,
n_candidates: int = 256,
n_opponent_samples: int = 8
) -> int:
"""Plan considering likely opponent responses."""
# For each candidate action sequence, simulate multiple
# possible opponent behaviors
my_actions = torch.randint(0, 20, (n_candidates, 10))
total_values = torch.zeros(n_candidates)
for _ in range(n_opponent_samples):
states = initial_state.unsqueeze(0).expand(n_candidates, -1)
trajectory_reward = torch.zeros(n_candidates)
for t in range(10):
# Predict opponent action distribution
opp_probs = opponent_model(states)
opp_actions = torch.multinomial(opp_probs, 1).squeeze()
# Combined state transition (my action + opponent action)
# This requires a world model that handles multi-agent transitions
next_states, rewards, _ = world_model.step_multi_agent(
states,
my_actions[:, t],
opp_actions
)
trajectory_reward += rewards * (0.99 ** t)
states = next_states
total_values += trajectory_reward
# Average across opponent samples
total_values /= n_opponent_samples
return my_actions[total_values.argmax(), 0].item()
```
#### 2. Best-Response Planning
Assume opponents play optimally against you. This is conservative—it prevents strategies that only work against weak opponents:
```python
def minimax_planning(world_model, state, depth=3):
"""
Minimax search: assume opponent plays optimally.
Good for zero-sum competitive games.
"""
def evaluate(state, is_my_turn, d):
if d == 0:
return reward_head(state).item(), None
if is_my_turn:
best_value = -float('inf')
best_action = None
for action in range(n_actions):
next_state = world_model(state, action)
value, _ = evaluate(next_state, False, d-1)
if value > best_value:
best_value = value
best_action = action
return best_value, best_action
else:
# Opponent's turn: assume they minimize our value
worst_value = float('inf')
for action in range(n_actions):
next_state = world_model(state, action)
value, _ = evaluate(next_state, True, d-1)
worst_value = min(worst_value, value)
return worst_value, None
_, best_action = evaluate(state, True, depth)
return best_action
```
#### 3. Population-Based Opponent Modeling
Train your world model against a diverse population of agent policies. This creates robust behavior:
```python
# Train world model that's seen many opponent types
opponent_policies = load_diverse_policies() # Historical winners, rule-based, etc.
for epoch in range(epochs):
# Sample opponent from population
opponent = random.choice(opponent_policies)
# Generate rollouts against this opponent
transitions = generate_rollouts(world_model, opponent)
# Train world model on these transitions
train_step(world_model, transitions)
```
### Challenges and Solutions
#### Compute Constraints
World models need GPU compute for training and inference. For onchain games, you're running this off-chain—the model lives on your server, not on Starknet.
**Solutions:**
* Train offline on historical data (cheap, one-time cost)
* Use small models (64-128 dim latent spaces work fine for most games)
* Cache planning results for similar states
* Run inference on CPU for simple games (it's fast enough)
#### Partial Observability
Some onchain games have fog of war or hidden information (e.g., opponent's hand in a card game).
**Solutions:**
* Learn a belief state that represents uncertainty
* Use recurrent models that accumulate information over time
* Monte Carlo rollouts over possible hidden states
```python
class BeliefWorldModel(nn.Module):
"""World model that maintains uncertainty over hidden state."""
def __init__(self, obs_dim, hidden_dim, latent_dim):
super().__init__()
# GRU to accumulate observations into belief
self.belief_rnn = nn.GRU(obs_dim, hidden_dim, batch_first=True)
# Standard world model on belief state
self.transition = WorldModel(hidden_dim, latent_dim)
def forward(self, observation_sequence, actions):
# Build belief from observation history
belief, _ = self.belief_rnn(observation_sequence)
belief_current = belief[:, -1] # Final belief state
# Predict from belief
return self.transition(belief_current, actions)
```
#### Distribution Shift
Your world model was trained on historical data. But as you deploy better agents, the game dynamics change—other players adapt, meta shifts. Your model becomes stale.
**Solutions:**
* Continual learning: fine-tune on recent data periodically
* Ensemble models: train multiple world models, use disagreement as uncertainty
* Online learning: update the model after every episode
```python
def update_world_model_online(model, buffer, batch_size=32):
"""Lightweight online update from recent experience."""
if len(buffer) < batch_size:
return
batch = random.sample(buffer, batch_size)
# Quick gradient update
optimizer.zero_grad()
loss = compute_loss(model, batch)
loss.backward()
optimizer.step()
# Keep buffer bounded
while len(buffer) > 10000:
buffer.pop(0)
```
### The Full Pipeline
Let's put it all together into a complete agent:
```python
import asyncio
import httpx
async def run_world_model_agent(
my_address: str,
game_contract: str,
torii_url: str,
model_path: str
):
"""Complete world model agent loop."""
# Load trained models
encoder = GameStateEncoder()
world_model = WorldModel()
encoder.load_state_dict(torch.load(f"{model_path}/encoder.pt"))
world_model.load_state_dict(torch.load(f"{model_path}/world_model.pt"))
encoder.eval()
world_model.eval()
# Initialize planner
agent = PlanningAgent(encoder, world_model)
# Experience buffer for online learning
experience_buffer = []
async with httpx.AsyncClient() as client:
while True:
# 1. Observe
game_state = await fetch_game_state(client)
# 2. Encode & Plan
action = await agent.act(game_state, my_address)
# 3. Execute
print(f"Executing action {action}")
result = execute_action(action)
print(f"TX: {result.get('transaction_hash')}")
# 4. Wait for state update
await asyncio.sleep(5)
# 5. Observe new state
new_state = await fetch_game_state(client)
# 6. Store experience for online learning
experience_buffer.append({
"state": game_state,
"action": action,
"next_state": new_state,
"reward": compute_reward(game_state, new_state)
})
# 7. Periodic online update
if len(experience_buffer) % 100 == 0:
update_world_model_online(world_model, experience_buffer)
# Loop delay
await asyncio.sleep(30)
if __name__ == "__main__":
asyncio.run(run_world_model_agent(
my_address="0x...",
game_contract="0x...",
torii_url="https://api.cartridge.gg/x/your-game/torii/graphql",
model_path="./trained_models"
))
```
### Looking Forward: Shared Infrastructure
Today, every developer trains their own world model for their own game. But there's an interesting future: shared world models.
Imagine a foundation model trained on every Dojo game's transition data. It learns the grammar of onchain games—how entities move, how resources flow, how combat resolves. Fine-tuning this foundation for a specific game would be much cheaper than training from scratch.
The data is already there. Torii indexes it all. The model architectures exist. Someone just needs to build it.
Even more speculative: decentralized world model training. Multiple agents contribute training compute and share the resulting model. The model itself could live on-chain (well, the weights could be committed to IPFS with hashes on-chain). True AI infrastructure for autonomous worlds.
We're not there yet. But the pieces are falling into place.
### Conclusion
World model agents represent a shift from reactive to predictive game AI. Instead of learning a policy directly (state → action), they learn a simulator (state + action → next state) and plan within that simulation.
For onchain games built on Dojo:
1. **Torii gives you perfect training data**—every transition ever, with ground truth labels
2. **Deterministic game logic** means your world model can be exact, not probabilistic
3. **Controller CLI** provides secure, scoped execution for agent actions
4. **The full pipeline**—observe, encode, simulate, plan, act—runs off-chain while interacting with on-chain state
Start with the basics: a simple encoder, a feedforward world model, random shooting for planning. Get that working. Then add complexity—better architectures, smarter planning, opponent modeling.
The code in this article is a foundation, not a finished product. The architecture scales from simple experiments to sophisticated autonomous systems. Where you take it depends on how deeply you want to understand the game you're playing.
***
*This article is part of the Agent-Native Games series. Previous: [Building Your First Dojo Game Agent](#TODO)*
**Resources:**
* [World Models paper (Ha & Schmidhuber, 2018)](https://worldmodels.github.io/)
* [Dreamer (Hafner et al.)](https://danijar.com/project/dreamer/)
* [Torii Documentation](https://book.dojoengine.org/toolchain/torii)
* [Controller CLI](https://github.com/cartridge-gg/controller-cli)
* [Dojo Book](https://book.dojoengine.org/)
## Building Your First Dojo Game Agent
You've read about why onchain games are perfect for AI agents. The theory makes sense—verifiable state, permissionless execution, real stakes. But how do you actually *build* one?
That's what we're doing today. No hand-waving, no "exercise left to the reader" cop-outs. By the end of this tutorial, you'll have a working game agent that can observe game state, make decisions, and execute actions on a Dojo-powered game. The whole loop.
We'll build something simple: an agent for a hypothetical resource management game. Think Settlers of Catan meets automation. Your agent will monitor resources, spot opportunities, and act on them—all without you clicking a single button.
Let's get into it.
### What You'll Need
Before we start coding, make sure you've got:
* **Node.js 18+** — We're writing the agent in JavaScript/TypeScript. You could use Python or Rust, but JS has the lowest friction for a tutorial.
* **Controller CLI** — Cartridge's tool for programmatic Starknet transactions. This is how your agent gets hands.
* **Basic Starknet knowledge** — You should know what a transaction is, what felt252 means, and roughly how smart contracts work. If terms like "calldata" make you nervous, spend 30 minutes with the Starknet docs first.
* **A Dojo game to target** — We'll use examples from a generic game, but you'll want to adapt this to a real deployed game. Eternum is a great candidate once you're comfortable.
Got all that? Good. Let's build.
### Step 1: Understanding the Game World Contract
Before your agent can play a game, it needs to understand the game. This isn't like screen-scraping a traditional game where you're guessing at internal state from pixels. Onchain games give you *everything*.
#### The World Contract
Every Dojo game has a World contract—the single source of truth for all game state. Models (think of them as tables) store entities and their components. Events emit whenever something interesting happens.
For our example game, imagine these models exist:
```cairo
#[derive(Model, Copy, Drop, Serde)]
struct Player {
#[key]
player_id: ContractAddress,
wood: u32,
stone: u32,
gold: u32,
last_harvest: u64,
}
#[derive(Model, Copy, Drop, Serde)]
struct ResourceNode {
#[key]
node_id: u32,
resource_type: ResourceType,
amount: u32,
position_x: u32,
position_y: u32,
}
```
Your agent needs to read this data. How? Not by calling the contract directly (that's slow and limited). Instead, we use Torii.
#### Torii: Your Window into the Game
Torii is Dojo's indexer. It watches the blockchain, processes events, and serves game state through a GraphQL API. Think of it as a read-optimized view of the World contract.
Every Dojo game deployment includes a Torii instance. You'll typically find it at a URL like:
```
https://api.cartridge.gg/x/your-game/torii
```
Let's explore what's available. Open GraphQL Playground (usually at `/graphql` on the Torii URL) and run:
```graphql
{
__schema {
types {
name
}
}
}
```
This shows you all the queryable types—essentially, all the models your target game has defined. For our resource game, we'd see `Player`, `ResourceNode`, and whatever else the devs created.
#### Reading Your Player State
Here's a practical query to get a player's resources:
```graphql
query GetPlayer($playerId: String!) {
playerModels(where: { player_id: $playerId }) {
edges {
node {
player_id
wood
stone
gold
last_harvest
}
}
}
}
```
Run this with your wallet address as `playerId` and you'll see exactly what the blockchain knows about your position in the game. No API keys. No rate limits (usually). Just direct access to truth.
This is the first superpower of onchain game agents: perfect information. Your agent sees the same state the game sees, because they're reading from the same source.
### Step 2: Setting Up Controller
Reading state is only half the equation. Your agent also needs to *act*—submit transactions that change game state. That's where Controller CLI comes in.
#### Why Controller?
Normally, Starknet transactions require a wallet signature. Every. Single. Time. That's fine for humans clicking buttons, but disastrous for an agent that might execute hundreds of actions per hour.
Controller solves this with session keys. You authorize a temporary keypair to sign transactions on your behalf, scoped to specific contracts and methods. Even if someone steals the session key, the damage is limited—they can only call what you authorized, and the session expires.
#### Installing Controller CLI
```bash
curl -fsSL https://raw.githubusercontent.com/cartridge-gg/controller-cli/main/install.sh | bash
```
Add it to your PATH if needed:
```bash
export PATH="$PATH:$HOME/.local/bin"
```
Verify it works:
```bash
controller --version
```
#### Generating Your Keypair
First, create the session keypair that'll live on your agent's machine:
```bash
controller generate --json
```
You'll get something like:
```json
{
"public_key": "0x1234567890abcdef...",
"stored_at": "~/.config/controller-cli",
"message": "Keypair generated successfully."
}
```
Keep that public key handy—you'll need it for registration.
The private key is stored locally. Even if someone got it, they couldn't do much without an authorized session. But still, treat it like a credential.
#### Creating Your Session Policy
The policy defines what your agent can do. This is the most important security boundary. Be specific.
Create `policy.json`:
```json
{
"contracts": {
"0x": {
"name": "ResourceGame",
"methods": [
{
"name": "harvest",
"entrypoint": "harvest",
"description": "Harvest resources from a node"
},
{
"name": "build",
"entrypoint": "build",
"description": "Build structures"
},
{
"name": "trade",
"entrypoint": "trade",
"description": "Trade resources with other players"
}
]
}
}
}
```
Notice what's *not* here: `transfer`, `withdraw`, or anything that moves tokens out of the game. Your agent can play the game but can't drain your wallet. That's intentional.
#### Registering the Session
This is the one step that requires human involvement. By design.
```bash
controller register \
--file policy.json \
--rpc-url https://api.cartridge.gg/x/starknet/sepolia \
--json
```
You'll get a URL:
```json
{
"authorization_url": "https://x.cartridge.gg/session?public_key=0x...",
"short_url": "https://api.cartridge.gg/s/abc123",
"message": "Open this URL in your browser to authorize..."
}
```
Open that URL in your browser. Review the permissions (they should match your policy). Approve with your wallet.
The CLI waits for confirmation, then stores the active session. Run `controller status --json` to verify:
```json
{
"status": "active",
"session": {
"address": "0x",
"chain_id": "SN_SEPOLIA",
"expires_at": 1735689600,
"expires_in_seconds": 604800
}
}
```
You're authorized. Your agent now has hands.
### Step 3: Querying State via Torii
Let's write real code. We'll create a function that fetches game state from Torii.
```javascript
// agent.js
const TORII_URL = 'https://api.cartridge.gg/x/resource-game/torii/graphql';
async function queryTorii(query, variables = {}) {
const response = await fetch(TORII_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
});
const result = await response.json();
if (result.errors) {
throw new Error(`Torii query failed: ${JSON.stringify(result.errors)}`);
}
return result.data;
}
async function getPlayerState(playerId) {
const query = `
query GetPlayer($playerId: String!) {
playerModels(where: { player_id: $playerId }) {
edges {
node {
player_id
wood
stone
gold
last_harvest
}
}
}
}
`;
const data = await queryTorii(query, { playerId });
const edges = data.playerModels?.edges || [];
return edges[0]?.node || null;
}
async function getResourceNodes() {
const query = `
query GetNodes {
resourceNodeModels(first: 100) {
edges {
node {
node_id
resource_type
amount
position_x
position_y
}
}
}
}
`;
const data = await queryTorii(query);
return data.resourceNodeModels?.edges.map(e => e.node) || [];
}
```
Simple, right? GraphQL makes this feel almost like querying a regular database. But remember—this "database" is cryptographically verifiable and can't lie to you.
#### Subscribing to Events
For more sophisticated agents, you might want real-time updates instead of polling. Torii supports GraphQL subscriptions:
```javascript
// Using graphql-ws (simplified example)
import { createClient } from 'graphql-ws';
const client = createClient({
url: 'wss://api.cartridge.gg/x/resource-game/torii/graphql',
});
function subscribeToEvents(callback) {
return client.subscribe(
{
query: `
subscription {
eventEmitted {
keys
data
transactionHash
}
}
`,
},
{
next: (data) => callback(data.data.eventEmitted),
error: console.error,
complete: () => console.log('Subscription closed'),
}
);
}
```
For this tutorial, we'll stick with polling. It's simpler and good enough for most use cases.
### Step 4: Making Decisions
Here's where your agent gets a brain. Given the game state, what should it do?
#### Rule-Based Logic (Start Here)
Don't overcomplicate it. Start with simple rules:
```javascript
function decideAction(playerState, resourceNodes) {
// Rule 1: If we have few resources, harvest
if (playerState.wood < 50 || playerState.stone < 50) {
const bestNode = findBestHarvestNode(playerState, resourceNodes);
if (bestNode) {
return {
type: 'harvest',
nodeId: bestNode.node_id,
};
}
}
// Rule 2: If we have enough resources, build
if (playerState.wood >= 100 && playerState.stone >= 100) {
return {
type: 'build',
structure: 'workshop',
};
}
// Rule 3: If we have excess gold, trade for what we need
if (playerState.gold > 500 && playerState.wood < 50) {
return {
type: 'trade',
give: { gold: 100 },
receive: { wood: 50 },
};
}
// No action needed
return null;
}
function findBestHarvestNode(playerState, nodes) {
// Find the node with the most resources
// In a real game, you'd factor in distance, competition, etc.
return nodes
.filter(n => n.amount > 0)
.sort((a, b) => b.amount - a.amount)[0];
}
```
These rules are dumb. They don't consider timing, other players, or strategy. But they *work*. You can watch your agent play and improve the rules based on what you observe.
#### LLM-Powered Decisions (Advanced)
Want your agent to think more like a human? You can use an LLM:
```javascript
import Anthropic from '@anthropic-ai/sdk';
const anthropic = new Anthropic();
async function decideLLM(playerState, resourceNodes, recentEvents) {
const gameContext = `
You're playing a resource management game. Your current state:
- Wood: ${playerState.wood}
- Stone: ${playerState.stone}
- Gold: ${playerState.gold}
- Last harvest: ${playerState.last_harvest}
Available resource nodes:
${JSON.stringify(resourceNodes, null, 2)}
Recent game events:
${JSON.stringify(recentEvents, null, 2)}
What action should you take? Respond with JSON:
{
"action": "harvest" | "build" | "trade" | "wait",
"params": { ... },
"reasoning": "brief explanation"
}
`;
const response = await anthropic.messages.create({
model: 'claude-sonnet-4-20250514',
max_tokens: 500,
messages: [{ role: 'user', content: gameContext }],
});
return JSON.parse(response.content[0].text);
}
```
LLMs shine when the optimal action depends on context that's hard to encode in rules. But they're slower, cost money per request, and can make bizarre mistakes. Start with rules, add LLM reasoning when you hit limits.
A hybrid approach often works best:
```javascript
async function decideHybrid(playerState, resourceNodes, recentEvents) {
// Try rule-based first (fast, free, predictable)
const rulesAction = decideAction(playerState, resourceNodes);
// If rules have a clear answer, use it
if (rulesAction && rulesAction.confidence > 0.8) {
return rulesAction;
}
// For ambiguous situations, ask the LLM
if (shouldConsultLLM(playerState, recentEvents)) {
return await decideLLM(playerState, resourceNodes, recentEvents);
}
return rulesAction;
}
function shouldConsultLLM(playerState, recentEvents) {
// Consult LLM for complex scenarios:
// - Multiple viable options
// - Recent threatening player activity
// - Resource levels are moderate (not obviously low or high)
const hasThreats = recentEvents.some(e => e.type === 'attack_nearby');
const resourcesAmbiguous =
playerState.wood > 30 && playerState.wood < 150;
return hasThreats || resourcesAmbiguous;
}
```
This gives you the best of both worlds: fast execution for obvious decisions, nuanced reasoning when it matters.
### Step 5: Executing Actions
Decision made. Now your agent needs to do something about it.
#### Building Calldata
Each action becomes a transaction. The game's contract defines what calldata it expects:
```javascript
function buildCalldata(action) {
switch (action.type) {
case 'harvest':
// harvest(node_id: u32)
return {
contract: GAME_CONTRACT,
entrypoint: 'harvest',
calldata: [action.nodeId.toString()],
};
case 'build':
// build(structure_type: felt252)
const structureTypes = {
workshop: '0x1',
warehouse: '0x2',
barracks: '0x3',
};
return {
contract: GAME_CONTRACT,
entrypoint: 'build',
calldata: [structureTypes[action.structure]],
};
case 'trade':
// trade(give_type: felt, give_amount: u32, receive_type: felt, receive_amount: u32)
return {
contract: GAME_CONTRACT,
entrypoint: 'trade',
calldata: [
resourceToFelt(Object.keys(action.give)[0]),
Object.values(action.give)[0].toString(),
resourceToFelt(Object.keys(action.receive)[0]),
Object.values(action.receive)[0].toString(),
],
};
default:
return null;
}
}
function resourceToFelt(resource) {
const mapping = { wood: '0x1', stone: '0x2', gold: '0x3' };
return mapping[resource];
}
```
The exact format depends on the game's contract. Read the source (it's public!) or check their docs.
#### Executing via Controller
Now we call Controller CLI to submit the transaction:
```javascript
import { execSync } from 'child_process';
function executeAction(calldata) {
if (!calldata) return null;
const { contract, entrypoint, calldata: args } = calldata;
const calldataStr = args.join(',');
try {
const result = execSync(
`controller execute ${contract} ${entrypoint} ${calldataStr} \
--rpc-url https://api.cartridge.gg/x/starknet/sepolia \
--json`,
{ encoding: 'utf-8' }
);
const parsed = JSON.parse(result);
console.log(`Transaction submitted: ${parsed.transaction_hash}`);
console.log(`View on Voyager: https://sepolia.voyager.online/tx/${parsed.transaction_hash}`);
return parsed;
} catch (error) {
console.error('Execution failed:', error.message);
throw error;
}
}
```
#### Handling Failures
Transactions can fail for many reasons. Handle them gracefully:
```javascript
async function executeWithRetry(calldata, maxRetries = 3) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const result = executeAction(calldata);
// Check for known error codes
if (result.status === 'error') {
const { error_code, recovery_hint } = result;
if (error_code === 'SessionExpired') {
console.log('Session expired. Please re-register.');
// In practice, you might notify yourself to re-auth
process.exit(1);
}
if (error_code === 'ManualExecutionRequired') {
console.log('Action not authorized in session policy');
return null;
}
console.log(`Error: ${error_code}. Hint: ${recovery_hint}`);
}
return result;
} catch (error) {
console.log(`Attempt ${attempt} failed: ${error.message}`);
if (attempt < maxRetries) {
await sleep(1000 * attempt); // Exponential backoff
}
}
}
console.error('All retries exhausted');
return null;
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
```
The recovery hints from Controller are genuinely useful. Don't ignore them.
### Step 6: The Game Loop
Time to put it all together. The classic agent loop: observe, decide, act, repeat.
```javascript
const PLAYER_ADDRESS = '0x';
const POLL_INTERVAL = 30000; // 30 seconds
async function runAgent() {
console.log('Starting game agent...');
console.log(`Player: ${PLAYER_ADDRESS}`);
console.log(`Polling every ${POLL_INTERVAL / 1000}s`);
// Check session is active
const status = JSON.parse(
execSync('controller status --json', { encoding: 'utf-8' })
);
if (status.status !== 'active') {
console.error('No active session. Run: controller register --file policy.json ...');
process.exit(1);
}
console.log(`Session expires: ${status.session.expires_at_formatted}`);
console.log('---');
while (true) {
try {
// 1. OBSERVE: Get current game state
const playerState = await getPlayerState(PLAYER_ADDRESS);
const resourceNodes = await getResourceNodes();
if (!playerState) {
console.log('Player not found in game. Have you joined?');
await sleep(POLL_INTERVAL);
continue;
}
console.log(`[${new Date().toISOString()}] State:`, {
wood: playerState.wood,
stone: playerState.stone,
gold: playerState.gold,
});
// 2. DECIDE: What should we do?
const action = decideAction(playerState, resourceNodes);
if (!action) {
console.log('No action needed.');
await sleep(POLL_INTERVAL);
continue;
}
console.log('Decided action:', action);
// 3. ACT: Execute the decision
const calldata = buildCalldata(action);
const result = await executeWithRetry(calldata);
if (result?.transaction_hash) {
console.log('Action executed successfully!');
// Wait a bit longer after acting to let state update
await sleep(5000);
}
} catch (error) {
console.error('Loop error:', error.message);
}
await sleep(POLL_INTERVAL);
}
}
// Entry point
runAgent().catch(console.error);
```
Run it:
```bash
node agent.js
```
And watch your agent play. It'll log what it sees, what it decides, and what it does. When something looks wrong, stop it, tweak the rules, and restart.
#### Making It Robust
The loop above works, but it's fragile. Here's how to make it production-ready:
**Graceful shutdown**: Handle signals properly so you don't corrupt state mid-action.
```javascript
let running = true;
process.on('SIGINT', () => {
console.log('\nShutting down gracefully...');
running = false;
});
// In your loop:
while (running) {
// ... your logic
}
```
**Session monitoring**: Don't wait for your session to expire mid-execution. Check proactively:
```javascript
function checkSessionHealth() {
const status = JSON.parse(
execSync('controller status --json', { encoding: 'utf-8' })
);
// Warn if less than 24 hours remaining
if (status.session?.expires_in_seconds < 86400) {
console.warn('⚠️ Session expires in <24h. Consider re-registering.');
}
// Stop if less than 1 hour remaining
if (status.session?.expires_in_seconds < 3600) {
console.error('Session nearly expired. Stopping agent.');
return false;
}
return status.status === 'active';
}
```
**Rate limiting**: Blockchains have throughput limits, and games often have cooldowns. Respect both:
```javascript
const actionCooldowns = new Map();
function canExecute(actionType) {
const lastExecution = actionCooldowns.get(actionType) || 0;
const cooldownMs = 60000; // 1 minute between same actions
if (Date.now() - lastExecution < cooldownMs) {
return false;
}
actionCooldowns.set(actionType, Date.now());
return true;
}
```
**Logging and metrics**: You'll want to know what your agent did while you were asleep:
```javascript
const fs = require('fs');
function logAction(action, result) {
const entry = {
timestamp: new Date().toISOString(),
action,
success: !!result?.transaction_hash,
txHash: result?.transaction_hash,
};
fs.appendFileSync(
'agent-log.jsonl',
JSON.stringify(entry) + '\n'
);
}
```
**Multiple agents**: Some strategies work better with coordination. But be careful—this can get complex fast. Start with one agent that works reliably before scaling up.
### Debugging Your Agent
Things will go wrong. Here's how to figure out what:
**State mismatch**: Your agent thinks it should harvest, but the transaction fails. Check if someone else harvested the node first. Onchain games are competitive—state changes between your read and your write.
```javascript
// Before executing, verify the opportunity still exists
async function verifyOpportunity(action, currentNodes) {
if (action.type === 'harvest') {
const node = currentNodes.find(n => n.node_id === action.nodeId);
if (!node || node.amount === 0) {
console.log('Node depleted since we last checked. Skipping.');
return false;
}
}
return true;
}
```
**Session issues**: If you see `ManualExecutionRequired`, your session doesn't authorize the action you're trying to take. Either update your policy or your decision logic is generating invalid actions.
**Transaction debugging**: Use Voyager to inspect failed transactions. The error message is usually in the execution trace. Common issues:
* Wrong calldata format (check argument types)
* Insufficient resources (your agent tried to spend what it doesn't have)
* Game-specific validation (cooldowns, turn requirements, etc.)
**Torii lag**: The indexer isn't instant. If you act immediately after state changes, you might read stale data. Add a small delay after your own transactions:
```javascript
if (result?.transaction_hash) {
console.log('Action executed. Waiting for indexer...');
await sleep(10000); // Give Torii time to process
}
```
Most bugs fall into these categories. When something new happens, add it to your logging and you'll spot patterns.
### What You've Built
Let's recap. You now have:
1. **State observation** via Torii GraphQL—perfect information about the game
2. **Autonomous signing** via Controller sessions—scoped, time-limited, secure
3. **Decision logic** that can be as simple or sophisticated as you want
4. **Transaction execution** with proper error handling
5. **A running loop** that ties it all together
This isn't a toy. Agents built on this pattern are already playing real games with real tokens at stake. The architecture scales from hobby experiments to serious autonomous systems.
### Next Steps
You've got the foundation. Where you take it depends on your interests:
**Better strategies**: The decision function is where competitive advantage lives. Can you model other players? Predict market movements? Find arbitrage opportunities?
**Multi-agent coordination**: What if you had 10 agents working together? Or competing against each other to test strategies?
**LLM integration**: We showed a basic example. But you could give the LLM memory, tools for analysis, or let it learn from outcomes.
**Real deployment**: Run this on a server 24/7. Add monitoring. Maybe even let it manage meaningful amounts of value.
The code from this tutorial is a starting point, not a ceiling. Fork it, break it, make it your own.
***
*This article is part of the Agent-Native Games series. Read the previous article: [Why Onchain Games Are Perfect for AI Agents](/blog/why-onchain-games-perfect-for-ai-agents)*
**Resources:**
* [Controller CLI Documentation](https://github.com/cartridge-gg/controller-cli/blob/main/LLM_USAGE.md)
* [Torii Documentation](https://book.dojoengine.org/toolchain/torii)
* [Dojo Book](https://book.dojoengine.org/)
import { BlogBanner } from "../../components/BlogBanner";
import { BlogPosts } from "../../components/BlogPosts";

## Provable Games
::authors
[Autonomous Worlds (AWs)](https://aw.network/posts/the-case-for-autonomous-worlds) are digitally native worlds, defined and governed by their smart contracts, existing entirely onchain and inheriting the unique affordances of the blockchain: composable, extensible, permissionless and persistent.
Unencumbered by platform risks, AWs promise a new medium for our collective expression, a natural extension of our digital culture, with a credible commitment to neutrality.
They represent [a new paradigm in game design](https://aw.network/posts/composable-engineering): where core mechanics can be openly defined, where primitives can be permissionlessly extended, and where a world's substrate can be continually reinterpreted into new experiences.
An AW's super power is its [hardness](https://stark.mirror.xyz/n2UpRqwdf7yjuiPKVICPpGoUNeDhlWxGqjulrlpyYi0).
Its ability to credibly commit to future states: that it will exist according to its rules and that it will exist as long as it is desired.
Blockchains accomplish this trivially, providing a credibly neutral, persistent shared computing environment for the AWs smart contracts; however, achieving these properties at the scale necessary for complex, interactive onchain worlds, without making significant tradeoffs, is a fundamental challenge.
#### Introducing Dojo: the provable game engine

Our solution is Dojo, a provable game engine, enabling games execution to be proved and attested to by a validity proof, a capability stemming from its [implementation in Cairo](https://github.com/starkware-libs/cairo).
Dojo serves as the world's engine, empowering developers to concentrate on creating games without the burden of developing all of the auxiliary infrastructure required to realize them.
It presents a solution to the challenge of fully deploying complex, immersive experiences, such as MMORPGs, on the blockchain.
We achieve this by moving computation out of a blockchain's shared execution context to specialized "micro-rollups", complemented by [fractal scaling](https://medium.com/starkware/fractal-scaling-from-l2-to-l3-7fe238ecfb4f) techniques, to provide cheap and abundant computation.
This is enabled through zero-knowledge proofs, which are ideally suited for balkanized execution and long tail computation typical of games, allowing the use of powerful centralized sequencers to achieve the performance necessary for immersive real-time games.
During the execution of a Dojo game, execution and sequencing can be done [natively](https://github.com/lambdaclass/cairo_native), [significantly out performing vm based execution](https://twitter.com/fede_intern/status/1729165583596597647), and then asynchronously proved to generate and settle a zero-knowledge proof that attests to the validity of the execution onchain, inheriting the full security of a base layer, like Ethereum.
#### Execution sharding with proofs

A core insight is that gaming experiences, outside of their shared economy provided by a decentralized blockchain, can be naturally sharded into more narrowly scoped execution contexts, removing the necessity to compete for computational resources in a zero-sum execution context.
Consider this World of Warcraft example, where there exists several layers:
* **Cities** (Orgrimmar, Stormwind) with their global execution context (auction house, messaging, trading) for thousands of players with low frequency actions (no combat, etc).
* **Regions** (Barrens, Strangle Thorn Vale) with their own regional execution shard for hundreds of players with sparse, average frequency actions (fighting mobs, turning in quests).
* **Instances** (Scarlet Monastery, Molten Core) with their own instance specific execution shard for up to tens of players, with high frequency actions (fighting bosses).
This is something uniquely enabled by zero-knowledge technology, since the security assumptions of optimistic approaches (that some honest party will validate the chain through re-execution) breaks down as the number of participants reduces and economic incentives scatter.
In particular, it is not possible to shard to the instance level with an optimistic rollup and maintain the security guarantees from the base layer, where there is no incentive for a third party to re-execute.
Approximately, this tradeoff looks something like this:

#### Towards cheap, abundant compute
In addition, we get several other nice properties:
* **Proof recursion** enables temporal compression of an entire sequence of actions from an instance run into a single attestation, massively reducing the data availability cost and enabling us to cheaply settle on robust data availability layers like Ethereum.
In contrast to optimistic approaches, where the input calldata for every action inside a game needs to be provided to the data availability layer to enable re-execution, forcing these approaches to make security tradeoffs in order to scale data availability bandwidth.
Another cool side effect is it allows players to keep their strategies secret, by not revealing the intermediate steps in a sequence that led to the defeat of a final boss, while still credibly convincing everyone that they accomplished the task and allowing them to unlock the reward.
* **Proof aggregation** enables proofs from every execution shard to be combined and the settlement cost amortized across those players, reducing transaction costs as the number of players increases (!).
This is possible not only across a particular game, but globally, across all Cairo based proving systems (starknets, katanas, coprocessors, etc), by leveraging shared proving infrastructure provided by SHARP.

#### What are the tradeoffs vs Optimistic approaches?
While this approach is incredibly well suited for the type of workload we are targeting, it does introduce some limitations compared to optimistic approaches:
* Maintaining provability gives us less flexibility to use tools such as precompiles and in-node modifications to enshrine game logic into the sequencer itself.
While we are able to introduce our own syscalls (similar to precompiles), their outputs are untrusted and need to be verified in the provable program.
However, our opinion is that in-node modifications should be limited to the extent possible anyways, since in-node changes add significant distribution overhead to sequencer binaries in order to maintain the optimistic security model and introduce a two-tiered development model, where node contributors are significantly more influential than smart contract developers, compromising on platform neutrality.
* Optimistic rollups with large execution contexts and many shared stakeholders, where the honest actor assumption is compelling, can maintain a strong security model and in some cases, can provide cheaper execution costs than a provable network where every execution needs to be proved.
In this regard, I believe there is an opportunity for engines like MUD and Dojo to be complementary (more on that in the future).
#### Conclusion
Dojo provides a developer friendly framework for developing and scaling onchain games and autonomous worlds that are composable, extensible, permissionless and persistent.
We do so by providing a \~zero-cost abstraction for developers to succinctly define their onchain world using Cairo and a robust toolchain for building, migrating, deploying, proving and settling these worlds in production.
Leveraging execution sharding and fractal scaling, we're able to scale computation to meet the needs of complex, interactive experiences, while maintaining the security properties of Ethereum.
Stay tuned as we delve into the depths of the Dojo stack, sharing insights on what we've built and what we plan to build, including exciting developments in client side proving, hidden information, fractal scaling, randomness, cross-chain messaging, and real-time communication.
In the meantime, checkout our website, read our book, and dig into the code for more info!
*Special thanks to Loaf and Gabe for feedback and review*
## Why Onchain Games Are Perfect for AI Agents
AI agents are getting good at games. Really good. But here's the thing most people miss: the limiting factor isn't intelligence anymore. It's access.
An agent can reason about complex strategy, optimize resource allocation, and learn from thousands of simulated games. What it can't do? Reliably interact with most games. Traditional games weren't built for programmatic players — they were built to keep them out.
Onchain games flip this entirely. They're permissionless by default, transparent by design, and cryptographically verifiable. For developers building AI game agents, this isn't just convenient. It's transformative.
Let's break down exactly why onchain architecture gives agents what they need, and how you can use the Dojo stack to build agents that actually work.
!\[Diagram showing agent architecture connecting to onchain game state]\(placeholder: technical diagram showing AI agent → Torii → World Contract → execution flow, clean line art style)
*The agent stack: query state via Torii, reason locally, execute via Controller*
### What Agents Actually Need
Before we talk about solutions, let's be precise about the problem. An AI agent trying to play a game needs four things:
**Observable state.** The agent needs to see what's happening. Not through pixel parsing or screen scraping — through structured data it can reason about. Give it a JSON object with game state, not a 1920x1080 screenshot.
**Reliable execution.** When the agent decides to make a move, that move needs to happen. Consistently. No random failures, no "please complete this CAPTCHA," no silent drops.
**Verifiable outcomes.** The agent needs to know its action worked. Not "the server said it worked" — cryptographic proof that the state changed as expected. This matters when real value is on the line.
**Economic alignment.** Agents need skin in the game. If playing well leads to real rewards (and playing poorly to real losses), you get agents that optimize for actually winning, not just exploring the action space.
Traditional games give you maybe one of these. Onchain games give you all four.
### Why Traditional Games Fail Agents
I've built game bots before. You probably have too. And you know the pain.
#### Screen Scraping Is Brittle
The classic approach: capture frames, run computer vision, extract state. It works... until it doesn't.
```python
# The traditional approach
screenshot = capture_screen()
health_bar = detect_region(screenshot, HEALTH_BAR_COORDS)
current_health = ocr_number(health_bar) # Good luck with this
```
Font changes break your OCR. UI updates shift your detection regions. Anti-aliasing makes edge detection unreliable. You spend more time maintaining the scraper than building the actual agent logic.
And that's assuming you can capture the screen at all. Many games detect and block screen capture software.
#### Private Servers Mean Trust Issues
Some games offer APIs. Great! Except you're trusting that:
* The server isn't lying about game state
* Other players aren't getting different information
* The server won't change behavior when it detects bot-like patterns
* Your API access won't get revoked
With private servers, you're playing in someone else's sandbox. They can change the rules whenever they want.
#### Permission Gates Kill Innovation
Want to build an agent for a popular game? Here's your path:
1. Apply for API access
2. Wait weeks/months for approval
3. Get rejected because "bots hurt player experience"
4. Try to reverse-engineer the protocol
5. Get banned
The games that do allow programmatic access typically lock it behind expensive licensing deals or restrict it to "approved partners." Innovation happens at the edges, and these gates keep independent developers out.
#### Anti-Bot Measures Are Adversarial
Game companies spend millions fighting bots. CAPTCHAs, behavioral analysis, device fingerprinting, rate limiting, shadow bans. It's an arms race, and as an agent developer, you're the enemy.
Even if your agent provides value — even if it makes the game more interesting — you're fighting the infrastructure instead of building cool stuff.
### How Onchain Games Deliver
Now let's flip the script. Here's what building agents looks like with onchain games.
#### Verifiable State via Torii
Every Dojo game exposes its state through Torii, an indexer that makes all game data queryable via GraphQL or gRPC. No screen scraping. No reverse engineering. Just query the data you need.
```graphql
# Query all realms with their resources and positions
query GetGameState {
entities(keys: ["*"]) {
edges {
node {
keys
models {
... on Position {
x
y
}
... on Resources {
wood
stone
food
}
... on Army {
units
strength
}
}
}
}
}
}
```
This isn't cached data from a friendly API. It's indexed directly from the blockchain. The state you're querying is the same state everyone else sees — cryptographically guaranteed.
Want to know if an opponent is massing troops near your border? Query it. Want historical data about price movements in the in-game market? It's all there, indexed and searchable.
```javascript
// Real-time subscription to game events
const subscription = gql`
subscription OnGameEvents {
events(keys: ["game_id"]) {
data
keys
transactionHash
}
}
`;
client.subscribe({ query: subscription }).subscribe({
next(event) {
agent.processEvent(event.data);
}
});
```
Your agent can subscribe to events and react in real-time. When an opponent moves, you know immediately — not when the game client decides to render it.
#### Permissionless Execution
Here's the part that changes everything: you don't need permission to play.
Onchain games are smart contracts. Anyone can call them. No API keys, no approval process, no Terms of Service that ban "automated play." If you can construct a valid transaction, you can execute it.
```bash
# Execute a game action via Controller CLI
controller execute \
--contract 0x123...abc \
--entrypoint move_army \
--calldata 0x5,0x10,0x3 \
--rpc-url https://api.cartridge.gg/x/starknet/mainnet \
--json
```
Your agent has the exact same interface as a human player. Better, actually — humans use clunky GUIs while your agent talks directly to the contract.
This isn't just convenient. It's a different relationship with the game. You're not a guest hoping the game company lets you play. You're a participant in a permissionless protocol.
#### Provable Logic with Cairo
Dojo games are written in Cairo, a provably-correct smart contract language. This means:
**Deterministic outcomes.** Given the same inputs, you always get the same outputs. Your agent can simulate actions locally before committing them onchain.
**Auditable rules.** The game logic is public. You can read the contracts, understand exactly how combat resolves, how resources accumulate, how edge cases are handled. No hidden mechanics.
**Guaranteed execution.** If your transaction is valid and included in a block, it will execute exactly as the contract specifies. No server-side fudging.
```rust
// Cairo game logic - completely transparent
fn attack(
ref self: ContractState,
attacker_id: u128,
defender_id: u128
) {
let attacker = get!(self.world, attacker_id, (Army));
let defender = get!(self.world, defender_id, (Army));
// Combat resolution is deterministic and auditable
let damage = calculate_damage(attacker.strength, defender.defense);
// State changes are atomic - all or nothing
set!(self.world, (
Army { id: defender_id, health: defender.health - damage }
));
emit!(self.world, AttackEvent { attacker_id, defender_id, damage });
}
```
Your agent can reason about this logic directly. No guessing how the black box works.
#### Real Economic Stakes
Onchain games use real tokens. Not points. Not "premium currency." Actual transferable assets with market value.
This creates genuine incentives. An agent that plays well accumulates valuable resources. One that plays poorly loses them. The feedback loop is real, immediate, and denominated in something that matters.
For agent developers, this is huge. You can build agents that earn their own operating costs. You can create agents that compete for profit. The economics align with performance.
Consider the training implications. With traditional games, you might run millions of simulated games to train an agent, but deployment is disconnected from training. The simulation doesn't match reality perfectly, and there's no economic feedback.
With onchain games, your agent learns from real stakes from day one. Bad decisions cost real resources. Good decisions accumulate real value. The agent isn't optimizing for a proxy metric — it's optimizing for the actual thing that matters.
This also enables new business models. Imagine an agent-as-a-service that plays on behalf of users, taking a cut of winnings. Or a DAO-owned agent that distributes profits to token holders. The permissionless nature of onchain games makes these arrangements enforceable and trustless.
### The Dojo Stack for Agents
Let's get concrete. Here's how the pieces fit together.
#### World Contract
Every Dojo game has a World contract — a single entry point that manages all game state. Think of it as the game's database and execution engine rolled into one.
```
World Contract (0x123...abc)
├── Models (data schemas)
│ ├── Position { entity_id, x, y }
│ ├── Resources { entity_id, wood, stone, food }
│ └── Army { entity_id, units, strength }
├── Systems (game logic)
│ ├── move_army(army_id, target_x, target_y)
│ ├── gather_resources(worker_id)
│ └── attack(attacker_id, defender_id)
└── Events (state changes)
├── ArmyMoved
├── ResourcesGathered
└── CombatResolved
```
For agents, this is the API. Query models for state, call systems to take actions, subscribe to events for updates.
#### Torii Indexer
Torii watches the blockchain and indexes everything that happens in the game. It provides:
* **GraphQL API** for complex queries
* **gRPC** for high-performance streaming
* **WebSocket subscriptions** for real-time updates
```javascript
// Initialize Torii client
import { ToriiClient } from "@dojoengine/torii-client";
const client = await ToriiClient.create({
toriiUrl: "https://api.cartridge.gg/x/eternum/torii",
worldAddress: "0x123...abc",
});
// Fetch all entities matching a query
const entities = await client.getEntities({
model: "Position",
query: { x: { $gte: 0, $lte: 100 } }
});
// Subscribe to model updates
client.onEntityUpdated((entity) => {
console.log("Entity changed:", entity);
agent.onStateChange(entity);
});
```
The indexer is your agent's eyes. Query frequently, subscribe to what matters, and keep your local state synchronized.
#### Controller CLI
Here's where agent execution gets practical. The Controller CLI lets you sign and submit transactions programmatically — no browser wallet, no popup confirmations.
**Generate a keypair:**
```bash
controller generate-keypair --json
# Output: { "publicKey": "0x...", "privateKey": "0x..." }
```
**Check session status:**
```bash
controller status --json
# Output: { "status": "no_session" | "keypair_only" | "active" }
```
**Register a session with policies:**
```json
// policy.json - defines what actions the session can take
{
"contracts": {
"0x123...abc": {
"methods": ["move_army", "gather_resources", "attack"]
}
},
"expires": "2026-03-12T00:00:00Z"
}
```
```bash
controller register-session policy.json \
--rpc-url https://api.cartridge.gg/x/starknet/mainnet \
--json
# Opens browser for user approval, then session is active
```
**Execute transactions:**
```bash
controller execute \
--contract 0x123...abc \
--entrypoint move_army \
--calldata 0x42,0x10,0x15 \
--rpc-url https://api.cartridge.gg/x/starknet/mainnet \
--json
```
The key insight: sessions are pre-authorized. Once a user approves a session policy, the agent can execute any action within that policy without further human intervention. Your agent can run autonomously for days or weeks.
#### Session Key Security
"Wait, doesn't that mean if someone gets my session key, they can drain my account?"
Not quite. Session keys are scoped:
* **Contract whitelist** — only call specific game contracts
* **Method whitelist** — only call specific functions
* **Expiration** — sessions automatically expire
* **No token transfers** — session can't move assets out of the game
Even in a worst case leak, the damage is limited to in-game actions the session was authorized for. An attacker could make your agent play badly, but they can't steal your tokens.
### Design Patterns for Agent-Friendly Games
If you're building a game (not just an agent), here's how to make it agent-friendly.
#### Atomic Actions
Each transaction should represent one complete decision. Don't make agents submit multi-step flows.
```rust
// Good: single atomic action
fn reinforce_and_attack(
ref self: ContractState,
reinforcement_count: u32,
target_id: u128
) {
// Reinforcement and attack in one tx
}
// Bad: requires two transactions with state between them
fn prepare_attack(...) { } // First tx
fn execute_attack(...) { } // Second tx, might fail if state changed
```
Atomic actions let agents reason about outcomes confidently. Multi-step flows create race conditions.
#### Rich Event Emission
Emit events for everything. Agents need to know what happened and why.
```rust
// Emit detailed events
emit!(self.world, CombatResolved {
attacker_id,
defender_id,
damage_dealt,
defender_destroyed: defender.health <= 0,
attacker_remaining_strength,
block_timestamp: starknet::get_block_timestamp()
});
```
Events are cheap. Debugging agents without them is expensive.
#### Composable Game Logic
Build systems that can be combined. Agents excel at finding optimal combinations humans wouldn't try.
```rust
// Separate systems that compose
fn move_army(...) { }
fn set_army_stance(...) { }
fn declare_alliance(...) { }
fn trade_resources(...) { }
// Agents can orchestrate these in creative ways
// Human: move, then attack
// Agent: move, set defensive stance, trade resources to ally,
// have ally attack while you defend
```
The more composable your systems, the more interesting the agent strategies.
#### Query-Friendly State
Structure your models for efficient querying. Agents will hammer your indexer.
```rust
// Include commonly-queried fields directly
struct Army {
id: u128,
owner: ContractAddress,
position_x: u32, // Denormalized for query efficiency
position_y: u32,
strength: u32,
last_action_time: u64, // Useful for cooldown calculations
}
```
Think about what queries agents will run and optimize for them.
#### Information Visibility
Be intentional about what's public and what's hidden. Onchain state is public by default, but you can design around this.
Some information should be visible — it creates strategic depth when agents can observe and react to each other. Army positions, resource levels, territorial control.
Other information might benefit from fog of war. You can implement commitment schemes where agents commit to actions in one transaction and reveal them later. This prevents reactive counter-play and rewards genuine prediction.
```rust
// Commitment scheme for hidden actions
fn commit_action(ref self: ContractState, commitment: felt252) {
// Store hash of (action, salt)
set!(self.world, (Commitment { player: get_caller_address(), hash: commitment }));
}
fn reveal_action(ref self: ContractState, action: felt252, salt: felt252) {
let commitment = get!(self.world, get_caller_address(), (Commitment));
assert(poseidon_hash(action, salt) == commitment.hash, 'invalid reveal');
// Execute the revealed action
}
```
The key is making information visibility a deliberate design choice, not an accident of architecture.
### The Agent Development Loop
Here's the actual workflow for building a game agent:
1. **Explore state** — Query Torii, understand what data is available
2. **Understand actions** — Read the system contracts, know what you can do
3. **Build perception** — Write code to fetch and parse game state into useful representations
4. **Implement strategy** — Rule-based, ML-powered, or LLM-driven decision making
5. **Execute actions** — Submit transactions via Controller
6. **Monitor and iterate** — Watch performance, refine strategy
```python
# The core agent loop
async def agent_loop():
while True:
# Perceive
state = await torii.query_game_state()
# Decide
action = strategy.decide(state)
# Act
if action:
tx = await controller.execute(action)
await wait_for_confirmation(tx)
# Rate limit appropriately
await asyncio.sleep(POLL_INTERVAL)
```
It's simple in structure, complex in the details. But the infrastructure handles the hard parts — you focus on strategy.
### What's Next
We're at the early days of agent-native gaming. The tools exist. The games are launching. What's missing is developers building agents.
If you want to go deeper:
* **Build something**: Check out [Building Your First Dojo Game Agent](/blog/building-your-first-dojo-agent) for a hands-on walkthrough
* **Understand the context**: Read [The Rise of Agent-Native Gaming](https://cartridge.gg/blog/agent-native-gaming) for the bigger picture
* **Get the tools**: [Controller CLI documentation](https://github.com/cartridge-gg/controller-cli) has everything you need to start executing transactions
Onchain games aren't just compatible with AI agents. They're waiting for them. The question is: what will you build?
***
*Building an agent for a Dojo game? Share what you're working on — we'd love to see it.*
import { LinkCard } from "../../components/LinkCard";
## Dojo Framework Overview
:::note
Dojo is built on top of Cairo.
We suggest [familiarizing yourself with Cairo](https://book.cairo-lang.org/) before using Dojo.
:::
### What is Dojo?
Dojo is a comprehensive framework for building provable games and autonomous worlds on Starknet.
It combines the power of Cairo smart contracts with Entity-Component-System (ECS) architecture to create scalable, composable onchain applications.
**Key Benefits:**
* **Provable**: All game logic and state changes are verifiable onchain
* **Composable**: Modular design allows for easy extension and integration
* **Scalable**: Optimized for high-performance onchain applications
* **Developer-friendly**: Rich tooling and familiar development patterns

### Understanding ECS Architecture
[Entity-Component-System](https://en.wikipedia.org/wiki/Entity_component_system) (ECS) is a design pattern that separates data from logic, enabling highly modular and scalable applications.
**The Problem ECS Solves:**
Traditional object-oriented programming can lead to complex inheritance hierarchies and tight coupling between data and behavior.
ECS addresses these issues by decomposing your application into three distinct parts:
#### The ECS Trinity

* **Entities**: The *objects* in your game — characters, items, etc.
* **Components**: The *properties* of your entities — position, durability, etc.
* **Systems**: The *rules* that govern your game — movement, combat, etc.
With ECS, you assign **components** to **entities**, and operate on them using **systems**.
This approach allows for a more modular and scalable design, as well as better performance and memory usage.
Entities are typically represented as a unique identifier, to which components are assigned.
Systems can then operate over large numbers of entities at once, depending on the components they have.
As an example, a combat system might reduce the durability of all weapons used in a battle, while a rest system might increase the health of all members of a party.
**ECS Benefits:**
* **Modularity**: Components can be mixed and matched across entity types
* **Performance**: Systems can efficiently process large numbers of entities
* **Flexibility**: Easy to add new components or systems without affecting existing code
#### Real-World Example
Consider a simple RPG where both players and monsters can move and fight:
```rust
// Components (Data)
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Position {
#[key]
entity_id: u32,
x: u32,
y: u32,
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Health {
#[key]
entity_id: u32,
hp: u32,
max_hp: u32,
}
// Systems (Logic)
#[starknet::interface]
trait IGameSystem {
fn move(ref self: T, entity_id: u32, direction: Direction);
fn fight(ref self: T, attacker_id: u32, target_id: u32);
}
#[dojo::contract]
mod game_system {
use super::{Position, Health, Direction, IGameSystem};
use dojo::model::{ModelStorage};
use dojo::world::{WorldStorage, WorldStorageTrait};
#[abi(embed_v0)]
impl GameSystemImpl of IGameSystem {
fn move(ref self: ContractState, entity_id: u32, direction: Direction) {
let mut world = self.world(@"my_game");
let mut position: Position = world.read_model(entity_id);
// Update position based on direction
world.write_model(@position);
}
fn fight(ref self: ContractState, attacker_id: u32, target_id: u32) {
let mut world = self.world(@"my_game");
let mut attacker_health: Health = world.read_model(attacker_id);
let mut target_health: Health = world.read_model(target_id);
// Handle combat logic
world.write_model(@target_health);
}
}
}
```
Both player and monsters can have Position and Health components, and the same systems work for both.
### Dojo's ECS Implementation
Dojo adapts ECS for the onchain environment with three core concepts:
#### 1. Models (Components)
Models are Cairo structs that define your application's data structures.
They act as *components* in the ECS pattern.
```rust
#[derive(Copy, Drop, Serde)]
#[dojo::model] // Defines a model, used to generate Torii events
struct Position {
#[key] // Defines a model's key, like an ORM primary key
pub player: ContractAddress,
// The rest of the model's attributes
pub x: u32,
pub y: u32,
}
```
:::note
`#[dojo::model]` and `#[key]` are examples of [procedural macros](https://book.cairo-lang.org/ch12-10-procedural-macros.html), which tell the Cairo compiler where to add Dojo-specific code.
:::
**Key Features:**
* **Automatic indexing**: All model changes are automatically indexed by Torii
* **Type safety**: Full Cairo type system support
* **Composability**: Models can be reused across different entity types
#### 2. Systems (Smart Contracts)
Systems implement your application's business logic as Cairo contract functions.
```rust
// First define the interface
#[starknet::interface]
trait IActions {
fn move_player(ref self: T, direction: Direction);
}
#[dojo::contract] // Defines a Dojo contract
mod actions {
use super::IActions;
use starknet::{ContractAddress, get_caller_address};
use dojo::model::{ModelStorage};
use dojo::world::{WorldStorage, WorldStorageTrait};
#[abi(embed_v0)]
impl ActionsImpl of IActions {
fn move_player(ref self: ContractState, direction: Direction) {
// Get the world and player entity key
let mut world = self.world(@"my_game");
let player = get_caller_address();
// Read current position
let mut position: Position = world.read_model(player);
// Update position based on direction
match direction {
Direction::Up => position.y += 1,
Direction::Down => position.y -= 1,
Direction::Left => position.x -= 1,
Direction::Right => position.x += 1,
}
// Write updated position
world.write_model(@position);
}
}
}
```
**Key Features:**
* **Permissioned access**: Fine-grained control over who can modify what
* **Atomic operations**: All state changes happen in a single transaction
* **Event emission**: Automatic event generation for state changes
#### 3. World (Resource Registry)
The World contract serves as the central coordinator, managing all models and systems.
```rust
// The World provides a unified interface for all operations
let mut world = self.world(@"my_game");
// Cairo's typing tells us we are querying the player's position
let position: Position = world.read_model(player_id);
// Update game state
world.write_model(@new_position);
// Emit custom events if needed
world.emit_event(@PlayerMoved { player, direction });
```
**Key Features:**
* **Centralized state**: Single source of truth for all application data
* **Permission management**: Hierarchical authorization system
* **Upgradeability**: Safe model and system upgrades
* **Introspection**: Rich metadata and schema information
### Architecture Overview

### Development Workflow
1. **Design Models**: Define your data structures as Dojo models
2. **Implement Systems**: Create contract functions that operate on models
3. **Configure Permissions**: Set up authorization for your systems
4. **Deploy to World**: Register your models and systems
5. **Build Client**: Use indexed data to create rich user experiences
### Best Practices
#### Model Design
* **Keep models small and focused** - Each model should represent a single concept
* **Design for reusability** - Models should be composable across different entity types
* **Plan for upgrades** - Consider future schema changes when designing models
* **Always derive required traits** - All models must derive `Drop` and `Serde`
* **Use key fields correctly** - All `#[key]` fields must come before non-key fields
#### System Design
* **Minimize gas costs** - Batch operations and optimize storage access
* **Handle errors gracefully** - Use assertions and proper error handling
* **Design for permissions** - Group related systems by permission requirements
* **Define interfaces first** - Always create a `#[starknet::interface]` before implementing systems
* **Use proper mutability** - Make world reference mutable when writing models
#### Architecture
* **Separate concerns** - Keep data (models) and logic (systems) distinct
* **Use events effectively** - Emit events for important state changes
* **Plan your namespaces** - Organize resources logically and use consistent namespace strings
* **Follow ECS principles** - Compose entities using multiple small, focused models
### Next Steps
Ready to start building with Dojo?
Here's your learning path:
{" "}
{" "}
## Development Workflow
Now that you understand Dojo's toolchain, let's explore the typical development workflow.
You'll learn how to iterate efficiently, test your code, and debug issues as you build your Dojo applications.
### The Development Cycle
A typical Dojo development session follows this pattern:
```
1. Plan & Design
↓
2. Write Cairo Code
↓
3. Test Locally
↓
4. Debug & Iterate
↓
5. Deploy & Verify
↓
6. Repeat
```
Let's walk through each step with practical examples.
### Setting Up Your Development Environment
Start each development session by launching your tools:
```bash
# Terminal 1: Start Katana
katana --dev
# Terminal 2: Deploy and start Torii
sozo migrate --dev
torii --dev
# Terminal 3: Your workspace for commands
# (keep this free for running sozo commands)
```
> **Tip**: Create a simple script to start all tools at once.
> Save this as `start-dev.sh`:
```bash
#!/bin/bash
# Start development environment
katana --dev &
sleep 2
sozo migrate --dev
torii --dev &
echo "🚀 Development environment ready!"
```
### Planning and Design
Before writing code, think about your game or application structure:
#### Define Your Models (Components)
What data does your application need to track?
```cairo
// Example: A simple trading card game
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Card {
#[key]
pub id: u32,
pub owner: ContractAddress,
pub attack: u8,
pub defense: u8,
pub cost: u8,
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Game {
#[key]
pub id: u32,
pub player1: ContractAddress,
pub player2: ContractAddress,
pub current_turn: ContractAddress,
pub status: GameStatus,
}
```
#### Plan Your Systems (Actions)
What actions can players take?
```cairo
#[dojo::interface]
trait IGameActions {
fn create_game(ref world: IWorldDispatcher, opponent: ContractAddress);
fn play_card(ref world: IWorldDispatcher, game_id: u32, card_id: u32);
fn end_turn(ref world: IWorldDispatcher, game_id: u32);
}
```
### Writing Cairo Code
#### Start with Models
Begin by implementing your data models in `src/models.cairo`:
```cairo
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Player {
#[key]
pub address: ContractAddress,
pub name: ByteArray,
pub wins: u32,
pub losses: u32,
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Inventory {
#[key]
pub player: ContractAddress,
pub cards: Array,
}
```
#### Implement Systems Incrementally
Add one system at a time in `src/actions.cairo`:
```cairo
#[dojo::contract]
mod actions {
use super::{Player, Inventory};
#[abi(embed_v0)]
impl ActionsImpl of super::IGameActions {
fn create_player(ref world: IWorldDispatcher, name: ByteArray) {
let caller = get_caller_address();
// Check if player already exists
let existing_player = get!(world, caller, Player);
assert(existing_player.address.is_zero(), 'Player already exists');
// Create new player
set!(world, Player {
address: caller,
name: name,
wins: 0,
losses: 0,
});
// Initialize empty inventory
set!(world, Inventory {
player: caller,
cards: array![],
});
}
}
}
```
#### Compile Early and Often
After adding each piece, compile to catch errors:
```bash
sozo build
```
Fix any compilation errors before moving on to the next feature.
### Testing Your Code
#### Local Testing Workflow
1. **Deploy your changes**:
```bash
sozo migrate --dev
```
2. **Test basic functionality**:
```bash
# Create a player
sozo execute create_player --calldata "Alice" --dev
# Verify it worked
sozo model get Player $ACCOUNT_ADDRESS --dev
```
3. **Test edge cases**:
```bash
# Try to create the same player again (should fail)
sozo execute create_player --calldata "Alice" --dev
```
#### Using GraphQL for Complex Queries
Test your data relationships using Torii's GraphQL interface at `http://localhost:8080/graphql`:
```graphql
query GetPlayersWithInventories {
playerModels {
edges {
node {
address
name
wins
losses
}
}
}
inventoryModels {
edges {
node {
player
cards
}
}
}
}
```
#### Automated Testing
For more comprehensive testing, use Sozo's built-in test framework:
```bash
# Run all tests
sozo test
# Run tests with output
sozo test -v
# Run specific test
sozo test test_create_player
```
Create tests in your `src/` directory:
```cairo
#[cfg(test)]
mod tests {
use super::{Player, actions};
use dojo::test_utils::{spawn_test_world, deploy_contract};
#[test]
fn test_create_player() {
let world = spawn_test_world!();
let contract_address = deploy_contract!(world, actions);
let actions_dispatcher = IGameActionsDispatcher { contract_address };
// Test player creation
actions_dispatcher.create_player("Alice");
let player = get!(world, get_caller_address(), Player);
assert(player.name == "Alice", 'Wrong name');
assert(player.wins == 0, 'Wrong wins');
}
}
```
### Debugging and Troubleshooting
#### Common Development Issues
##### Compilation Errors
```bash
# Error: trait has no implementation
# Solution: Make sure all traits are properly implemented
sozo build 2>&1 | grep -A 5 "error"
```
##### Transaction Failures
```bash
# Check recent transactions
sozo events --dev
# Get detailed transaction info
sozo call get_transaction --calldata $TX_HASH --dev
```
##### Data Not Updating
```bash
# Restart Torii to refresh indexing
pkill torii
torii --dev
```
#### Debugging Techniques
##### 1. Use Events for Logging
Add events to your systems for debugging:
```cairo
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct PlayerCreated {
#[key]
pub player: ContractAddress,
pub name: ByteArray,
}
// In your system:
emit!(world, PlayerCreated { player: caller, name: name });
```
##### 2. Check State Step by Step
After each operation, verify the state:
```bash
# Execute action
sozo execute create_player --calldata "Bob" --dev
# Check immediate result
sozo model get Player $ACCOUNT_ADDRESS --dev
# Check related data
sozo model get Inventory $ACCOUNT_ADDRESS --dev
```
##### 3. Use Verbose Logging
Enable detailed logging on your tools:
```bash
# Katana with debug info
katana --dev -vvv
# Torii with debug logging
torii --dev --log-level debug
```
### Iterating on Features
#### Feature Development Pattern
1. **Implement minimal version**:
```cairo
fn attack(ref world: IWorldDispatcher, target: ContractAddress) {
// Basic implementation
let attacker = get_caller_address();
// TODO: Add damage calculation
// TODO: Add health reduction
// TODO: Add death handling
}
```
2. **Test basic functionality**:
```bash
sozo execute attack --calldata $TARGET_ADDRESS --dev
```
3. **Add complexity incrementally**:
```cairo
fn attack(ref world: IWorldDispatcher, target: ContractAddress) {
let attacker = get_caller_address();
let attacker_stats = get!(world, attacker, CombatStats);
let mut target_stats = get!(world, target, CombatStats);
// Calculate damage
let damage = attacker_stats.attack - target_stats.defense;
if damage > 0 {
target_stats.health -= damage;
set!(world, target_stats);
emit!(world, AttackExecuted {
attacker,
target,
damage
});
}
}
```
4. **Test edge cases**:
```bash
# Test with different scenarios
sozo execute attack --calldata $WEAK_TARGET --dev
sozo execute attack --calldata $STRONG_TARGET --dev
sozo execute attack --calldata $DEAD_TARGET --dev
```
#### Managing Code Changes
When modifying existing models, you might need to reset your local state:
```bash
# Clean rebuild
sozo clean
sozo build
sozo migrate --dev
# If you have persistent data you want to keep:
# Back up important state first
sozo model get Player $ACCOUNT_ADDRESS --dev > player_backup.json
```
### Performance Optimization
#### Efficient Data Queries
Structure your GraphQL queries to minimize data transfer:
```graphql
# Good: Specific fields only
query GetActiveGames {
gameModels(where: {status: {eq: "ACTIVE"}}) {
edges {
node {
id
player1
player2
current_turn
}
}
}
}
# Avoid: Fetching all data
query GetAllGames {
gameModels {
edges {
node {
# all fields...
}
}
}
}
```
#### Gas Optimization
Keep transactions lightweight:
```cairo
// Good: Single batch operation
fn play_multiple_cards(ref world: IWorldDispatcher, cards: Array) {
let mut i = 0;
loop {
if i >= cards.len() {
break;
}
// Process card
i += 1;
};
}
// Less efficient: Multiple separate transactions
// (would require multiple sozo execute calls)
```
### Deployment Strategy
#### Local to Testnet Progression
1. **Perfect locally**:
```bash
# Ensure everything works
sozo migrate --dev
# Test all features thoroughly
```
2. **Deploy to Sepolia**:
```bash
# Configure Sepolia profile in dojo_dev.toml
sozo migrate --profile sepolia
```
3. **Test on testnet**:
```bash
# Use real testnet for final validation
sozo execute create_player --calldata "TestUser" --profile sepolia
```
4. **Deploy to mainnet** (when ready):
```bash
sozo migrate --profile mainnet
```
### Development Best Practices
#### Keep Your Workspace Organized
```
my-game/
├── src/
│ ├── models/ # Separate model files
│ │ ├── player.cairo
│ │ ├── game.cairo
│ │ └── card.cairo
│ ├── systems/ # Separate system files
│ │ ├── player_actions.cairo
│ │ ├── game_actions.cairo
│ │ └── card_actions.cairo
│ └── lib.cairo # Main exports
├── tests/ # Dedicated test directory
├── scripts/ # Utility scripts
└── docs/ # Game design documents
```
#### Version Control Best Practices
Commit frequently with meaningful messages:
```bash
git add -A
git commit -m "Add player creation system with basic validation"
# Before major changes
git branch feature/card-trading
git checkout feature/card-trading
```
#### Documentation
Keep a development log of major decisions:
```markdown
# Development Log
## 2024-01-15: Player System
- Added basic player creation
- Implemented inventory management
- Next: Add card trading mechanics
## 2024-01-16: Combat System
- Added basic attack/defense
- Issue: Need to handle death states
- TODO: Add resurrection mechanics
```
### Quick Reference: Development Commands
#### Starting Fresh
```bash
# Terminal 1: Start blockchain
katana --dev
# Terminal 2: Build and deploy
sozo build && sozo migrate
# Terminal 3: Start indexer
torii --dev
```
#### Making Changes
```bash
# After editing contracts
sozo build && sozo migrate
# After changing configuration
# Restart the affected service
```
#### Testing Your Changes
```bash
# Execute a system to test
sozo execute di-actions spawn
# Query model data
sozo model get Position
# Check events
sozo events
```
#### Common Issues
```bash
# Clean rebuild
sozo clean && sozo build && sozo migrate
# Check service status
curl http://localhost:5050 # Katana
curl http://localhost:8080 # Torii
# Restart development environment
pkill katana && pkill torii
# Then restart services
```
### Next Steps
You now have a solid understanding of the Dojo development workflow!
For detailed information about each tool and its configuration options, see [Understanding the Toolchain](./understanding-the-toolchain).
Ready to explore what comes next in your journey?
Continue to [Next Steps](./next-steps) to discover advanced topics, deployment strategies, and community resources.
## Getting Started with Dojo
Welcome to Dojo!
This learning path will guide you from your first "Hello World" to building and deploying your own provable games and autonomous worlds on Starknet.
Dojo is a comprehensive toolchain for building fully onchain applications using the Entity Component System (ECS) architecture.
Whether you're new to blockchain development or an experienced smart contract developer, these tutorials will help you understand Dojo's unique approach to onchain applications.
### Learning Path Overview
Follow these tutorials in order to build your understanding progressively:
#### 1. [Your First Dojo App](/getting-started/your-first-dojo-app)
**Time: 20-30 minutes** | **Prerequisites: None**
Create and deploy your first Dojo project using the spawn-and-move example.
You'll learn:
* How to set up a new Dojo project
* Basic ECS concepts (Entities, Components, Systems)
* Running local development tools (Katana, Torii)
* Deploying your first World
#### 2. [Understanding the Toolchain](/getting-started/understanding-the-toolchain)
**Time: 15-20 minutes** | **Prerequisites: First Dojo App**
Explore the tools that make Dojo development possible.
You'll understand:
* **Katana**: Local Starknet sequencer for development
* **Torii**: Indexing engine for efficient data queries
* **Sozo**: CLI tool for project management and deployment
* How these tools work together in your development workflow
#### 3. [Development Workflow](/getting-started/development-workflow) Coming soon!
**Time: 25-35 minutes** | **Prerequisites: Understanding the Toolchain**
Learn the typical development process for Dojo projects.
You'll practice:
* Writing and testing new Systems
* Adding and modifying Components
* Using Sozo for common development tasks
* Debugging and iterating on your application
#### 4. [Next Steps](/getting-started/next-steps) Coming soon!
**Time: 10 minutes** | **Prerequisites: Development Workflow**
Discover where to go next in your Dojo journey.
You'll explore:
* Advanced tutorials and projects
* Client SDK integration options
* Community resources and support
* Deployment to testnet and mainnet
### What You'll Build
Throughout this learning path, you'll work with a simple but complete game where a player can:
* Spawn characters in a virtual world
* Move characters around using directional commands
* Subscribe to UI updates through GraphQL
This foundation demonstrates all core Dojo concepts while remaining simple enough to understand quickly.
### Prerequisites
Before starting, ensure you have:
* **Dojo installed**: Follow the [installation guide](/installation) if you haven't already
* **Basic command line knowledge**: You'll be running terminal commands
* **Text editor**: Any editor that supports Cairo syntax highlighting (VS Code recommended)
> **Note**: No prior blockchain or Cairo experience is required.
> We'll explain concepts as we go.
### Getting Help
If you get stuck during these tutorials:
* Check the [FAQ](/faq) for common issues
* Join our [Discord community](https://discord.gg/dojoengine) for real-time help
* Browse the [reference documentation](/framework) for detailed explanations
### Ready to Start?
Let's begin with [your first Dojo app](/getting-started/your-first-dojo-app) and get your development environment running!
## Next Steps
Congratulations! You've completed the getting started path and now have a solid foundation in Dojo development.
Here's your roadmap to becoming a proficient Dojo developer and building production-ready applications.
### What You've Accomplished
Through this learning path, you've:
* ✅ Built and deployed your first Dojo application
* ✅ Understood the ECS architecture and Dojo's toolchain
* ✅ Learned the development workflow and best practices
* ✅ Experienced the complete cycle from code to deployment
You're now ready to tackle more advanced topics and build more complex applications.
### Choose Your Path Forward
Depending on your goals and experience level, here are the recommended next steps:
#### 🎮 For Game Developers
##### Intermediate Game Development
Start with these tutorials to build more complex games:
1. **Multiplayer Game Tutorial** (Coming Soon)
* Real-time player interactions
* State synchronization patterns
* Handling concurrent actions
2. **Complex Systems Tutorial** (Coming Soon)
* Advanced ECS patterns
* System interdependencies
* Performance optimization
3. **NFT Integration** (Coming Soon)
* Token-gated gameplay
* Asset ownership mechanics
* Cross-game interoperability
#### 🛠️ For Application Developers
##### Client Integration
Connect your Dojo world to user interfaces:
1. **[JavaScript SDK](/client/sdk/javascript/)**
* React applications with real-time updates
* Web-based game clients
* Mobile web applications
2. **[Unity Integration](/client/sdk/unity)**
* Desktop and mobile game clients
* Cross-platform development
* Native game engine integration
3. **[Other Platforms](/client/sdk/)**
* Unreal Engine, Godot, Bevy
* Custom C/C++ applications
* Telegram and Discord bots
#### 🚀 For Production Deployment
##### Deployment and Infrastructure
Take your applications live:
1. **[Deploy to Mainnet](/tutorials/deploy-to-mainnet/main)**
* Production deployment checklist
* Security considerations
* Cost optimization
2. **[Deploy using Slot](/tutorials/deploy-using-slot/main)**
* Managed deployment platform
* Simplified infrastructure
* Monitoring and analytics
3. **[Custom Infrastructure](/toolchain/katana/)**
* Self-hosted deployment
* Custom Katana configurations
* Advanced Torii setups
##### Scaling and Performance
Handle production workloads:
* **[Execution Sharding](/scaling/execution-sharding)**: Scale beyond single chain limits
* **[Sovereign Rollups](/scaling/sovereign-rollups)**: Custom execution environments
* **[Indexer Optimization](/toolchain/torii/)**: Efficient data access patterns
#### 📚 For Framework Contributors
##### Contributing to Dojo
Help improve the ecosystem:
1. **Code Contributions** (Coming Soon)
* Core framework development
* Tool improvements
* Bug fixes and optimizations
2. **Documentation** (Coming Soon)
* Tutorial writing
* API documentation
* Example applications
3. **Community Support**
* Discord participation
* Forum discussions
* Mentoring newcomers
### Recommended Learning Projects
#### Beginner Projects (1-2 weeks each)
1. **Turn-Based Strategy Game**
* Grid-based movement
* Resource management
* AI opponents
2. **Trading Card Game**
* Deck building mechanics
* Card effects and abilities
* Tournament system
3. **Virtual Pet Game**
* Pet care mechanics
* Evolution systems
* Social features
#### Intermediate Projects (2-4 weeks each)
1. **Decentralized Exchange**
* Order book mechanics
* Automated market making
* Liquidity provision
2. **Battle Royale Game**
* Large-scale multiplayer
* Shrinking play area
* Real-time combat
3. **City Builder**
* Complex resource chains
* Player interactions
* Economic simulation
#### Advanced Projects (1-3 months each)
1. **MMORPG Foundation**
* Persistent world state
* Character progression
* Guild systems
2. **Cross-Chain Protocol**
* Multi-world interactions
* Asset bridging
* Unified identity
3. **Autonomous World Platform**
* User-generated worlds
* Modding systems
* Governance mechanisms
### Essential Resources
#### Community and Support
Stay connected and get help:
* **[Discord Community](https://discord.gg/dojoengine)**: Real-time discussions and support
* **[GitHub Repository](https://github.com/dojoengine/dojo)**: Latest code and issues
* **Community Showcase**: Learn from other projects (Coming Soon)
#### Tools and Libraries
Extend your development capabilities:
* **[Origami Library](/libraries/origami/)**: Common game mechanics
* **[Alexandria Library](/libraries/alexandria/)**: Data structures and algorithms
* **Community Libraries**: Third-party extensions (Coming Soon)
### Setting Up for Advanced Development
#### Enhanced Development Environment
Create a more sophisticated setup:
```bash
# Project structure for larger applications
mkdir my-advanced-game && cd my-advanced-game
# Initialize with custom structure
sozo init . --template minimal
mkdir -p src/{models,systems,tests,utils}
mkdir -p client/{web,mobile}
mkdir -p docs/{design,api}
```
#### Development Scripts
Create helper scripts for common workflows:
```bash
# scripts/dev-setup.sh
#!/bin/bash
echo "🚀 Starting development environment..."
katana --dev &
sleep 3
sozo migrate --dev
torii --dev &
echo "✅ Ready for development!"
# scripts/test-all.sh
#!/bin/bash
echo "🧪 Running comprehensive tests..."
sozo test
# Add integration tests
# Add performance benchmarks
```
#### Advanced Tooling
Set up additional development tools:
* **Code formatters**: Scarb + Cairo formatter
* **VS Code extensions**: Cairo language support
* **Git hooks**: Pre-commit testing and formatting
* **CI/CD pipelines**: Automated testing and deployment
### Staying Current
The Dojo ecosystem evolves rapidly.
Stay updated:
#### Regular Updates
* **Monthly**: Check for new Dojo releases
* **Weekly**: Read community updates and discussions
* **Daily**: Monitor Discord for breaking changes during development
#### Best Practices
* **Version pinning**: Lock Dojo versions in production
* **Migration guides**: Follow upgrade paths carefully
* **Breaking changes**: Test thoroughly before updating
#### Community Engagement
* **Share your projects**: Show off what you're building
* **Ask questions**: The community is helpful and welcoming
* **Contribute back**: Help others learn and improve the ecosystem
### Getting Unstuck
When you encounter challenges:
#### Technical Issues
1. **Check the [FAQ](/faq)** for common problems
2. **Search [GitHub issues](https://github.com/dojoengine/dojo/issues)** for similar problems
3. **Ask in [Discord](https://discord.gg/dojoengine)** with specific error messages
4. **Review troubleshooting guides** for systematic debugging (Coming Soon)
#### Design Decisions
1. **Study existing games** in the showcase (Coming Soon)
2. **Read best practices guides** (Coming Soon)
3. **Discuss architecture** with the community
4. **Prototype quickly** to test ideas
#### Learning Gaps
1. **Practice with [tutorials](/tutorials)** at your level
2. **Study reference documentation** for detailed APIs (Coming Soon)
3. **Build small projects** to reinforce learning
### Your Journey Continues
Building with Dojo is a journey, not a destination.
The ecosystem is rapidly evolving, with new patterns, tools, and possibilities emerging regularly.
Whether you're building the next viral onchain game, creating innovative DeFi mechanisms, or pushing the boundaries of autonomous worlds, you now have the foundation to succeed.
**Ready to build something amazing?**
* Join our [Discord community](https://discord.gg/dojoengine) to connect with other builders
* Explore the [tutorials section](/tutorials) for your next learning challenge
* Check out the ecosystem showcase for inspiration (Coming Soon)
Welcome to the future of onchain applications.
Let's build something incredible together! 🚀
## Understanding the Toolchain
Now that you've built your first Dojo app, let's explore the tools that made it possible.
Understanding each tool's role will help you develop more effectively and debug issues when they arise.
:::note
The code examples are run in the context of the `dojo-intro` repo.
:::
### Katana: Your Local Starknet
**What it is**: Katana is a high-performance Starknet sequencer designed for development.
**What it does**:
* Provides a local sequencer environment
* Mines blocks instantly for fast development
* Comes with pre-funded accounts for testing
* Supports gRPC for faster network communication
#### Key Features
When you run `katana --dev`, you get:
```
██╗ ██╗ █████╗ ████████╗ █████╗ ███╗ ██╗ █████╗
██║ ██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗ ██║██╔══██╗
█████╔╝ ███████║ ██║ ███████║██╔██╗ ██║███████║
██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║
██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝
PREDEPLOYED CONTRACTS
==================
| Contract | ETH Fee Token
| Address | 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
| Class Hash | 0x00a2475bc66197c751d854ea8c39c6ad9781eb284103bcd856b58e6b500078ac
...
PREFUNDED ACCOUNTS
==================
| Account address | 0x127fd5f1fe78a71f8bcd1fec63e3fe2f0486b6ecd5c86a0466c3a21fa5cfcec
| Private key | 0xc5b2fcab997346f3ea1c00b002ecf6f382c5f9c9659a3894eb783c5320f912
| Public key | 0x33246ce85ebdc292e6a5c5b4dd51fab2757be34b8ffda847ca6925edf31cb67
...
ACCOUNTS SEED
=============
0
2025-07-10T19:40:46.325567Z INFO katana_node: Starting node. chain=0x4b4154414e41
2025-07-10T19:40:46.325662Z INFO rpc: RPC server started. addr=127.0.0.1:5050
```
:::note
Katana's chain id, `0x4b4154414e41`, is just "KATANA" in ASCII!
:::
#### Development vs Production
**Development mode** (`--dev`):
* Instant block mining
* Pre-funded accounts
* No fees or account validation
**Production mode**:
* Configurable mining intervals
* Can fork existing chains
* Can persist state to a database
#### Katana Configuration
Customize Katana's behavior:
```bash
# Start in development mode without fees
katana --dev --dev.no-fee
# Automatically deploy the Controller contract
katana --cartridge.controllers
# Fork off of a specific chain & block
katana --rpc-url --fork-block-number
```
### Torii: Your Data Layer
**What it is**: Torii is an automatic indexing engine that makes your World's data easily queryable.
**What it does**:
* Introspects and monitors all events from your World
* Builds a searchable database of application state
* Provides flexible GraphQL and gRPC APIs
* Enables real-time client subscriptions
#### How Torii Works
1. **Event Listening**: Monitors your World for state changes
2. **Data Processing**: Converts Cairo events into structured data
3. **Database Storage**: Maintains a SQL database of current state
4. **API Generation**: Automatically creates GraphQL schemas
5. **Real-time Updates**: Pushes changes to connected clients
#### Using Torii's APIs
After starting `torii --world `, you can access data through multiple interfaces.
Let's make a query to retrieve all the instances of the `Position` model.
##### GraphiQL (Browser)
Visit `http://localhost:8080/graphql` for an interactive query interface:
```graphql
query {
diPositionModels {
edges {
node {
player
x
y
}
}
}
}
```
##### GraphQL (Programmatic)
```bash
curl -X POST http://localhost:8080/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ diPositionModels { edges { node { player x y } } } }"}'
```
:::tip
You can learn more about GraphQL [here](https://graphql.org/learn).
:::
##### gRPC
For high-performance applications, use the gRPC endpoint at `localhost:8080`.
#### Torii Configuration
Customize Torii's behavior:
```bash
# Set the RPC endpoint to index (default localhost:5050)
torii --rpc
# Path to persistent storage (recommended for production)
torii --db-dir torii.db
# Enable CORS for local development
torii --http.cors_origins "*"
```
### Sozo: Your Command Center
**What it is**: Sozo is the command-line interface for managing Dojo projects.
**What it does**:
* Initializes new projects
* Compiles Cairo code
* Deploys contracts
* Manages World interactions
* Handles authorization
> **New to Sozo?** See our [complete Sozo reference](/toolchain/sozo/) for all available commands and options.
#### Project Lifecycle with Sozo
##### 1: Project Creation
```bash
# Clone the template into the my-game directory
sozo init my-game --template dojoengine/dojo-starter (default)
```
##### 2. Development
```bash
# Compile your code (defaults to dev profile)
sozo build
# Compile your code with a different profile
sozo build --profile staging
# Run tests
sozo test
# Clean build artifacts
sozo clean
```
:::info
To build new profiles, add `[profile.]` to your project's `Scarb.toml`.
:::
##### 3. Deployment
```bash
# Deploy to local Katana (defaults to dev profile)
sozo migrate
# Deploy to a staging environment
sozo migrate --profile staging
# Deploy to a production environment
sozo migrate --profile mainnet
```
**Expected output for local build and deployment:**
```
# sozo build
Compiling dojo_intro v1.5.0
Finished `dev` profile target(s) in 8 seconds
# sozo migrate
🌍 World deployed at block 2 with txn hash: 0x06a03...
⛩️ Migration successful with world at address 0x04d97...
```
:::info
To define new profiles, create a `dojo_.toml` file in your contract directory.
:::
:::warning
You **must** have a `dojo_dev.toml` file in your contract directory, or Sozo will complain.
:::
After migrating, Sozo will generate a `manifest_.json` file, which contains information about the deployed contracts and interfaces.
This file is meant to be consumed by clients to help them interact with the world.
##### 4. Interaction
```bash
# Execute a system (note the namespacing)
sozo execute di-actions spawn
# Query historical events
sozo events
# Query model data (get keys from events)
sozo model get Position
```
#### Sozo Profiles
Manage different deployment environments with profiles:
```
contracts/
├── dojo_dev.toml
├── dojo_staging.toml
├── Scarb.toml
└── ...
```
Each profile defines basic data for a deployment:
```toml
[world]
name = "Dojo intro"
description = "A simple intro to Dojo."
seed = "123" # Changes the World address
[namespace]
default = "di" # Default namespace for the world
[env]
rpc_url = "http://localhost:5050/" # Katana instance
account_address = "0x127f..." # Deployer address
private_key = "0xc5b2..." # Deployer private key
world_address = "0x04d9..." # For previously deployed worlds
[writers]
"di" = ["di-actions"] # target = grantee
```
See [this page](/framework/configuration) for more information about Sozo profiles.
### Saya: Your Proof Generator
**What it is**: Saya generates cryptographic proofs of your World's execution.
**What it does**:
* Creates STARK proofs of game state transitions
* Enables provable gaming and autonomous worlds
* Supports different proving backends
* Handles proof verification and settlement
:::tip
You **do not** need Saya for basic Dojo development.
:::
#### When You Need Saya
Saya becomes important when you want:
* **Provable fairness**: Public guarantees of correct execution
* **L1 settlement**: Settle state changes on Ethereum or other chains
* **Competition integrity**: Tamper-proof tournaments and rankings
For more information about Saya, [see the docs](/toolchain/saya).
### How The Toolchain Works Together
Here's the typical development flow:
1. **Sozo** compiles your Cairo code into deployable contracts
2. **Katana** provides a local sequencer to deploy and test on
3. **Sozo** deploys your World to Katana
4. **Torii** automatically indexes your World's data
5. **Saya** (optionally) generates proofs for settlement
### Tool Configuration
Each tool can be configured through various methods:
#### Configuration Priority (highest to lowest)
1. Command-line flags
2. Environment variables
3. Configuration files
4. Default values
#### Common Configuration Files
* `dojo_dev.toml`: Sozo project and World settings
* `torii_dev.toml`: Torii indexing and World settings
* `katana.toml`: Katana network configuration settings
### Development Best Practices
#### Keep Tools Running
During development, keep all tools running in separate terminals:
```bash
# Terminal 1
katana --config katana.toml
# Terminal 2
torii --config torii_dev.toml
# Terminal 3
# Your sozo commands
```
#### Use Profiles
Set up different profiles for different environments:
```bash
# Development
sozo execute di-actions spawn
# Testing on staging
sozo execute di-actions spawn --profile staging
```
### Troubleshooting Common Issues
#### Katana Won't Start
* Verify your `katana.toml` configuration is correct
* Check if the port is in use (`lsof -i :5050`)
#### Torii Can't Connect
* Verify the World address in `torii_dev.toml` matches your migration output
* Check that the World was successfully deployed (`sozo inspect`)
#### Sozo Commands Fail
* Verify you're in a Dojo project directory (look for `Scarb.toml`)
* Check that you're using the correct account address and private key
### Next Steps
Now that you understand the toolchain, you're ready to learn about the [development workflow](/getting-started/development-workflow) and start building more complex applications.
Each tool has extensive documentation in the toolchain section for when you need to dive deeper into specific features and configurations.
## Your First Dojo App
In this tutorial, you'll create your first Dojo project from scratch and deploy it locally.
By the end, you'll have a working game where you can spawn and move a character using a browser-based client.
### What You'll Learn
* The structure of a Dojo project
* Basic Entity Component System (ECS) concepts
* How to start the development environment
* Deploying your first World
* Interacting with your deployed game
### Step 1: Create Your Project
First, clone the [Dojo Intro](https://github.com/dojoengine/dojo-intro) project, which implements a simple spawn-and-move game:
```bash
git clone https://github.com/dojoengine/dojo-intro && cd dojo-intro
```
Now open up the repo in your code editor and explore the project:
```
dojo-intro/
├── client/
├── contracts/
│ ├── src/
│ │ ├── systems/ # Game logic (Systems)
│ │ │ └── actions.cairo
│ │ ├── lib.cairo # Main Cairo entry point
│ │ └── models.cairo # Data models (Components)
│ ├── dojo_dev.toml # Dojo configuration
│ ├── katana.toml # Katana configuration
│ ├── Scarb.toml # Cairo configuration
│ └── torii_dev.toml # Torii configuration
└── README.md # Project readme
```
> Note: Some files and directories are omitted for simplicity
### Step 2: Understand the Contract Structure
Let's examine the key `contracts/` files to understand what we're building:
#### Models (Entities & Components)
Look at `contracts/src/models.cairo` to see the data structures:
```cairo
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Position {
#[key]
pub player: ContractAddress,
pub x: u32,
pub y: u32,
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Moves {
#[key]
pub player: ContractAddress,
pub remaining: u8,
}
```
These models define:
* **Position**: Where each player is located (x, y coordinates)
* **Moves**: How many moves each player has remaining
The `#[dojo::model]` attribute tells Cairo that the struct is a Dojo model.
The `#[key]` attribute tells Cairo that `player` is the Dojo entity identifier for these components.
:::info
These attributes are examples of "procedural macros," a [Cairo language feature](https://www.starknet.io/cairo-book/ch12-10-procedural-macros.html) which tells the compiler to generate additional code at compile-time.
This additional code is what enables Dojo functionality, such as specialized storage formats or events for the Torii indexer.
:::
#### Contracts (Systems)
Open `contracts/src/systems/actions.cairo` to see the game logic:
```cairo
#[starknet::interface]
pub trait IActions {
fn spawn(ref self: T);
fn move(ref self: T, direction: Direction);
fn move_random(ref self: T);
}
#[dojo::contract]
pub mod actions {
// Implementation details...
fn spawn(ref self: ContractState) {
// Create a new player with starting position and moves
}
fn move(ref self: ContractState, direction: Direction) {
// Move the player and decrease remaining moves
}
fn move_random(ref self: ContractState) {
// Move the player randomly and decrease remaining moves
}
}
```
These functions define the game's core actions:
* **spawn**: Create a new player at the starting position
* **move**: Move a player in a direction (up, down, left, right)
* **move\_random**: Use the [Cartridge vRNG](https://docs.cartridge.gg/slot/vrng) to move in a random direction
#### Project Configuration
Open `contracts/dojo_dev.toml` to see the project's Dojo configuration:
```toml
[world]
name = "Dojo intro"
... additional project details
[namespace]
default = "di"
[env]
rpc_url = "http://localhost:5050/"
... additional environment details
[writers]
"di" = ["di-actions"]
```
This configuration tells Dojo how to organize permissions when deploying the world.
Specifically, it defines `di` as the default namespace and permits the systems defined in `actions.cairo` to write game state.
For more details about configuration options, see the [configuration guide](/framework/configuration).
### Step 3: Start the Development Environment
Now let's get the development tools running.
You'll run three tools simultaneously, each in its own terminal:
| Tool | Purpose | Terminal |
| ---------- | ------------------------------- | ---------- |
| **Katana** | Local blockchain sequencer | Terminal 1 |
| **Sozo** | Build and deploy your contracts | Terminal 2 |
| **Torii** | Index and serve game data | Terminal 3 |
#### Terminal 1: Start Katana (Local Blockchain)
First, start [Katana](/toolchain/katana), Dojo's high-performance sequencer:
```bash
cd contracts && katana --config katana.toml
```
*This creates a fast, local Starknet environment for development*
You'll see output like this:
```
2025-07-10T15:46:43.495560Z INFO katana_core::backend: Genesis initialized
██╗ ██╗ █████╗ ████████╗ █████╗ ███╗ ██╗ █████╗
██║ ██╔╝██╔══██╗╚══██╔══╝██╔══██╗████╗ ██║██╔══██╗
█████╔╝ ███████║ ██║ ███████║██╔██╗ ██║███████║
██╔═██╗ ██╔══██║ ██║ ██╔══██║██║╚██╗██║██╔══██║
██║ ██╗██║ ██║ ██║ ██║ ██║██║ ╚████║██║ ██║
╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝╚═╝ ╚═╝
PREDEPLOYED CONTRACTS
==================
| Contract | ETH Fee Token
| Address | 0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7
| Class Hash | 0x00a2475bc66197c751d854ea8c39c6ad9781eb284103bcd856b58e6b500078ac
...
PREFUNDED ACCOUNTS
==================
| Account address | 0x127fd5f1fe78a71f8bcd1fec63e3fe2f0486b6ecd5c86a0466c3a21fa5cfcec
| Private key | 0xc5b2fcab997346f3ea1c00b002ecf6f382c5f9c9659a3894eb783c5320f912
| Public key | 0x33246ce85ebdc292e6a5c5b4dd51fab2757be34b8ffda847ca6925edf31cb67
...
ACCOUNTS SEED
=============
0
2025-07-10T15:46:43.496299Z INFO katana_node: Starting node. chain=0x4b4154414e41
2025-07-10T15:46:43.496406Z INFO rpc: RPC server started. addr=127.0.0.1:5050
```
Keep this running - it's your local sequencer environment.
:::tip
By default, Katana runs on port 5050
:::
#### Terminal 2: Deploy Your World
In a new terminal, run the following command using [Sozo](/toolchain/sozo), Dojo's build tool.
```bash
cd contracts && sozo build && sozo migrate
```
*This compiles your Cairo contracts and deploys them to the local sequencer*
**Expected output:**
```
# sozo build
Compiling dojo_intro v1.5.0 (~/dojo-intro/contracts/Scarb.toml)
Finished `dev` profile target(s) in 8 seconds
# sozo migrate
profile | chain_id | rpc_url
---------+----------+------------------------
dev | KATANA | http://localhost:5050/
🌍 World deployed at block 2 with txn hash: 0x06a03fcc52739807ff4e7cd96fceb40b8fc194f3d7b370c1c8388eb380a4a069
⛩️ Deploying 0 external contracts...
⛩️ Migration successful with world at address 0x04d9778a74d2c9e6e7e4a24cbe913998a80de217c66ee173a604d06dea5469c3
```
#### Terminal 3: Start Torii (Indexer)
In a third terminal you'll start Torii, Dojo's indexer:
```bash
cd contracts && torii --config torii_dev.toml
```
*This watches your blockchain and creates queryable APIs for your game data*
:::note
If you look at `torii_dev.toml`, you'll see the same world address as that generated by `sozo migrate`.
Sozo migrations are deterministic based on the seed defined in `dojo_dev.toml`.
If you change the seed, the world address will change.
:::
Torii will start indexing your world's data.
You'll see:
```
2025-07-10T15:59:27.961623Z INFO torii::relay::server: Relay peer id. peer_id=12D3KooWLaZ5bummiPSM2HHtRahELYUFWQA12NWNeMQ4aYpVhk9h
2025-07-10T15:59:27.966166Z INFO torii:runner: Starting torii endpoint. endpoint=127.0.0.1:8080
2025-07-10T15:59:27.966177Z INFO torii:runner: Serving Graphql playground. endpoint=127.0.0.1:8080/graphql
2025-07-10T15:59:27.966202Z INFO torii:runner: Serving SQL playground. endpoint=127.0.0.1:8080/sql
2025-07-10T15:59:27.966207Z INFO torii:runner: Serving MCP endpoint. endpoint=127.0.0.1:8080/mcp
2025-07-10T15:59:27.966210Z INFO torii:runner: Serving World Explorer. url=https://worlds.dev/torii?url=127.0.0.1%3A8080%2Fgraphql
2025-07-10T15:59:27.966212Z INFO torii:runner: Serving ERC artifacts at path path=/var/folders/dh/khjwlwbd6tx9p785khl57xjr0000gn/T/.tmpqeRBF0
2025-07-10T15:59:27.967054Z INFO torii::relay::server: Serving libp2p Relay. address=/ip4/127.0.0.1/tcp/9090
2025-07-10T15:59:27.967228Z INFO torii::relay::server: Serving libp2p Relay. address=/ip4/127.0.0.1/udp/9090/quic-v1
2025-07-10T15:59:27.967369Z INFO torii::relay::server: Serving libp2p Relay. address=/ip4/127.0.0.1/udp/9091/webrtc-direct/certhash/uEiB-v7fMcMjwd5WT_kgzPYOETr_IcyJ91zKbpG3AilHV_w
2025-07-10T15:59:27.967637Z INFO torii::relay::server: Serving libp2p Relay. address=/ip4/127.0.0.1/tcp/9092/ws
2025-07-10T15:59:27.974035Z INFO torii::indexer::processors::register_model: Registered model. namespace=di name=Moves
2025-07-10T15:59:27.974166Z INFO torii::indexer::processors::register_model: Registered model. namespace=di name=Position
```
Reading this output, you'll see that Torii has set up some local endpoints, which the client will use to fetch game state.
You'll also see that Torii has registered the game models `Moves` and `Position` we saw earlier, within the `di` namespace.
:::tip
By default, Torii runs on port 8080
:::
Congratulations!
Your Dojo development environment is up-and-running.
### Step 4: Launch Your Game Client
Now let's test your game!
First, let's launch your game client:
```bash
cd client && pnpm install && pnpm run dev
```
You should see:
```
# pnpm install
# Installation details ...
Done in 409ms using pnpm v10.6.4
# pnpm run dev
> client@1.0.0 dev /Users/kronosapiens/code/cartridge/dojo-intro/client
> vite dev
VITE v6.3.4 ready in 152 ms
➜ Local: https://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help
```
Now navigate to [https://localhost:5173/](https://localhost:5173/) to reach the running client.
:::note
The Cartridge Controller *requires* https to connect.
:::
You should see a page which looks like this:

Click `Connect` to launch the Cartridge Controller log-in flow.
Once you have logged in with Controller, you should be able to spawn a character and move them around.
:::info
Take a moment to appreciate what's happening under the hood.
Every system execution is being sent by Controller as a transaction to Katana, which emits an event that is picked up by Torii, which is sent back to the client and rendered for you!
:::
After a while, you should have something which looks like this:

**Congratulations, you've just created your first provable game with Dojo!**
### Understanding What Happened
Let's break down what you just accomplished:
1. **Defined Components**: `Position` and `Moves` store data about each player
2. **Implemented Systems**: The `spawn` and `move` functions modify component data
3. **Created a World**: Your World acts as the central registry for all game data, logic, and access control
4. **Used the Toolchain**:
* Katana provided a local sequencer
* Sozo managed compilation and deployment
* Torii indexed data and sent it to the client
### Key Concepts
#### Entity Component System (ECS)
* **Entities**: Players in your game (identified by their address)
* **Components**: Data attached to entities (`Position`, `Moves`)
* **Systems**: Functions that operate on components (`spawn`, `move`)
#### Dojo World
Your World coordinates everything:
* Registers all models and systems
* Handles permissions and authorization
* Provides a consistent interface for data access
#### Event-Driven Updates
When you execute a system, it emits events that Torii captures and indexes, which clients can subscribe to.
### Next Steps
Congratulations!
You've successfully:
* ✅ Understood basic ECS concepts
* ✅ Created and deployed your first Dojo project
* ✅ Used the core development tools
* ✅ Interacted with your deployed world
Ready to dive deeper?
Continue to [Understanding the Toolchain](./understanding-the-toolchain) to learn more about the tools that make Dojo development possible.
When you're ready to explore the broader Dojo ecosystem, see [Next Steps](./next-steps) for advanced topics and deployment strategies.
## Execution Sharding on Starknet
:::note
The execution sharding architecture described in this document represents **planned functionality** currently under development.
While the design is established, the implementation is not yet available in production.
See the [Saya docs](/toolchain/saya/persistent) for more information.
:::
### Overview
Execution sharding enables isolated transaction execution within independent shard environments ("Layer 3"s).
Shards are independent sequencers that branch from Starknet mainnet, process transactions, then merge final state back to mainnet.
This enables parallel processing across multiple shards while maintaining unified state anchored to Starknet mainnet.
:::info
Execution sharding differs from [sovereign rollups](/scaling/sovereign-rollups), in that sovereign chains are long-lived and **do not** commit state back to Starknet.
:::
#### Key Benefits
* **Parallel Execution**: Multiple shards process transactions simultaneously
* **Cost Efficiency**: Only final state changes settle on mainnet
* **Low Latency**: Gasless, high-throughput execution environments
* **Unified State**: All shards merge back to single Starknet state
### Planned Architecture
The proposed execution sharding system involves five key components:
1. **Starknet Mainnet**: The settlement layer where Dojo worlds are deployed
2. **World Contract**: Receives and incorporates state changes from completed shards
3. **Katana**: Acts as the sequencer running individual shards
4. **Saya**: Generates proofs of shard execution and submits them to the World Contract
5. **DojoOS**: A specialized Cairo program generating execution traces for validation

### Proposed Shard Initialization
A shard begins when an initialization transaction is submitted to the world contract, specifying:
* Unique shard identifier
* Designated operator address
* Initial state conditions
* Settlement configuration for mainnet data merging
The operator then spins up a Katana instance, branching from Starknet mainnet to create the shard environment.
#### Planned Data Structure
The shard initialization would use a data structure similar to:
```rust
struct ShardStartData {
// Unique identifier for the shard
shard_id: felt252,
// Address of the shard operator running the sequencer
operator: felt252,
// Initial state configuration
initial_state: ShardInitialState {
// Contracts authorized for execution within the shard
contracts: Span,
// Event indicating shard completion
end_event: EventSelector {
// Contract address that will emit the completion event
from: felt252,
// Event selector to monitor
selector: felt252,
},
},
// Settlement configuration
settlement_config: SettlementConfig {
// Storage addresses to update on the World Contract
storage_addresses: Span,
},
}
```
#### Execution Environment
The shard branches from mainnet at the initialization block, then operates independently as a gasless environment.
Since contract logic is already defined on mainnet, shards execute without additional contract declarations while maintaining mainnet compatibility.
### Planned Execution Flow
Once initialized, the shard sequencer processes transactions within the isolated environment.
#### Access Control Options
1. **Contract Whitelisting**: Restrict execution to predefined contracts
2. **Account Whitelisting**: Allow only specific accounts to submit transactions
3. **Declaration Restrictions**: Prevent new contract declarations
4. **Permissionless Execution**: Rely on existing mainnet permissions
Access restrictions may be necessary to prevent DoS attacks, depending on use case and security requirements.

### Planned Completion Detection and Settlement
#### Completion Detection
A specific event emitted by a designated Cairo contract signals shard completion, indicating all intended transactions are processed and the shard is ready for settlement.
#### Proposed Settlement Pipeline
Once the completion event is detected, [Saya](/toolchain/saya) would initiate the settlement process:
1. **Execution Trace Generation**: DojoOS generates an execution trace similar to SNOS but optimized for shard validation:
* Validates transaction sequences rather than blocks
* Outputs only modified storage addresses per settlement config
* Supports shard-specific validation rules
2. **Proof Generation**: Saya submits the trace to a proving service (e.g., Atlantic) for cryptographic proof generation
3. **Layout Bridge**: Proof conversion to `recursive_with_poseidon` layout for Starknet verification
4. **Settlement**: Final proof and storage updates submitted to the world contract
:::note
The DojoOS component and shard settlement pipeline are currently in development.
The existing Saya proving service supports SNOS-based proving for standard Starknet blocks.
:::
### Proposed Concurrency Management with CRDTs
Concurrent shard execution requires managing consistent state across Starknet to prevent inconsistencies during settlement.
#### CRDT Integration
The Dojo team is exploring Conflict-Free Replicated Data Types (CRDTs) for concurrency management.
CRDTs enable concurrent updates across distributed systems without complex locking, with mathematical guarantees that changes converge to a valid global state during settlement.
#### Game Example: Dungeon Adventures
Consider a dungeon game where multiple players participate across different shards:
##### Experience Points (Grow-Only Counter)
XP can only increase, ideal for grow-only counter CRDTs.
Each shard independently awards XP; final XP is the sum across all shards.
##### Gold (Escrow-based System)
Gold can increase/decrease, requiring careful handling.
Player gold is "escrowed" (locked on mainnet) when entering a shard, preventing double-spending.
##### Unique Items (Lock-based Control)
Legendary items require exclusive access.
Items lock on mainnet when used in a shard, releasing when execution completes.
#### Planned CRDT Types
The research focuses on three CRDT patterns for initial implementation:
1. **Grow-Only Counters**: For monotonically increasing values
2. **Escrow Mechanisms**: For controlled resource spending
3. **Lock-based Controls**: For unique assets requiring exclusive access

### Current Limitations and Challenges
While execution sharding offers significant potential for scaling and parallel execution, several technical challenges must be addressed:
#### 1. Proving Time Bottleneck
Proof generation and verification is computationally intensive, potentially creating settlement delays and state update backlogs.
#### 2. All-or-Nothing Proof Validation
Invalid proofs require discarding entire shard execution, even if only a small portion of transactions were problematic.
#### 3. Limited Cross-Shard Communication
No direct communication between active shards restricts complex interactions requiring immediate state awareness across parallel executions.
#### 4. Implementation Status
The complete system remains in development:
* DojoOS Cairo program for shard validation
* CRDT integration for concurrency management
* Shard initialization and completion mechanisms
* Integration testing across all components
:::note
Current development focuses on the core proving infrastructure through Saya and SNOS integration.
The specialized DojoOS and shard management components are planned for future releases.
:::
## Sovereign Rollup Using Celestia and Dojo

### Introduction
This documentation outlines the design and implementation of sovereign rollups built with Celestia and Dojo's infrastructure stack.
Sovereign rollups provide scalable and decentralized execution by leveraging Celestia's modular data availability layer combined with Starknet's cryptographic proofs.
For alternative scaling approaches that maintain stronger connection to mainnet, see [execution sharding](/scaling/execution-sharding).
:::tip
See [Mage Duel](https://mageduel.evolute.network/) for an example of a game built using this architecture.
:::
#### Architecture Overview
Three components power the sovereign rollup:
* [**Katana**](/toolchain/katana): Sequences transactions into blocks
* [**Saya**](/toolchain/saya): Generates proofs from Katana blocks and posts to Celestia
* [**Celestia**](https://celestia.org/): Stores proofs as blobs with retrieval metadata
#### Key Features
* **State Reconstruction**: Full state rebuilding from latest proof
* **Proof Chaining**: Headers enable retrieval of previous proofs via commitment/height
* **Batch Aggregation**: Multiple blocks can be proven together for efficiency
:::note
This implementation shares conceptual similarities with **Rollkit**, another framework for building sovereign rollups using modular blockchain architecture.
The difference lies in the underlying infrastructure: this solution is built specifically around Dojo's proven stack (Katana and Saya), while Rollkit uses different technical components.
Both approaches enable developers to deploy customizable sovereign rollups with similar capabilities.
:::
### Technical Architecture

#### 1. Katana - Block Producer
Katana sequences transactions into blocks within the sovereign rollup.
##### Sync Mode
Katana reconstructs complete state from Celestia proofs:
* **State Recovery**: Rebuilds rollup state from latest Celestia proof
* **Requires**: Celestia height and commitment of latest proof
* **Process**: Retrieves prior proofs backward until full state reconstruction
* **Independence**: Nodes start/resume using only Celestia data
#### 2. Saya - Prover and Proof Poster
Saya generates proofs from Katana blocks and posts them to Celestia.
##### Workflow
* **Polling**: Actively retrieves new blocks from Katana
* **STARK Proving**: Generates cryptographic proofs of block validity and state updates
* **Posting**: Submits proofs to Celestia for storage
##### Proof Aggregation
* **Batching**: Combines multiple blocks into single proofs
* **Efficiency**: Reduces proving and storage costs
* **Scalability**: Handles higher transaction volumes
* **Completeness**: Maintains all state transition data
#### 3. Celestia - Data Availability
Celestia provides decentralized storage for rollup proofs.
##### Blob Storage
* **Proof Blobs**: Stores STARK proofs with retrieval metadata
* **Navigation**: Headers enable proof retrieval via commitment/height
* **Reconstruction**: State rebuilding by following proof chains
* **Decentralization**: No centralized coordinator required
##### Header Format
Each blob contains metadata for rollup continuity:
**`prev_state_root`**: Previous rollup state root, ensuring transition continuity
**`state_root`**: Current rollup state root after STARK-verified transitions
**`prev_height`**: Celestia height of previous proof blob for efficient retrieval
**`prev_commitment`**: Previous blob's commitment hash, required for Celestia retrieval
**`proof`**: STARK proof with state updates enabling integrity verification
### Implementation Notes
Current optimizations under consideration:
#### Network Architecture
* **Progress Tracking**: Nodes use latest Celestia height/commitment only
* **Namespace**: Predetermined and shared among participants
* **Future**: P2P network for propagating chain heads and updates
#### Proof Format
* **Current**: String format converted to bytes for Celestia
* **Future**: Compression using Starknet felts for improved efficiency
### Onchain games to provable games
Onchain games promise us freedom of expression and sovereignty over our information.
They possess these properties because they run on a blockchain verified by consensus.
Provable games, using zk proofs, allow for the verification of game state and computations without large consensus schemes.
Written in languages like Cairo, Noir or running RISC-Zero, these games can operate independently on an isolated zkVM like a browser, with verifiable outputs ensuring truthful execution.
This broadens our possibilities in the onchain gaming industry.
An illustrative example is a game like Donkey Kong.
Currently, to have your high score recognized on the leaderboard, you must play on a certified machine to prevent cheating, while recording your gameplay.
However, if Donkey Kong were a provable game, players could compete in isolation.
Achieving a high score would simply require submitting a proof to the Donkey Kong organization for verification.
This method allows players to establish themselves as the King of Kong from the comfort of their home, without the need to record their gameplay!
It's important to note that the approach isn't binary.
For instance, you could operate an onchain game on an EVM and then layer a Cairo-based game on top, enhancing the core game while broadening its capabilities.
For more information about Starknet, Cairo and its tech stack, check out the [Starknet & Cairo book](https://book.starknet.io/).
### Autonomous Worlds
> "Autonomous worlds represent persistent, permissionless, and decentralized open environments that users can freely interact with and contribute to."
The precise definition of Autonomous Worlds (AWs) remains somewhat elusive, as it is more of an abstract concept that has yet to be fully crystallized.
Lattice first [introduced](https://0xparc.org/blog/autonomous-worlds) the terminology in 2022, but the notion of open worlds operating on the blockchain has been around for a while.
The abstraction introduced by MUD served as a catalyst for the market to recognize the potential of these worlds.
Autonomous Worlds share notable similarities with blockchains in their fundamental nature.
Once established, they persist, maintaining their state throughout the lifespan of the chain.
Players can join or leave, and developers can expand these worlds by deploying features in a permissionless manner, much like how contracts are added to a chain.
While there is no universally accepted definition for an Autonomous World, we believe that a game must possess at least the following two essential features to be considered as such:
1. Decentralized data availability layer: While the state execution may reside on a centralized layer, it is crucial that the state can be reconstructed if the execution layer ceases to exist.
Rollups offer a solution, providing increased capacity execution layers while ensuring data is permanently settled on Ethereum.
This guarantees the world's perpetual persistence.
2. Permissionless entry point for expanding the world: The World contract must be capable of accepting new systems and components without requiring permission.
While this doesn't imply that every component and system will be utilized, they must adhere to this pattern, ensuring open and unrestricted access for potential enhancements.
We're firm believers in the potential for Autonomous Worlds to catalyze the exploration of novel forms in the medium provided by zk proofs and blockchain technology.
This is not only about games, but also about new forms of artwork, coordination, fun, emerging from tinkering and radical innovation, eventually questioning the very notion of "play" in this brave new decentralized and trustless world.
#### Homework
* [Wired - Autonomous Worlds Primer](https://www.wired.com/story/autonomous-worlds-aim-to-free-online-games-from-corporate-control/)
* [0xParc - Autonomous Worlds (Part 1)](https://0xparc.org/blog/autonomous-worlds)
* [Gubsheep - The Strongest Crypto Gaming Thesis](https://gubsheep.substack.com/p/the-strongest-crypto-gaming-thesis)
* [Lattice - MUD: An engine for Autonomous Worlds](https://lattice.xyz/blog/mud-an-engine-for-autonomous-worlds)
* [Guiltygyoza - Game 2.0](https://www.guiltygyoza.xyz/2022/07/game2)
* [Guiltygyoza - Composable Engineering](https://www.guiltygyoza.xyz/2023/05/composable-engineering)
* [Jay Springett - Wind-up Worlds](https://www.thejaymo.net/2022/05/06/wind-up-worlds/)
* [Are.na collection on Autonomous Worlds](https://www.are.na/sylve-chevet/on-chain-realities-and-autonomous-worlds)
* [Tarrence - Provable Games](https://www.dojoengine.org/en/articles/provable-games/)
* [Loaf - Provable Goblins](https://loaf.coffee/posts/provable-goblins)
## Cainome
[**Cainome**](https://github.com/cartridge-gg/cainome) is the foundational binding generator for Cairo smart contracts, providing the parsing infrastructure and plugin architecture that powers client binding generation -- enabling type-safe interaction with Cairo contracts.
The name combines "Cairo" and "Genome" - representing the DNA of the Cairo ecosystem that expresses into different language bindings.
##### Core Value Proposition
* **Type Safety**: Compile-time validation prevents runtime errors
* **Code Generation**: Eliminates manual binding creation and maintenance
* **Multi-Language Foundation**: Single parser supports multiple target languages
* **Cairo Expertise**: Deep understanding of Cairo's unique type system and serialization
#### What are Bindings?
**Language bindings** are code libraries that allow programs written in one language to interact with systems written in another language.
In blockchain development, smart contracts are often written in specialized languages (like Cairo or Solidity), while client applications use general-purpose languages (like Rust or TypeScript).
Bindings solve this integration challenge by:
* **Translating function calls** from the client language to the contract's native format
* **Converting data types** between different language type systems
* **Handling serialization** to transform data for network transmission
* **Providing type safety** to catch errors at compile-time rather than runtime
For example, without bindings, calling a Cairo contract from Rust would require manually:
```rust
// Manual contract interaction (error-prone)
let calldata = vec![
Felt::from_hex("0x123...")?, // What does this represent?
Felt::from(42_u32), // Raw numeric conversion
// ... more manual serialization
];
let result = provider.call(contract_address, "mystery_function", calldata).await?;
// Result is raw Felt values - what do they mean?
```
With generated bindings, the same interaction becomes:
```rust
// Type-safe binding (clear and safe)
let result = contract.transfer_tokens(recipient_address, amount).call().await?;
// Result is a strongly-typed struct with meaningful fields
```
### Architecture
Cainome uses a plugin-based architecture with a shared parsing core:
```
Cairo Contract ABI
↓
Cainome Parser (Rust)
↓
Token Representation
↓
Language Plugins
↓ ↓ ↓
Rust Go TypeScript
```
#### Core Components
* **Parser Core**: Rust ABI parser handles both current and legacy Cairo formats
* **Plugin System**: Extensible architecture supports built-in and external plugins
* **Type Mapping**: Sophisticated mapping between Cairo types and language types
* **Serialization**: Automatic handling of Cairo's unique serialization
Cainome provides the foundation for multiple language bindings through its plugin system.
Several targets (Rust, Go) are supported by Cainome's CLI directly, while others (TypeScript, Unity) are available through ecosystem plugins.
### Usage
Cainome provides both compile-time and CLI approaches for binding generation:
#### Rust Integration
**Compile-time macro (automatic):**
```rust
use cainome::rs::abigen;
// Bindings generated automatically during compilation
abigen!(MyContract, "./target/dev/contract.contract_class.json");
#[tokio::main]
async fn main() {
// Contract struct immediately available
let contract = MyContract::new(contract_address, account);
// Call view functions
let balance = contract.get_balance(player_address).call().await.unwrap();
println!("Player balance: {}", balance);
}
```
**CLI generation (manual):**
```bash
cainome --rust --output-dir ./bindings --execution-version v3 contract.json
```
#### Go Integration
**Inline directive (automatic):**
```go
//go:generate cainome --golang --golang-package mycontract --output-dir ./bindings ./contract.json
package main
import (
"context"
"fmt"
"mycontract" // Generated bindings
)
func main() {
// Run: go generate ./...
// Create contract reader for view functions
reader := mycontract.NewReader(contractAddress, provider)
// Call view functions with type safety
balance, err := reader.GetBalance(context.Background(), playerAddress)
if err != nil {
panic(err)
}
fmt.Printf("Player balance: %s\n", balance)
}
```
**CLI generation (manual):**
```bash
cainome --golang --golang-package mycontract --output-dir ./bindings contract.json
```
#### Other Targets
**TypeScript/Unity (via Sozo):**
```bash
sozo bindgen --typescript
sozo bindgen --unity
```
:::tip
For more binding generation workflows, see the [Sozo bindgen documentation](/toolchain/sozo/binding-generation).
:::
## Advanced Tutorials
This section covers more complex topics and advanced use cases.
Each tutorial builds upon the foundational concepts.
### Prerequisites
Before starting these tutorials, you should have completed the [Dojo Starter tutorial](/tutorials/dojo-starter), which covers the foundational concepts.
You'll also need a good understanding of the core concepts.
### Tutorial List
1. **Advanced Configuration** - Learn how to customize advanced settings.
This includes performance tuning and optimization techniques.
2. **Integration Patterns** - Explore different ways to integrate with external systems.
We'll cover REST APIs, webhooks, and message queues.
3. **Scalability Considerations** - Understand how to scale your application.
This tutorial covers load balancing and distributed architectures.
### Getting Help
If you run into issues, check the troubleshooting guide first.
You can also reach out on our community forum for assistance.
## Dojo React
import { LinkCard } from "../../components/LinkCard";
## Dojo 101 Tutorial
:::note
This section assumes that you have already installed the Dojo toolchain and are familiar with Cairo. If not, please refer to the [installation](/installation) section and the [Cairo book](https://book.cairo-lang.org).
:::
### Dojo in 15 minutes or less
To start, let's create a new project to run locally on your machine.
```bash
sozo init dojo-starter
```
Congratulations! You now have a local Dojo project!
This command creates a `dojo-starter` project in your current directory from the [Dojo starter template](https://github.com/dojoengine/dojo-starter). It's the ideal starting point for a new project and equips you with everything you need to begin hacking.
#### Anatomy of a Dojo Project
Inspect the contents of the `dojo-starter` project, and you'll notice the following structure (excluding the non-Cairo files):
```bash
├── Scarb.toml
├── dojo_dev.toml
├── dojo_release.toml
└── src
├── lib.cairo
├── models.cairo
├── systems
│ └── actions.cairo
└── tests
└── test_world.cairo
```
The scarb manifest (`Scarb.toml`) is a configuration file where project dependencies, metadata and other configurations are defined.
### Models
Next, open the `src/models.cairo` file to continue.
```rust
// ...
#[derive(Copy, Drop, Serde)]
#[dojo::model]
struct Moves {
#[key]
player: ContractAddress,
remaining: u8,
...
}
// ...
```
Notice the `#[dojo::model]` attribute.
For a model to be recognized, we *must* add this attribute to a Cairo struct. This tells to the Dojo compiler that this struct should be treated as a model.
#### Understanding the `#[key]` Attribute in the Moves Model
Our Moves model includes a `player` field, which is crucial for how Dojo manages and queries data. Models act like structured database entries, managing and organizing your onchain data. Like a regular ORM - but onchain!
Next, lets have a look at the `src/models/position.cairo` file.
```rust
// ...
#[derive(Drop, Copy, Serde)]
#[dojo::model]
struct Position {
// Define a Key. This acts like a primary key does in a regular ORM
#[key]
player: ContractAddress,
// Define a value field. This acts like a column in a regular ORM.
vec: Vec2,
}
// define Introspect struct
#[derive(Drop, Copy, Serde, Introspect)]
struct Vec2 {
// Define a value field. This acts like a column in a regular ORM.
x: u32,
y: u32
}
// ...
```
The `Position` model, like `Moves`, is indexed by the `player` field and includes a `Vec2` struct for x and y coordinates. Models can contain any Cairo struct that derives the [`Introspect`](/framework/models/introspection) trait, which allows the compiler to introspect the struct and generate the necessary code to interact with it.
### Contract Systems
A dojo contract is a Starknet contract annotated with the `#[dojo::contract]` attribute.
Like any Starknet contract, it defines an interface and an implementation.
Let's examine the `src/systems/actions.cairo` file.
```rust
use dojo_starter::models::{Direction, Position};
// define the interface
#[starknet::interface]
trait IActions {
fn spawn(ref self: T);
fn move(ref self: T, direction: Direction);
}
// dojo decorator
#[dojo::contract]
pub mod actions {
use super::{IActions, Direction, Position, next_position};
use starknet::{ContractAddress, get_caller_address};
use dojo_starter::models::{Vec2, Moves, DirectionsAvailable};
use dojo::model::{ModelStorage, ModelValueStorage};
use dojo::event::EventStorage;
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct Moved {
#[key]
pub player: ContractAddress,
pub direction: Direction,
}
#[abi(embed_v0)]
impl ActionsImpl of IActions {
fn spawn(ref self: ContractState) {
// Get the default world.
let mut world = self.world_default();
// Get the address of the current caller, possibly the player's address.
let player = get_caller_address();
// Retrieve the player's current position from the world.
let position: Position = world.read_model(player);
// Update the world state with the new data.
// 1. Move the player's position 10 units in both the x and y direction.
let new_position = Position {
player, vec: Vec2 { x: position.vec.x + 10, y: position.vec.y + 10 }
};
// Write the new position to the world.
world.write_model(@new_position);
// 2. Set the player's remaining moves to 100.
let moves = Moves {
player, remaining: 100, last_direction: Direction::None(()), can_move: true
};
// Write the new moves to the world.
world.write_model(@moves);
}
// Implementation of the move function for the ContractState struct.
fn move(ref self: ContractState, direction: Direction) {
// Get the default world.
let mut world = self.world_default();
// Get the address of the current caller, possibly the player's address.
let player = get_caller_address();
// Retrieve the player's current position and moves data from the world.
let position: Position = world.read_model(player);
let mut moves: Moves = world.read_model(player);
// Deduct one from the player's remaining moves.
moves.remaining -= 1;
// Update the last direction the player moved in.
moves.last_direction = direction;
// Calculate the player's next position based on the provided direction.
let next = next_position(position, direction);
// Write the new position to the world.
world.write_model(@next);
// Write the new moves to the world.
world.write_model(@moves);
// Emit an event to the world to notify about the player's move.
world.emit_event(@Moved { player, direction });
}
}
#[generate_trait]
impl InternalImpl of InternalTrait {
// Use the default namespace "dojo_starter".
// This function is handy since the ByteArray can't be const.
fn world_default(self: @ContractState) -> dojo::world::WorldStorage {
self.world(@"dojo_starter")
}
}
}
// Define function like this:
fn next_position(mut position: Position, direction: Direction) -> Position {
match direction {
Direction::None => { return position; },
Direction::Left => { position.vec.x -= 1; },
Direction::Right => { position.vec.x += 1; },
Direction::Up => { position.vec.y -= 1; },
Direction::Down => { position.vec.y += 1; },
};
position
}
```
#### Using the `world.write_model` method
Here we use the `world.write_model` method to set the `Moves` and `Position` models for the `player` entity. This method is used to update the world state with the new data.
```rust
let moves = Moves {
player, remaining: 100, last_direction: Direction::None(()), can_move: true
};
// Write the new moves to the world.
world.write_model(@moves);
```
We covered a lot here in a short time. Let's recap:
* Explained the anatomy of a Dojo project
* Explained the importance of the `#[dojo::model]`attribute and how models are defined
* Explained how `#[dojo::contract]` are used to define systems
* Explained how to inject the world into a system
* Touched on the `world.read_model` and `world.write_model` methods to interact with the world
### Deploy it locally
Enough theory! Let's build the Dojo project! In your primary terminal:
```bash
sozo build
```
That compiled the models and systems into artifacts that can be deployed. Simple as that!
Now, let's deploy it to [Katana](/toolchain/katana)! First, we need to get Katana running. Open a second terminal and execute:
```bash
katana --dev --dev.no-fee
```
Success! [Katana](/toolchain/katana) should now be running locally on your machine. Now, let's inspect the world. In your primary terminal, execute:
```bash
sozo inspect
```
This command provides a preview of your World's state and contract addresses. It performs a read-only comparison between local and remote resources without sending any transactions.
Now, let's deploy! In your primary terminal, execute:
```bash
sozo migrate
```
This command will deploy the artifact to `Katana`. You should see terminal output similar to this:
```console
profile | chain_id | rpc_url
---------+----------+------------------------
dev | KATANA | http://localhost:5050/
⛩️ Migration successful with world at address 0x06171ed98331e849d6084bf2b3e3186a7ddf35574dd68cab4691053ee8ab69d7
```
Your 🌎 is now deployed at `0x06171ed98331e849d6084bf2b3e3186a7ddf35574dd68cab4691053ee8ab69d7`!
This establishes the world address for your project.
Let's discuss the `Scarb.toml` file in the project. This file contains environment variables that make running CLI commands in your project a breeze (read more about it [here](/framework/configuration)).
```toml
[scripts]
migrate = "sozo build && sozo migrate" # scarb run migrate
spawn = "sozo execute dojo_starter-actions spawn --wait" # scarb run spawn
move = "sozo execute dojo_starter-actions move -c 1 --wait" # scarb run move
```
At the same time, make sure your file specifies the version of Dojo you have installed! In this case version `v1.0.0`.
```toml
[dependencies]
dojo = "1.8.0"
```
### Indexing
With your local world address established, let's delve into indexing. You can index the entire world. To accomplish this we have to copy your `world address` from the output of `sozo migrate`. Now Open a new terminal and input this simple command that includes your own world address:
```bash
torii --world 0xb4079627ebab1cd3cf9fd075dda1ad2454a7a448bf659591f259efa2519b18 --http.cors_origins "*"
```
Running the command mentioned above starts a [Torii](/toolchain/torii) server on your local machine. This server uses SQLite as its database and is accessible at [http://0.0.0.0:8080/graphql](http://0.0.0.0:8080/graphql). `Torii` will automatically organize your data into tables, making it easy for you to perform queries using GraphQL. When you run the command, you'll see terminal output that looks something like this:
```console
2024-06-14T04:38:33.450886Z INFO torii::relay::server: Relay peer id. peer_id=12D3KooWNJYDBVvnrWgi6QeVaQr6TZMEgJNni51UhZVGw1is4i9P
2024-06-14T04:38:33.451533Z INFO libp2p_swarm: local_peer_id=12D3KooWNJYDBVvnrWgi6QeVaQr6TZMEgJNni51UhZVGw1is4i9P
2024-06-14T04:38:33.452284Z INFO torii::cli: Starting torii endpoint. endpoint=http://127.0.0.1:8080
2024-06-14T04:38:33.452289Z INFO torii::cli: Serving Graphql playground. endpoint=http://127.0.0.1:8080/graphql
2024-06-14T04:38:33.452292Z INFO torii::cli: Serving World Explorer. url=https://worlds.dev/torii?url=http%3A%2F%2F127.0.0.1%3A8080%2Fgraphql
2024-06-14T04:38:33.452388Z INFO torii::relay::server: New listen address. address=/ip4/127.0.0.1/tcp/9090
2024-06-14T04:38:33.452405Z INFO torii::relay::server: New listen address. address=/ip4/192.168.1.249/tcp/9090
2024-06-14T04:38:33.452417Z INFO torii::relay::server: New listen address. address=/ip4/127.0.0.1/udp/9090/quic-v1
2024-06-14T04:38:33.452422Z INFO torii::relay::server: New listen address. address=/ip4/192.168.1.249/udp/9090/quic-v1
2024-06-14T04:38:33.452441Z INFO torii::relay::server: New listen address. address=/ip4/127.0.0.1/udp/9091/webrtc-direct/certhash/uEiCGVkq8QLf7zYLTYj389DZOcEjPOlTTj0D8VdFWb8qg9w
2024-06-14T04:38:33.452451Z INFO torii::relay::server: New listen address. address=/ip4/192.168.1.249/udp/9091/webrtc-direct/certhash/uEiCGVkq8QLf7zYLTYj389DZOcEjPOlTTj0D8VdFWb8qg9w
2024-06-14T04:38:33.455849Z INFO tori_core::engine: Processed block. block_number=3
2024-06-14T04:38:33.455857Z INFO tori_core::engine: Processed block. block_number=7
2024-06-14T04:38:33.455860Z INFO tori_core::engine: Processed block. block_number=9
2024-06-14T04:38:33.455862Z INFO tori_core::engine: Processed block. block_number=10
2024-06-14T04:38:33.455864Z INFO tori_core::engine: Processed block. block_number=11
2024-06-14T04:38:33.466242Z INFO torii_core::processors::register_model: Registered model. name=DirectionsAvailable
2024-06-14T04:38:33.474421Z INFO torii_core::processors::register_model: Registered model. name=Moves // [!code focus]
2024-06-14T04:38:33.481562Z INFO torii_core::processors::register_model: Registered model. name=Position // [!code focus]
2024-06-14T04:38:33.483915Z INFO torii_core::processors::metadata_update: Resource metadata set. resource=0x0 uri=ipfs://QmPVJzGEcj9Buy1mxTaXf43Fsv8CAzAwLNrjAN8JbigNQE/
2024-06-14T04:38:33.483953Z INFO torii_core::processors::metadata_update: Resource metadata set. resource=0x2491b781c4270f3f07a935b043ba8e1adf26afff83a3de3c6d5b87f1d0e23a5 uri=ipfs://QmbfMea3hDYMsS4gKpHjWW1h7RTPZv86Ua9BwEsHGzoZiH/
2024-06-14T04:38:33.483989Z INFO torii_core::processors::metadata_update: Resource metadata set. resource=0x23a5929b01fe8ac7a5c4ac078445d94c81ecdc23ae2c5c8555b3a4e0280964a uri=ipfs://QmeEbEAi3yr8Rxzi5TWc3MBncj295waxgeftQwbhNzrWnz/
2024-06-14T04:38:33.484024Z INFO torii_core::processors::metadata_update: Resource metadata set. resource=0x19a4478427ad87dac878352f7b5c33354395e17e7041e759f9581174962fe72 uri=ipfs://QmV3jymZLP6HJe26m9JchTogDY6xNedmT4h88xAvPfAroT/
2024-06-14T04:38:33.484059Z INFO torii_core::processors::metadata_update: Resource metadata set. resource=0x3610b797baec740e2fa25ae90b4a57d92b04f48a1fdbae1ae203eaf9723c1a0 uri=ipfs://Qmb9xTtZ7seVpmBj3vMdv4ox9PJMypAzzXdYgm9TpXR5cX/
2024-06-14T04:38:34.061939Z INFO torii_core::processors::metadata_update: Updated resource metadata from ipfs. resource=0x19a4478427ad87dac878352f7b5c33354395e17e7041e759f9581174962fe72
2024-06-14T04:38:34.066134Z INFO torii_core::processors::metadata_update: Updated resource metadata from ipfs. resource=0x2491b781c4270f3f07a935b043ba8e1adf26afff83a3de3c6d5b87f1d0e23a5
2024-06-14T04:38:34.077085Z INFO torii_core::processors::metadata_update: Updated resource metadata from ipfs. resource=0x23a5929b01fe8ac7a5c4ac078445d94c81ecdc23ae2c5c8555b3a4e0280964a
2024-06-14T04:38:34.091113Z INFO torii_core::processors::metadata_update: Updated resource metadata from ipfs. resource=0x3610b797baec740e2fa25ae90b4a57d92b04f48a1fdbae1ae203eaf9723c1a0
2024-06-14T04:38:34.776025Z INFO torii_core::processors::metadata_update: Updated resource metadata from ipfs. resource=0x0
```
You can observe that our `Moves` and `Position` models have been successfully registered.
#### GraphQL Queries
Next, let's use the GraphQL IDE to retrieve data from all the models that have been registered. In your web browser, navigate to `http://localhost:8080/graphql`, and enter the following query:
```graphql
query {
models {
edges {
node {
id
name
classHash
contractAddress
}
}
totalCount
}
}
```
After you run the query, you will receive an output like this:
```json
{
"data": {
"models": {
"edges": [
{
"node": {
"id": "0x77844f1facb51e60e546a9832d56c6bd04fa23be4fd5b57290caae5e9a3c1e4",
"name": "DirectionsAvailable",
"classHash": "0x7deb48ccf95cc441a0489cfefdae54aeb6f8ec462ba13ff25e23f080e66cc2f",
"contractAddress": "0x410c3b01e8209f1bb0c6591283efebddd5034b4baed4f0e9ea3c318d9fbae0b"
}
},
{
"node": {
"id": "0x504403e5c02b6442527721fc464d9ea5fc8f1ee600ab5ccd5c0845d36fd45f1",
"name": "Moved",
"classHash": "0x5be0a05a5df3bd3b4fc17f8b1feb395cb463ced20ea41d4fbb9b86a4d7efc66",
"contractAddress": "0x47d654cf2ea600e9689219b1b8545c742c1229ac948c87a01787462f3e93f96"
}
},
{
"node": {
"id": "0x2ac8b4c190f7031b9fc44312e6b047a1dce0b3f2957c33a935ca7846a46dd5b",
"name": "Position",
"classHash": "0x2283c68ecba5c60bbbbd3b00659808a02244468e41a1d2cdba1312d65b83594",
"contractAddress": "0x6e2770c9bbcf3f4f11529780fc04c2041f186225634541da225b93fe98b5bfd"
}
},
{
"node": {
"id": "0x2a29373f1af8348bd366a990eb3a342ef2cbe5e85160539eaca3441a673f468",
"name": "Moves",
"classHash": "0x70edf8f3be0b118e78f856f3ea9ebb652cba3684abaf7f299bfa6f93bf907c9",
"contractAddress": "0x554606894a9be2241c0dc3735c0f322c8d4816bfe292165614ca9a455b47503"
}
}
],
"totalCount": 4
}
}
}
```
Awesome, now let's work with subscriptions to get real-time updates. Let's clean up your workspace on the GraphQL IDE and input the following subscription:
```graphql
subscription {
entityUpdated {
id
keys
eventId
createdAt
updatedAt
}
}
```
Once you execute the subscription, you will receive notifications whenever new entities are updated or created. For now, don't make any changes to it, and proceed to create a new entities.
To accomplish this, we have to execute `spawn` function from `actions` contract. In your main local terminal, run the following command:
```bash
sozo execute dojo_starter-actions spawn
```
By running this command, you've executed the spawn system, resulting in the creation of a new entity.
Now, go back to your GraphQL IDE, and you will notice that you have received the subscription's results, which should look something like this:
```json
{
"data": {
"entityUpdated": {
"id": "0x43ebbfee0476dcc36cae36dfa9b47935cc20c36cb4dc7d014076e5f875cf164",
"keys": [
"0x127fd5f1fe78a71f8bcd1fec63e3fe2f0486b6ecd5c86a0466c3a21fa5cfcec"
],
"eventId": "0x0000000000000000000000000000000000000000000000000000000000000b:0x3baa02ae902388ed39ba124d1538e889d8265cfc99d461dc30781c478f4799:0x01",
"createdAt": "2024-11-12T13:23:35Z",
"updatedAt": "2024-11-12T13:23:35Z"
}
}
}
--------------------------------------------------------------------------------------------------------
{
"data": {
"entityUpdated": {
"id": "0x43ebbfee0476dcc36cae36dfa9b47935cc20c36cb4dc7d014076e5f875cf164",
"keys": [
"0x127fd5f1fe78a71f8bcd1fec63e3fe2f0486b6ecd5c86a0466c3a21fa5cfcec"
],
"eventId": "0x0000000000000000000000000000000000000000000000000000000000000b:0x3baa02ae902388ed39ba124d1538e889d8265cfc99d461dc30781c478f4799:0x00",
"createdAt": "2024-11-12T13:23:35Z",
"updatedAt": "2024-11-12T13:23:35Z"
}
}
}
```
In the GraphQL IDE, by clicking the `DOCS`-button on the right, you can open the API documentation. This documentation is auto-generated based on our schema definition and displays all API operations and data types of our schema. In order to know more about query and subscription, you can jump to [GraphQL](/toolchain/torii/graphql) section.
We've covered quite a bit! Here's a recap:
* Built a Dojo world
* Deployed the project to Katana
* Indexed the world with Torii
* Executed the `spawn` system locally
* Interacted with GraphQL
#### Next Steps
This overview provides a rapid end-to-end glimpse of Dojo. However, the potential of these worlds is vast! Designed to manage hundreds of systems and models, Dojo is equipped for expansive creativity. So, what will you craft next?
Coming soon!
import { LinkCard } from "../../components/LinkCard";
#### Quickstart with React
Concise.
Get started with Dojo in minutes.
This assumes knowledge of React and the Dojo CLI.
Prerequisites:
* [Dojo CLI installed](/installation)
1. Create project
```bash
npx @dojoengine/create-dojo start -t example-vite-react-sdk
```
2. Run development network with Katana
```bash
# In new terminal
katana --dev --dev.no-fee --http.cors_origins "*"
```
3. Build and deploy contracts
```bash
cd dojo-starter
sozo build
sozo migrate
```
From the `sozo migrate` command output, find the deployed world address.
For example:
```log
profile | chain_id | rpc_url
---------+----------+------------------------
dev | KATANA | http://localhost:5050/
🌍 World deployed at block 2 with txn hash: 0x038e984efa3e91e045b33d14e63c5e9f765e5a8fe2b3546fc3ab872f608e37a2
⛩️ Migration successful with world at address 0x00e2ea9b5dd9804d13903edf712998943b7d5d606c139dd0f13eeb8f5b84da8d
```
The world address in this case would be `0x00e2ea9b5dd9804d13903edf712998943b7d5d606c139dd0f13eeb8f5b84da8d`.
4. Run indexer with `torii`
Start the indexer `torii` with the world address from the last step:
```bash
# In new terminal
torii --http.cors_origins "*" --world YOUR_WORLD_ADDRESS
```
The command for the example world address above would be:
```bash
# Example command only. Use your own world address.
torii --http.cors_origins "*" --world 0x00e2ea9b5dd9804d13903edf712998943b7d5d606c139dd0f13eeb8f5b84da8d
```
5. Setup client
```bash
cd client
pnpm i
pnpm dev
```
Visit `http://localhost:5173` to view the project.
You should now see the project connected to the network and indexed!
#### Next Steps
This should have given you a good starting point to build your first application with Dojo.
Here are some next steps to take:
## dojo.bevy
Bevy is a modern, data-driven game engine built in Rust that leverages the Entity Component System (ECS) architecture.
Known for its performance, modularity, and ergonomic API, Bevy enables developers to create everything from simple 2D games to complex 3D experiences with compile-time safety and zero-cost abstractions.
dojo.bevy is the official Bevy engine SDK for interacting with Dojo worlds, providing native Rust integration for building high-performance onchain games.
Built specifically for Bevy's ECS architecture, it seamlessly integrates with Bevy's component system while maintaining the performance and safety guarantees that Rust developers expect.
### Core Concepts
Before diving into building onchain games with Bevy, let's explore the essential components of the dojo.bevy architecture:
#### `DojoPlugin`
The **`DojoPlugin`** is the central Bevy plugin that manages all connections to Torii and Starknet.
It handles async task coordination and event emission within Bevy's single-threaded execution model.
```rust
use bevy::prelude::*;
use dojo_bevy_plugin::DojoPlugin;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(DojoPlugin) // Add the Dojo plugin
.run();
}
```
#### `DojoResource`
The **`DojoResource`** serves as the main interface for all Dojo operations.
It manages connections to both Torii (for querying entities) and Starknet (for executing transactions).
```rust
use dojo_bevy_plugin::{DojoResource, TokioRuntime};
fn connect_to_dojo(
mut dojo: ResMut,
tokio: Res
) {
// Connect to Torii indexer
dojo.connect_torii(&tokio, "http://localhost:8080".to_string(), world_address);
// Connect to Starknet using predeployed account
dojo.connect_predeployed_account(&tokio, "http://localhost:5050".to_string(), 0);
}
```
#### Event System
dojo.bevy leverages Bevy's event system for reactive blockchain interactions:
* **`DojoInitializedEvent`**: Emitted when connections to Torii and Starknet are established
* **`DojoEntityUpdated`**: Emitted when entity state changes are received from Torii
```rust
use dojo_bevy_plugin::{DojoInitializedEvent, DojoEntityUpdated};
fn handle_dojo_events(
mut ev_initialized: EventReader,
mut ev_entity_updated: EventReader,
) {
for _ in ev_initialized.read() {
info!("Dojo connections established!");
}
for ev in ev_entity_updated.read() {
info!("Entity {} updated with {} models", ev.entity_id, ev.models.len());
}
}
```
#### `TokioRuntime`
The **`TokioRuntime`** resource provides async execution capabilities within Bevy's single-threaded environment.
This is essential for blockchain operations that require asynchronous processing.
### Getting Started
#### Prerequisites
Before getting started, ensure you have:
* [Rust](https://rustup.rs/) `>= 1.70.0` installed
* [Bevy](https://bevyengine.org/) `>= 0.16.0` in your project
#### Installation
Add dojo.bevy to your `Cargo.toml`:
```toml
[dependencies]
starknet = "0.16"
bevy = "0.16.0"
dojo_bevy_plugin = { git = "https://github.com/dojoengine/dojo.bevy" }
tokio = { version = "1.0", features = ["full"] }
```
#### Basic Setup
Create a basic Bevy application with Dojo integration:
```rust
use bevy::prelude::*;
use dojo_bevy_plugin::{
DojoPlugin, DojoResource, TokioRuntime,
DojoInitializedEvent, DojoEntityUpdated
};
use starknet::core::types::Felt;
// Your world and contract addresses (from sozo migrate output)
const WORLD_ADDRESS: Felt = Felt::from_hex_unchecked("0x07cb61df...");
const TORII_URL: &str = "http://localhost:8080";
const KATANA_URL: &str = "http://localhost:5050";
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(DojoPlugin)
.init_resource::()
.init_resource::()
.add_systems(Startup, setup_dojo)
.add_systems(Update, handle_dojo_events)
.run();
}
fn setup_dojo(
mut dojo: ResMut,
tokio: Res
) {
// Initialize connections
dojo.connect_torii(&tokio, TORII_URL.to_string(), WORLD_ADDRESS);
dojo.connect_predeployed_account(&tokio, KATANA_URL.to_string(), 0);
}
fn handle_dojo_events(
mut ev_initialized: EventReader,
mut ev_entity_updated: EventReader,
) {
for _ in ev_initialized.read() {
info!("Dojo initialized successfully!");
}
for ev in ev_entity_updated.read() {
info!("Entity {} updated", ev.entity_id);
}
}
```
### Usage Patterns
#### Querying Entities
Fetch entities from your Dojo world using Torii queries:
```rust
use torii_grpc_client::types::{Query as ToriiQuery, Pagination, PaginationDirection};
fn fetch_entities(
mut dojo: ResMut,
tokio: Res,
keyboard: Res>,
) {
if keyboard.just_pressed(KeyCode::KeyF) {
let query = ToriiQuery {
clause: None,
pagination: Pagination {
limit: 100,
cursor: None,
direction: PaginationDirection::Forward,
order_by: vec![],
},
no_hashed_keys: false,
models: vec![],
historical: false,
};
dojo.queue_retrieve_entities(&tokio, query);
}
}
```
#### Subscribing to Updates
Set up real-time subscriptions to track entity changes:
```rust
fn setup_subscriptions(
mut dojo: ResMut,
tokio: Res,
keyboard: Res>,
) {
if keyboard.just_pressed(KeyCode::KeyS) {
// Subscribe to all position updates
dojo.subscribe_entities(&tokio, "position_updates".to_string(), None);
}
}
```
#### Executing Transactions
Send transactions to your Dojo systems:
```rust
use starknet::core::types::Call;
use starknet::macros::selector;
// Contract addresses from your deployment
const ACTION_ADDRESS: Felt = Felt::from_hex_unchecked("0x0693bc04...");
fn handle_player_actions(
mut dojo: ResMut,
tokio: Res,
keyboard: Res>,
) {
if keyboard.just_pressed(KeyCode::Space) {
// Spawn a new entity
let spawn_call = Call {
to: ACTION_ADDRESS,
selector: selector!("spawn"),
calldata: vec![],
};
dojo.queue_tx(&tokio, vec![spawn_call]);
}
if keyboard.just_pressed(KeyCode::ArrowLeft) {
// Move entity left
let move_call = Call {
to: ACTION_ADDRESS,
selector: selector!("move"),
calldata: vec![Felt::from(0u32)], // Direction: Left
};
dojo.queue_tx(&tokio, vec![move_call]);
}
}
```
### Example Project
Here's a pedagogical example showing the key concepts for a 3D game where players can spawn and move cubes:
```rust
use bevy::prelude::*;
use dojo_bevy_plugin::{
DojoPlugin, DojoResource, TokioRuntime,
DojoInitializedEvent, DojoEntityUpdated
};
use starknet::core::types::{Call, Felt};
use starknet::macros::selector;
// Configuration constants
const WORLD_ADDRESS: Felt = Felt::from_hex_unchecked("0x07cb61df...");
const ACTION_ADDRESS: Felt = Felt::from_hex_unchecked("0x0693bc04...");
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(DojoPlugin) // 1. Add the Dojo plugin
.init_resource::()
.init_resource::()
.add_systems(Startup, setup_dojo)
.add_systems(Update, (handle_keyboard_input, handle_dojo_events))
.run();
}
fn setup_dojo(
mut dojo: ResMut,
tokio: Res
) {
// 2. Connect to Torii and Starknet
dojo.connect_torii(&tokio, "http://localhost:8080".to_string(), WORLD_ADDRESS);
dojo.connect_predeployed_account(&tokio, "http://localhost:5050".to_string(), 0);
}
fn handle_keyboard_input(
mut dojo: ResMut,
tokio: Res,
keyboard: Res>,
) {
if keyboard.just_pressed(KeyCode::Space) {
// 3. Execute transactions
let spawn_call = Call {
to: ACTION_ADDRESS,
selector: selector!("spawn"),
calldata: vec![],
};
dojo.queue_tx(&tokio, vec![spawn_call]);
}
}
fn handle_dojo_events(
mut ev_initialized: EventReader,
mut ev_entity_updated: EventReader,
) {
// 4. React to blockchain events
for _ in ev_initialized.read() {
info!("Connected to Dojo world!");
}
for ev in ev_entity_updated.read() {
info!("Entity {} updated with {} models", ev.entity_id, ev.models.len());
// Process entity updates and sync with Bevy components
}
}
```
#### Key Learning Points
1. **Plugin Integration**: Add `DojoPlugin` to enable blockchain connectivity
2. **Resource Management**: Use `DojoResource` and `TokioRuntime` for async operations
3. **Transaction Execution**: Queue transactions with `dojo.queue_tx()`
4. **Event Handling**: React to `DojoInitializedEvent` and `DojoEntityUpdated` events
For a complete implementation including 3D rendering, entity tracking, and full game logic, see the [full example](https://github.com/dojoengine/dojo.bevy/blob/main/examples/intro.rs).
### Advanced Features
#### Account Management
For production applications, you'll want to use custom accounts instead of predeployed ones:
```rust
fn setup_custom_account(
mut dojo: ResMut,
tokio: Res
) {
let account_address = Felt::from_hex("0x1234...").unwrap();
let private_key = Felt::from_hex("0x5678...").unwrap();
dojo.connect_account(
&tokio,
"https://api.cartridge.gg/x/your-game/katana".to_string(),
account_address,
private_key
);
}
```
#### Batch Transactions
Execute multiple system calls in a single transaction:
```rust
fn batch_operations(
mut dojo: ResMut,
tokio: Res
) {
let calls = vec![
Call {
to: ACTION_ADDRESS,
selector: selector!("spawn"),
calldata: vec![],
},
Call {
to: ACTION_ADDRESS,
selector: selector!("move"),
calldata: vec![Felt::from(1u32)],
},
];
dojo.queue_tx(&tokio, calls);
}
```
#### Entity Management
* Use Bevy's change detection to minimize unnecessary updates
* Batch entity operations when possible
* Consider using Bevy's sparse sets for entities with optional components
```rust
fn optimized_position_updates(
mut ev_position_updated: EventReader,
mut query: Query<&mut Transform, (With, Changed)>,
) {
// Only process entities that have actually changed
for ev in ev_position_updated.read() {
// Update logic here
}
}
```
### Troubleshooting
Enable debug logging to troubleshoot issues:
```rust
use bevy::log::LogPlugin;
fn main() {
App::new()
.add_plugins(DefaultPlugins.set(LogPlugin {
level: bevy::log::Level::DEBUG,
..default()
}))
.add_plugins(DojoPlugin)
.run();
}
```
## dojo.godot
Godot Engine is a free, open-source cross-platform game engine renowned for its flexibility, ease of use, and powerful scene system.
With its intuitive node-based architecture, GDScript scripting language, and robust 2D and 3D capabilities, Godot empowers developers to create everything from indie platformers to complex multiplayer experiences.
dojo.godot is the official Godot Engine SDK for building onchain games powered by Dojo.
This GDExtension seamlessly integrates blockchain functionality into your Godot projects, built on the [dojo.c](/client/sdk/c) foundation.
It enables you to create fully decentralized games without compromising on performance or developer experience.
:::tip
Always [Download](https://github.com/lonewolftechnology/godot-dojo/releases) the latest version.
:::
:::info
If there is something that is not covered here, please refer to the in-editor documentation.
You can access it by pressing Ctrl + Left Click on a class name, or by pressing F1 and using the search bar.
:::
### Core Concepts
#### Torii Client
The `ToriiClient` is your gateway to the Dojo world, managing all communication with the blockchain indexer.
Key responsibilities:
* **Connection Management**: Establish and maintain connections to Torii servers.
* **Entity Queries**: Fetch game entities and their associated models from the blockchain.
* **Event Subscriptions**: Subscribe to real-time blockchain events and entity updates.
#### DojoSessionAccount
The `DojoSessionAccount` handles all transaction-related operations and wallet management.
Core features:
* **Wallet Authentication**: Secure connection to Cartridge Controller accounts.
* **Transaction Execution**: Execute smart contract calls with proper signing.
* **Session Management**: Maintain authenticated sessions with configurable policies.
#### Dojo Calls
Used for smart contract function calls.
Structure:
* **Contract Address**: The address of the target smart contract.
* **Function Name (`entrypoint/method`)**: The name of the function to call.
* **Call Data**: An array of parameters to pass to the function.
#### Helper Classes
Two helper classes were added to simplify common tasks:
* `ControllerHelper`: Provides utility functions for session management, key generation, and calldata handling.
* `GodotDojoHelper`: Offers utility for float-point conversion and extension configuration.
### Editor Utilities
Some in-editor tools were added under
`Project -> Tools -> Godot Dojo Tools`
#### Cairo Type System
dojo.godot automatically handles conversions between Cairo types and Godot equivalents:
* **Primitives**: `u8`, `u16`, `u32`, `u64`, `u128`, `u256`, `felt252` map to Godot integers and strings.
* **Structures**: Cairo structs convert to Godot Dictionaries with proper field mapping.
* **Arrays**: Cairo arrays become Godot Arrays with automatic element conversion.
* **Enums**: Cairo enums map to Godot integers with enumeration support.
:::info
Godot does not natively support big integers like `i128`, `u128`, and `u256`.
However, the extension supports them using wrapper classes to store and display the data.
`I128`, `U128`, and `U256` wrappers can be used directly inside the `calldata` array.
:::
### Getting Started
We will be using `dojo-starter` contract for this example.
::::steps
##### Download the latest version
Visit the [release page](https://github.com/lonewolftechnology/godot-dojo/releases) and download the latest version for your platform.
##### Create Your Godot Project
Create a new Godot project or open an existing one.
##### Install the GDExtension
1. Create an `addons` folder in the root of your project if it does not exist.
2. Copy the downloaded files into the `addons` folder.
3. Restart Godot to ensure the extension is correctly loaded.
##### Setup `ToriiClient`
* Add a `ToriiClient` node to your scene tree.
* Connect the client:
```gdscript
var _success = torii_client.connect("http://localhost:8080")
```
* Register a subscription:
:::tip
If `ToriiClient.subscribe_entity_updates` is used with an empty `DojoClause`, it retrieves ALL entity updates.
:::
```gdscript
func _entity_callback(entity: Dictionary):
printt("Entity Models", entity["models"])
func _register_sub():
var _dojo_callback = DojoCallback.new()
_dojo_callback.on_update = _entity_callback
_dojo_callback.on_error = func(_err): printt("DojoError", _err) # Can also be a lambda
var dojo_worlds: Array = ["0x..."]
if torii_client.is_valid():
var entity_sub: int = torii_client.subscribe_entity_updates(DojoClause.new(), dojo_worlds, _dojo_callback)
```
:::note
If `torii_client.connect` is used in the same method, you can use its return value instead of checking `is_valid()`.
:::
##### Connect `DojoSessionAccount`
Add a `DojoSessionAccount` node to your scene tree.
Generate the URL to start the login process and open it in the browser:
```gdscript
func trigger_login():
# Create policies
var policies = { "policies": [
{
"target": "0x...",
"method": "move"
},
{
"target": "0x...",
"method": "spawn"
}
]}
# Generate private key.
# If a previously generated key is used, you can create a session without logging in again.
_priv_key = ControllerHelper.generate_private_key() # Save this key for the next step
# Create session registration URL
var session_url = ControllerHelper.create_session_registration_url(_priv_key, policies, "http://localhost:5050")
# Open the default web browser
OS.shell_open(session_url)
```
Once the session is successfully registered, fetch the session:
```gdscript
dojo_session_account.max_fee = "0x100000"
dojo_session_account.full_policies = policies
dojo_session_account.create_from_subscribe(
_priv_key,
"http://localhost:5050", # Katana URL
policies, # Optional if `full_policies` is set
"https://api.cartridge.gg" # Controller URL (defaults to this value)
)
```
::::
#### Creating Contract Calls
When creating calls, you need the contract address, the selector/function name, and call arguments.
:::warning
The `calldata` is an array and an optional parameter.
If the function in your contract does not take arguments, you can pass an empty array.
Otherwise, arguments must be inside an array.
Calldata is always flattened.
This means that if it has other arrays inside, they will be merged into a single array.
:::
:::info
If your function uses a custom type defined on your contract, you need to send all members in order inside an array or directly on the calldata array.
In the example a Vector3 is used.
But it works with any type defined on your contract.
:::
:::tip
`execute` and `execute_from_outside` can make calls in bulk by using an array.
`execute_single` and `execute_from_outside_single` were added as a utility for a simple unique transaction.
:::
```gdscript
var new_pos:Vector3 = Vector3(6, 2.5, -6)
var position:Array = [new_pos.x, new_pos.y, new_pos.z]
var direction:int = Directions.LEFT
func move_to(_position:Array, _direction:int) -> void:
var move_call:Dictionary = {
"contract_address": "0x...",
"entrypoint": "move",
"calldata": [_position, _direction]
}
dojo_session_account.execute(
[move_call]
)
# or
dojo_session_account.execute_single(
"0x...",
"move",
[_position, _direction]
)
```
#### Subscriptions
Subscriptions can be created through `ToriiClient`.
When a subscription is successfully created, it returns a `sub_id`.
This `sub_id` is required to update or cancel the subscription.
Subscriptions are automatically cancelled when closing the game.
:::tip
Refer to the in-editor documentation to see other available subscriptions.
:::
##### Subscribing to Events
Listen for blockchain events in real-time.
The first parameter is always a `Callable`:
This can be a lambda function, a `Callable` constructed from another object's function, or any function in the current script.
```gdscript
# Configured in inspector
@export var entity_sub: int
@export var event_sub: int
func setup_subscriptions() -> void:
# Subscribe to all events
var _dojo_callback_message: DojoCallback = DojoCallback.new()
_dojo_callback_message.on_update = callback.bind("Message") # We add an extra parameter to the callback
_dojo_callback_message.on_error = error_callback.bind("Message")
message_sub = torii_client.subscribe_event_updates([], _dojo_callback_message)
# Subscribe to entity updates
var _dojo_callback: DojoCallback = DojoCallback.new()
_dojo_callback.on_update = callback.bind("Entity")
_dojo_callback.on_error = error_callback.bind("Entity")
entity_sub = torii_client.subscribe_entity_updates(DojoClause.new(), ["0x..."], _dojo_callback)
func callback(data: Dictionary, type: String):
if type == "Entity":
var result = data["models"]
if type == "Message":
var _data = data["data"]
for _key in _data:
printt("**", _key)
func error_callback(err, type: String):
push_error("Error on %s subscription: %s" % [type, err])
```
#### Querying Entities
Fetch the current blockchain state using queries:
:::tip
An empty query retrieves **ALL** entities across **ALL** worlds indexed by Torii.
:::
:::note
`DojoQuery` is used for retrieving entities.
For other query types, refer to the `ToriiClient` in-editor documentation.
:::
```gdscript
func query_players() -> void:
var query: DojoQuery = DojoQuery.new()
var entities = torii_client.entities(query)
for entity in entities:
process_entity(entity)
func process_entity(entity: Dictionary) -> void:
for model in entity["items"]:
for key in model:
var data = model[key]
# Process based on your model structure
if data.has("dojo_starter-Position"):
var position_model = data["dojo_starter-Position"]
# Structure depends on your contract models/ABI
var position = Vector2(position_model['vec']['x'], position_model['vec']['y'])
var id: String = position_model['player']
printt(id, position)
```
##### Advanced Queries (Builder Pattern)
All queries follow the **Builder Pattern**, allowing for flexible and readable construction of complex filters:
```gdscript
var query: DojoQuery = DojoQuery.new()
var clause = MemberClause.new()
clause.member("id")
clause.op(MemberClause.ComparisonOperator.Eq)
var _addr = "0x..." # Player address
clause.hex(_addr, MemberClause.PrimitiveTag.ContractAddress)
clause.model("dojo_starter-Player")
query.with_clause(clause)
query.models(["dojo_starter-Moves", "dojo_starter-Position"])
var data: Dictionary = torii_client.entities(query)
var _player_items: Array = data['items']
printt("ITEMS", _player_items)
```
You can also create the query in a single line:
```gdscript
var query: DojoQuery = DojoQuery.new() \
.with_clause(MemberClause.new() \
.member("id") \
.op(MemberClause.ComparisonOperator.Eq) \
.hex("0x...", MemberClause.PrimitiveTag.ContractAddress) \
.model("dojo_starter-Player")) \
.models(["dojo_starter-Moves", "dojo_starter-Position"])
var data: Dictionary = torii_client.entities(query)
var _player_items: Array = data['items']
printt("ITEMS", _player_items)
```
#### Managing Policies
The recommended structure for policies for this extension is the following:
```gdscript
# Create policy
var full_policies: Dictionary = {
"max_fee": "0x100000",
"policies": [{
"contract_address": "0x...",
"methods": [
"spawn",
"move"
]
}]
}
# Use with session account
dojo_session_account.max_fee = full_policies["max_fee"] # Can be omitted if present on full_policies dictionary
dojo_session_account.full_policies = full_policies
dojo_session_account.create_from_subscribe(
_priv_key,
"http://localhost:5050", # Katana
full_policies, # Optional if `full_policies` is set
"https://api.cartridge.gg" # Optional, defaults to `https://api.cartridge.gg`
)
```
#### Type Conversion
dojo.godot handles type conversion automatically:
Every struct/type in your contract will return inside a Dictionary with its name and parameters.
In the Starter Project there is a custom struct `Vec2`, the following snippet is an example.
:::info
Cairo native types are converted to Godot's types automatically.
:::
```gdscript
# Cairo Vec2 becomes Godot Vector2
func handle_position_update(data: Dictionary) -> void:
if data.has("Vec2"):
var position = Vector2(data["Vec2"]["x"], data["Vec2"]["y"])
player.position = position
# Enums convert to integers
enum Direction { LEFT, RIGHT, UP, DOWN }
func move_player(direction: Direction) -> void:
move_call.calldata = [direction] # Automatically converts to felt252
controller_account.execute_from_outside([move_call])
```
### Building and Deployment
We use scons build system as it is what Godot uses.
However, a Cmake equivalent is placed so the extension can be developed using any modern IDE.
The extension compiles and places itself under *demo/addons/godot-dojo*.
As of `godot-dojo` [v0.7.2](https://github.com/lonewolftechnology/godot-dojo/releases/tag/v0.7.2), `Linux`, `Windows`, `MacOS` , `Android` and `iOS` are supported.
Make sure to check new versions under the [releases tab](https://github.com/lonewolftechnology/godot-dojo/releases)
:::note
`Linux` and `Windows` builds are x86\_64 only.
You can build arm64 builds.
Let us know if it works by opening an issue or by leaving a message on `Dojo's Discord` under the `Godot section`.
`MacOS` builds are not universal, so while they work on both Intel and Apple Silicon, they require separate binaries.
A universal build could be created, but we have not been able to properly test it yet.
:::
#### Editor builds
The editor builds contain code that only runs in-editor and is only found in-editor.
This is the only required build to run the extension; for exporting the templates are required.
```bash
scons platform=linux target=editor
```
#### Development Builds
For development, use debug builds to enable logging:
```bash
scons platform=linux target=template_debug
scons platform=windows target=template_debug
scons platform=macos target=template_debug
```
You can set environment variables in your game for detailed logging.
This is useful when developing the extension or reporting bugs:
```gdscript
func _ready() -> void:
OS.set_environment("RUST_BACKTRACE", "full")
OS.set_environment("RUST_LOG", "debug")
```
#### Production Builds
Create optimized builds for distribution:
```bash
scons platform=linux target=template_release
scons platform=windows target=template_release
scons platform=macos target=template_release
```
### Example Project
The dojo.godot repository includes a complete demo project showcasing:
* **Player Movement**: Onchain player spawning and movement using arrow keys.
* **Real-time Updates**: Live synchronization between blockchain state and game visuals.
* **Controller Integration**: Seamless wallet authentication and transaction signing.
* **Event Handling**: Processing both events and entity updates from subscriptions.
To run the demo:
:::note
Follow the [dojo-starter](/tutorials/dojo-starter) or [dojo-intro](/getting-started/your-first-dojo-app) guide.
:::
1. Set up a local Dojo Starter environment.
2. Build/download the dojo.godot extension following the instructions above.
3. Open the `demo` folder in Godot and run the project.
The demo connects to a live testnet deployment, demonstrating real blockchain integration in a simple 2D movement game.
### Troubleshooting
Enable detailed logging for troubleshooting:
```gdscript
func _ready() -> void:
OS.set_environment("RUST_LOG", "debug,tokio=info,hyper=info")
OS.set_environment("RUST_BACKTRACE", "full")
```
Check the Godot console for detailed error messages and stack traces.
## Dojo SDK Architecture
Dojo provides a comprehensive suite of SDKs for building onchain games and applications across multiple platforms.
All SDKs share a unified foundation built on **dojo.c**, ensuring consistent functionality with platform-specific optimizations.
### Foundation-First Architecture
The Dojo SDK ecosystem is built on a two-layer foundation that ensures both consistency and type safety across all platforms.
#### Layer 1: Cainome - Type-Safe Binding Generation
**Cainome** generates type-safe bindings from Cairo contract ABIs, providing the compile-time foundation for all SDK interactions.
**Key capabilities:**
* **ABI Parsing**: Converts Cairo contract ABIs into platform-specific type definitions
* **Type Safety**: Ensures compile-time guarantees for contract interactions
* **Multi-Language Support**: Generates bindings for Rust, TypeScript, C#, and other languages
* **Serialization Handling**: Automatically handles Cairo ↔ native type conversions
#### Layer 2: dojo.c - Runtime Blockchain Integration
**dojo.c** provides the runtime foundation that handles all blockchain interactions across platforms.
**Key capabilities:**
* **Account Management**: Support for controller accounts, session accounts, and burner wallets
* **Transaction Handling**: Sign and execute transactions with proper gas estimation
* **Torii Client Integration**: Query entities, subscribe to real-time updates, and sync world state
* **Cross-Platform Compatibility**: Compiles to both native binaries (via C bindings) and WebAssembly
**Dual Compilation Strategy:**
* **Native Platforms**: Uses `cbindgen` to generate C headers for Unity, Unreal, and other native integrations
* **Web Platforms**: Uses `wasm-bindgen` to generate WebAssembly modules for JavaScript/TypeScript applications
#### The Complete Foundation
**Cainome** (compile-time) + **dojo.c** (runtime) = **Complete SDK Foundation**
This two-layer architecture ensures that:
* Contract interactions are type-safe and validated at compile time (Cainome)
* Blockchain operations are consistent and optimized at runtime (dojo.c)
* Platform SDKs can focus on providing idiomatic APIs for their ecosystems
### Common Patterns
Regardless of which SDK you choose, all Dojo applications follow similar patterns:
#### Core Game Loop
```
Client → SDK → Katana (sequencer) → Torii (indexer) → SDK → Client
```
All SDKs provide mechanisms to send transactions and query entities from your world.
Player actions in the client are translated by the SDK into transactions that are sent to the sequencer.
The sequencer executes the transactions and the indexer updates the world state.
The client can then query the world state to get the latest state, which is then rendered to the user.
#### Account Management
* **Session Accounts**: Temporary accounts for seamless gameplay
* **Controller Accounts**: Delegate specific permissions to game contracts
* **Burner Accounts**: Disposable accounts funded by a master account
#### Transaction Flow
1. **Prepare**: Build transaction calls using contract bindings
2. **Sign**: Use account credentials to sign transaction
3. **Execute**: Submit transaction to Katana sequencer
4. **Wait**: Monitor transaction status and confirmation
5. **Sync**: Update local state with new world state
### Platform-Specific SDKs
#### Production Ready
##### JavaScript/TypeScript SDK
**Best for:** Web applications, React/Vue/Svelte apps, Node.js backends
* Full-featured SDK with React hooks and state management
* Multiple examples: Vanilla JS, React, Vue, Svelte, Phaser integration
* Real-time entity synchronization with RECS (Reactive Entity Component System)
* Built-in support for wallet connections and burner accounts
##### Unity SDK
**Best for:** 2D and 3D games, cross-platform game development
* Native C# bindings built on dojo.c foundation
* Unity-specific components and prefabs for common patterns
* Support for WebGL, desktop, and mobile platforms
* Integrated world state synchronization with Unity's component system
#### Active Development
##### Bevy SDK
**Best for:** Rust-based game development
* ECS-native integration with Bevy's component system
* Rust-first development experience with compile-time safety
* Direct access to dojo.c functionality without FFI overhead
##### Unreal Engine SDK
**Best for:** High-fidelity 3D games and applications
* C++ integration with Unreal's Blueprint system
* Support for complex game mechanics and AAA-quality experiences
* Native performance with dojo.c foundation
#### Experimental
##### C/C++ Bindings
**Best for:** Custom integrations, maximum performance requirements
* Direct access to dojo.c API without additional abstraction layers
* Full control over memory management and optimization
* Foundation for building custom SDK wrappers
##### Native Rust Integration
**Best for:** Rust applications requiring direct Dojo integration
* Import Dojo as a native Rust crate
* Zero-cost abstractions with compile-time optimization
* Suitable for high-performance backends and custom tooling
### Choosing the Right SDK
| Platform/Framework | Recommended SDK | Maturity Level |
| ------------------------ | --------------------- | --------------------- |
| Web (React, Vue, Svelte) | JavaScript/TypeScript | ✅ Production |
| Unity Games | Unity SDK | ✅ Production |
| Bevy Games | Bevy SDK | 🔄 Active Development |
| Unreal Engine | Unreal SDK | 🔄 Active Development |
| Custom C/C++ | C Bindings | ⚗️ Experimental |
| Rust Applications | Native Rust | ⚗️ Experimental |
### Getting Started
To begin development with any Dojo SDK:
1. **Set up your development environment** with Katana and Torii
2. **Choose your SDK** based on your platform and requirements
3. **Follow the platform-specific setup guide** linked below
4. **Explore examples** to understand integration patterns
#### Quick Links
* [JavaScript/TypeScript SDK →](./javascript)
* [Unity SDK →](./unity)
* [Bevy SDK →](./bevy)
* [Unreal Engine SDK →](./unrealengine)
* [C Bindings →](./c)
* [Rust Integration →](./rust)
### Architecture Benefits
This foundation-first approach provides several key advantages:
* **Consistency**: Core blockchain logic is identical across all platforms
* **Maintainability**: Bug fixes and features in dojo.c benefit all SDKs
* **Performance**: Native compilation ensures optimal performance on each platform
* **Interoperability**: Applications built with different SDKs can interact seamlessly
* **Rapid Development**: New platform support can be added by wrapping dojo.c
## dojo.js
The dojo.js SDK provides a powerful, intuitive interface for interacting with onchain state in JavaScript.
It streamlines data fetching and subscriptions, supporting both simple and complex queries.
### Key Features
* **Type Safety**: Leverage TypeScript for robust, error-resistant code.
* **Intuitive query syntax**: Write ORM-like queries that feel natural.
* **Flexible subscriptions**: Subscribe to granular state changes in your world.
* **Built-in Zustand support**: Reactive state management.
* **Signed messages**: Sign off-chain state and send to Torii.
* **Optimistic Client Rendering**: Update state before a transaction has finalized.
:::note
dojo.js is a wrapper around [dojo.c](/client/sdk/c/wasm-bindings) that exposes Torii client features via WASM.
For more information about the Torii gRPC client, check out [this documentation](/toolchain/torii/grpc).
:::
### Getting Started
#### Quickstart Wizard
The fastest way to get started is using our quickstart wizard.
```bash
pnpx @dojoengine/create-dojo start
```
This will guide you through a series of prompts to configure your project.
#### Manual Setup
For full control over your project structure, follow these steps:
::::steps
##### Create Your Project
Pick any JavaScript framework.
We recommend [pnpm](https://pnpm.io/) as your package manager.
##### Install Dependencies
```bash
mkdir {project_name} && cd {project_name} && pnpm init
# Essential packages
pnpm add @dojoengine/core @dojoengine/sdk @dojoengine/torii-client
# For React integration
pnpm add @dojoengine/create-burner @dojoengine/utils
# For state management (v1.6+)
pnpm add @dojoengine/state
# Build tools for WASM support
pnpm add -D vite-plugin-wasm vite-plugin-top-level-await
# Additional dependencies (if using React)
pnpm add zustand immer
```
##### Create `dojoConfig.ts`
Create a `dojoConfig.ts` file and pass in your project's manifest:
```typescript
import { createDojoConfig } from "@dojoengine/core";
import manifest from "../path/to/manifest_dev.json";
export const dojoConfig = createDojoConfig({ manifest });
```
##### Generate TypeScript Bindings
[Generate contract bindings](/toolchain/sozo/binding-generation) with Sozo, letting you import Dojo models into TypeScript:
```bash
DOJO_MANIFEST_PATH="../path/to/Scarb.toml" sozo build --typescript
```
:::note
These instructions assume you have Dojo contracts relative to your client root.
:::
##### Initialize the SDK
With your contract bindings generated, you can now link Dojo models to your UI.
```typescript
// main.tsx
// React imports
import { createRoot } from "react-dom/client";
import App from "./App.tsx";
// Dojo imports
import { init } from "@dojoengine/sdk";
import { DojoSdkProvider } from "@dojoengine/sdk/react";
// Local imports
import { dojoConfig } from "./dojoConfig.ts";
import { setupWorld } from "./bindings/typescript/contracts.gen.ts";
import type { SchemaType } from "./bindings/typescript/models.gen.ts";
async function main() {
// Initialize the SDK with configuration options
const sdk = await init({
client: {
// Required: Address of the deployed World contract
worldAddress: dojoConfig.manifest.world.address,
// Optional: Torii indexer URL (defaults to http://localhost:8080)
toriiUrl: dojoConfig.toriiUrl || "http://localhost:8080",
// Optional: Relay URL for real-time messaging
relayUrl: dojoConfig.relayUrl || "/ip4/127.0.0.1/tcp/9090",
},
// Domain configuration for typed message signing (SNIP-12)
domain: {
name: "MyDojoProject",
version: "1.0",
chainId: "KATANA", // or "SN_MAIN", "SN_SEPOLIA"
revision: "1",
},
});
createRoot(document.getElementById("root")!).render(
);
}
main();
```
:::note
If using `starknet-react`, wrap `DojoSdkProvider` *around* `StarknetProvider`:
```typescript
```
:::
:::warning
Call `init` only once to avoid creating multiple Torii clients.
:::
::::
### Usage Overview
#### Core SDK Methods
The SDK provides several key methods for interacting with your Dojo world:
* **`getEntities()`** - Fetch entities with flexible filtering
* **`subscribeEntityQuery()`** - Subscribe to real-time entity updates
* **`getEventMessages()`** - Fetch historical events
* **`subscribeEventQuery()`** - Subscribe to real-time event updates
* **`sendMessage()`** - Send signed off-chain messages
All queries use the same `ToriiQueryBuilder` and support the same filtering operators: `Eq`, `Neq`, `Gt`, `Gte`, `Lt`, `Lte`, `In`, `NotIn`.
:::note
For more information about Torii queries, consult the [Torii gRPC API reference](/toolchain/torii/grpc).
:::
#### Querying Entities
Fetch entities using the `ToriiQueryBuilder` with various clause types:
```typescript
import { ToriiQueryBuilder, KeysClause } from "@dojoengine/sdk";
// Simple query: Find a specific player with KeysClause
const entities = await sdk.getEntities({
query: new ToriiQueryBuilder().withClause(
KeysClause(["dojo_starter-Player"], ["0xabcde..."], "FixedLen").build()
),
});
// Access the results
entities.items.forEach((entity) => {
const player = entity.models.dojo_starter.Player;
console.log(`Player: ${player?.name}, Score: ${player?.score}`);
});
```
:::note
Models are accessed using the pattern `entity.models.{namespace}.{ModelName}` where:
* `{namespace}` is your project's namespace (e.g., `dojo_starter`, `world`, `game`)
* `{ModelName}` is the exact model name as defined in your Cairo code
:::
:::tip
You do not need to `build()` your `query`; the sdk will build it automatically.
:::
Here is an example of a complex query that finds high-scoring warriors and mages in a specific area of the map:
```typescript
import { ToriiQueryBuilder, MemberClause } from "@dojoengine/sdk";
// Complex query: find matching entities with MemberClause
const entities = await sdk.getEntities({
query: new ToriiQueryBuilder().withClause(
AndComposeClause([
// Player conditions
MemberClause("world-Player", "score", "Gt", 100),
OrComposeClause([
MemberClause("world-Player", "class", "Eq", "warrior"),
MemberClause("world-Player", "class", "Eq", "mage"),
]),
// Position conditions (same entities)
MemberClause("world-Position", "x", "Lt", 50),
MemberClause("world-Position", "y", "Gt", 20),
]).build()
),
});
```
:::note
When you use AND with different model types, you're looking for **entities that have both components**.
:::
For large datasets, use pagination and ordering:
```typescript
const entities = await sdk.getEntities({
query: new ToriiQueryBuilder()
.withClause(
MemberClause("dojo_starter-Player", "score", "Gt", 0).build()
)
.withLimit(10) // Limit to 10 results
.withOffset(0) // Start from beginning
.withOrderBy([
{
// Order by score descending
field: "score",
direction: "Desc",
},
]),
});
```
#### Subscribing To Entity Changes
Subscribe to real-time updates for entities matching your query criteria:
```typescript
const [initialEntities, subscription] = await sdk.subscribeEntityQuery({
query: new ToriiQueryBuilder()
.withClause(
MemberClause("dojo_starter-Player", "score", "Gt", 100).build()
)
.includeHashedKeys(),
callback: ({ data, error }) => {
if (data) {
console.log("Player updated:", data);
data.forEach((entity) => {
const player = entity.models.dojo_starter.Player;
console.log(`Player ${player?.id}: ${player?.score} points`);
});
}
if (error) {
console.error("Subscription error:", error);
}
},
});
// Cancel the subscription when no longer needed
// subscription.cancel();
```
#### Saving State With Zustand
The SDK integrates with Zustand for reactive state management, updating your components automatically when blockchain data changes:
```
1. useEntityQuery() subscribes to Torii for real-time updates
↓
2. Blockchain state changes (transaction, new entity, etc.)
↓
3. Torii detects the change and pushes update to your client
↓
4. SDK automatically updates the Zustand store
↓
5. React components using useModels/useModel re-render automatically
↓
6. UI shows the latest data
```
##### Option 1: Using Convenience Hooks (Recommended)
The easiest way is to use the provided React hooks, which abstract away the store:
```typescript
import { useEntityQuery, useModels, useModel, useEntityId } from "@dojoengine/sdk/react";
function MyComponent() {
// Subscribe to entity changes - data automatically goes into the store
useEntityQuery(
new ToriiQueryBuilder()
.withClause(MemberClause("dojo_starter-Item", "durability", "Eq", 2).build())
.includeHashedKeys()
);
// Get all items from the store using convenience hooks
const items = useModels("dojo_starter-Item");
// Get a single item by entity ID
const entityId = useEntityId(1);
const item = useModel(entityId, "dojo_starter-Item");
return (
All Items with Durability 2:
{Object.entries(items).map(([id, item]) => (
Item {id}: durability {item?.durability}
))}
Single Item:
{item &&
Item 1: durability {item.durability}
}
);
}
```
##### Option 2: Direct Zustand Store Access
For more advanced use cases, you can access the Zustand store directly:
```typescript
import { useDojoSDK, useEntityQuery } from "@dojoengine/sdk/react";
function MyComponent() {
const { useDojoStore } = useDojoSDK(); // The Zustand store
// Subscribe to entity changes
useEntityQuery(
new ToriiQueryBuilder()
.withClause(MemberClause("dojo_starter-Item", "durability", "Eq", 2).build())
.includeHashedKeys()
);
// Access the raw store
const allEntities = useDojoStore((state) =>
state.entities
);
const itemEntities = useDojoStore((state) =>
state.getEntitiesByModel("dojo_starter", "Item")
);
return (
Total entities in store: {Object.keys(allEntities).length}
);
}
```
#### Sending Signed Messages
Signed messages allow you to send **authenticated off-chain data** to Torii without expensive blockchain transactions.
This can be used to implement things like **chat systems, leaderboards, social features, game coordination, and real-time events.**
The key benefit: **Players authenticate the data** (proving it came from them) **without gas fees**, while Torii broadcasts it to all connected clients in real-time.
Here's an example of how to send a signed message:
```typescript
// Generate typed data for a chat message model
const typedData = sdk.generateTypedData("world-Message", {
identity: account?.address,
content: messageContent,
timestamp: Date.now(),
});
try {
// Sign and send the message using the SDK
const result = await sdk.sendMessage(typedData, account);
if (result.isOk()) {
console.log("Message sent successfully:", result.value);
} else {
console.error("Failed to send message:", result.error);
}
} catch (error) {
console.error("Error sending message:", error);
}
```
:::note
If you want messages to be broadcast to all of your Torii client instances, you'll have to pass a `relayUrl` to `init`.
`relayUrl` is a *multiaddr* format which looks like something like this when deployed on slot:
`/dns4/api.cartridge.gg/tcp/443/x-parity-wss/%2Fx%2Fyour-slot-deployment-name%2Ftorii%2Fwss`
:::
#### Querying Tokens
dojo.js can query token data (ERC20, ERC721, ERC1155) indexed by Torii.
First, configure Torii to index your tokens:
```toml
# dojo_dev.toml
[indexing]
contracts = [
"erc20:0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", # ETH
"erc20:0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", # STRK
"erc721:0x..." # Your NFT contract
]
```
Then query token balances in your React components:
```typescript
import { useTokens } from "@dojoengine/sdk/react";
function TokenBalance({ address }: { address: string }) {
const { tokens, balances, getBalance, toDecimal } = useTokens({
accountAddresses: [address],
});
return (
);
}
```
#### Optimistic Client Rendering
We use [immer](https://immerjs.github.io/immer/) for efficient optimistic rendering.
This allows instant client-side entity state updates while awaiting blockchain confirmation.
**The process:**
1. Update entity state optimistically.
2. Wait for condition (e.g., a specific state change).
3. Resolve update, providing immediate user feedback.
This ensures a responsive user experience while maintaining blockchain data integrity.
:::note
You will need to have a subscription running in order for the update to resolve.
:::
```typescript
import { useCallback } from "react";
import { v4 as uuidv4 } from "uuid";
import { useDojoSDK } from "@dojoengine/sdk/react";
import { useAccount } from "@starknet-react/core";
import { getEntityIdFromKeys } from "@dojoengine/utils";
export function useSystemCalls(entityId: string) {
const { account } = useAccount();
const { useDojoStore, client } = useDojoSDK();
const state = useDojoStore((s) => s);
const spawn = useCallback(async () => {
if (!account) return;
// Generate a unique transaction ID
const transactionId = uuidv4();
// The value to update the Moves model with
const remainingMoves = 100;
// Apply an optimistic update to the state
// this uses immer drafts to update the state
state.applyOptimisticUpdate(transactionId, (draft) => {
if (draft.entities[entityId]?.models?.dojo_starter?.Moves) {
draft.entities[entityId].models.dojo_starter.Moves!.remaining =
remainingMoves;
}
});
try {
// Execute the spawn action
await client.actions.spawn({ account });
// Wait for the entity to be updated with the new state
await state.waitForEntityChange(entityId, (entity) => {
return (
entity?.models?.dojo_starter?.Moves?.remaining ===
remainingMoves
);
});
} catch (error) {
// Revert the optimistic update if an error occurs
state.revertOptimisticUpdate(transactionId);
console.error("Error executing spawn:", error);
throw error;
} finally {
// Confirm the transaction if successful
state.confirmTransaction(transactionId);
}
}, [account, client, entityId, state]);
return { spawn };
}
```
### Additional Examples
See this [example project](https://github.com/dojoengine/dojo.js/tree/main/examples/example-vite-react-sdk) for a real-world implementation.
## Dojo Rust SDK
Dojo is built in Rust, making it seamless to integrate into your Rust projects.
Simply import the required crates and you're ready to build powerful applications that interact with Dojo worlds.
The Dojo Rust SDK provides access to the core framework functionality, built on the same [dojo.c foundation](/client/sdk/c) that powers other language bindings.
### Core Components
The Dojo Rust ecosystem provides several key crates for different use cases:
* **`dojo-types`**: Core types and data structures for Dojo
* **`dojo-world`**: World contract interaction and management
* **`torii-client`**: Client for connecting to Torii indexer
* **`torii-grpc`**: gRPC client for real-time data streaming
* **`torii-relay`**: P2P networking and relay functionality
* **`cainome`**: Contract bindings generation for Cairo contracts
### Getting Started
Add these dependencies to your `Cargo.toml`:
```toml
[dependencies]
# Core Dojo types and functionality
dojo-types = { git = "https://github.com/dojoengine/dojo", tag = "v1.7.0-alpha.0" }
dojo-world = { git = "https://github.com/dojoengine/dojo", tag = "v1.7.0-alpha.0" }
# Torii client for indexing and real-time data (separate repository since 1.5.0)
torii-client = { git = "https://github.com/dojoengine/torii", tag = "v1.6.1-preview.2" }
torii-grpc-client = { git = "https://github.com/dojoengine/torii", tag = "v1.6.1-preview.2" }
# Contract bindings generation
cainome = { git = "https://github.com/cartridge-gg/cainome", rev = "7d60de1", features = ["abigen-rs"] }
cainome-cairo-serde = { git = "https://github.com/cartridge-gg/cainome", rev = "7d60de1" }
# Starknet integration
starknet = "0.17.0-rc.2"
starknet-crypto = "0.7.4"
starknet-types-core = "0.1.7"
# Async runtime
tokio = { version = "1.39", features = ["full"] }
```
### Basic Usage
#### Connecting to a Dojo World
```rust
// Import necessary types for connecting to Dojo
use torii_client::client::Client;
use starknet_crypto::Felt;
// The #[tokio::main] attribute makes this function run in an async runtime
// This is required because we'll be making network calls
#[tokio::main]
async fn main() -> Result<(), Box> {
// Configure connection URLs for your Dojo world
let torii_url = "http://localhost:8080".to_string(); // Torii indexer endpoint
let rpc_url = "http://localhost:5050".to_string(); // Starknet RPC endpoint (Katana)
let relay_url = "http://localhost:9090".to_string(); // P2P relay for real-time updates
// The world address is a unique identifier for your Dojo world on Starknet
let world_address = Felt::from_hex_unchecked("0x123...");
// Create a new Torii client connection
let client = Client::new(
torii_url,
rpc_url,
relay_url,
world_address,
).await?;
// At this point, you have a connected client ready to interact with your Dojo world
// You can now query models, subscribe to events, etc.
Ok(())
}
```
#### Subscribing to Events
```rust
// Import types needed for event subscriptions and stream processing
use torii_grpc_client::client::{EntityKeysClause, KeysClause, PatternMatching};
use futures::StreamExt; // Provides the .next() method on streams
// Subscribe to all entity updates in the Dojo world
// The `mut` keyword makes the subscription mutable so we can read from it
let mut subscription = client
.on_event_message_updated(
// Create a filter for which entities/models to watch
vec![EntityKeysClause::Keys(KeysClause {
keys: vec![], // Empty = watch all entities
pattern_matching: PatternMatching::VariableLen, // Allow flexible key matching
models: vec![], // Empty = watch all models
})],
true, // Include historical data (events that happened before we subscribed)
)
.await?; // Wait for the subscription to be established
// Process incoming updates in a loop
// This will run continuously, waiting for new events
while let Some(Ok((_, entity))) = subscription.next().await {
// The `Some(Ok(...))` pattern handles the Result