Dojo System Generation Create Dojo systems (smart contracts) that implement your game's logic and modify model state. Essential Imports (Dojo 1.0+) Copy these imports for any Dojo system: // Core Dojo imports - ALWAYS needed for systems use dojo::model::{ModelStorage, ModelValueStorage}; use dojo::event::EventStorage; // Starknet essentials use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; Where does self.world_default() come from? self.world_default() is provided automatically by
[dojo::contract]
- no import needed!
[dojo::contract] // <-- This macro provides world_default()
mod my_system { use dojo::model::{ModelStorage, ModelValueStorage}; use dojo::event::EventStorage;
[abi(embed_v0)]
impl MyImpl of IMySystem
[derive(Copy, Drop, Serde)]
[dojo::event]
struct PlayerMoved {
[key]
player: ContractAddress, from_x: u32, from_y: u32, to_x: u32, to_y: u32, } // 2. Emit it (inside a function) fn move_player(ref self: ContractState, direction: u8) { let mut world = self.world_default(); // ... game logic ... // Emit event - note the @ for snapshot world.emit_event(@PlayerMoved { player: get_caller_address(), from_x: 0, from_y: 0, to_x: 1, to_y: 1, }); } Quick reference: What imports what You want to use Import this world.read_model() use dojo::model::ModelStorage; world.write_model() use dojo::model::ModelStorage; world.emit_event() use dojo::event::EventStorage; self.world_default() Nothing! Provided by
[dojo::contract]
get_caller_address() use starknet::get_caller_address; When to Use This Skill "Create a spawn system" "Add a move system that updates position" "Implement combat logic" "Generate a system for [game action]" What This Skill Does Generates Cairo system contracts with:
[dojo::contract]
attribute Interface definition with
[starknet::interface]
System implementation World access ( world.read_model() , world.write_model() ) Event emissions with
[dojo::event]
Quick Start Interactive mode: "Create a system for player movement" I'll ask about: System name Functions and their parameters Models used Authorization requirements Direct mode: "Create a move system that updates Position based on Direction" System Structure A Dojo contract consists of an interface trait and a contract module: use dojo_starter::models::{Direction, Position}; // Define the interface
[starknet::interface]
trait IActions
[dojo::contract]
pub mod actions { use super::{IActions, Direction, Position}; use starknet::{ContractAddress, get_caller_address}; use dojo_starter::models::{Vec2, Moves}; use dojo::model::{ModelStorage, ModelValueStorage}; use dojo::event::EventStorage; // Define a custom event
[derive(Copy, Drop, Serde)]
[dojo::event]
pub struct Moved {
[key]
pub player: ContractAddress, pub direction: Direction, }
[abi(embed_v0)]
impl ActionsImpl of IActions
[generate_trait]
impl InternalImpl of InternalTrait { fn world_default(self: @ContractState) -> dojo::world::WorldStorage { self.world(@"dojo_starter") } } } // Helper function outside the contract 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 } Key Concepts World Access Get the world storage using your namespace: let mut world = self.world(@"my_namespace"); Create a helper function to avoid repeating the namespace:
[generate_trait]
impl InternalImpl of InternalTrait { fn world_default(self: @ContractState) -> dojo::world::WorldStorage { self.world(@"my_namespace") } } Reading Models let position: Position = world.read_model(player); Writing Models world.write_model(@Position { player, vec: Vec2 { x: 10, y: 20 } }); Emitting Events Define events with
[dojo::event]
:
[derive(Copy, Drop, Serde)]
[dojo::event]
pub struct PlayerMoved {
[key]
- pub player: ContractAddress,
- pub from: Vec2,
- pub to: Vec2,
- }
- // Emit in your function
- world.emit_event(@PlayerMoved { player, from: old_pos, to: new_pos });
- Getting Caller
- use starknet::get_caller_address;
- let player = get_caller_address();
- Generating Unique IDs
- let entity_id = world.uuid();
- System Design
- Single Responsibility
- Each system should have one clear purpose:
- MovementSystem
-
- Handles player/entity movement
- CombatSystem
-
- Manages battles and damage
- InventorySystem
- Manages items Stateless Design Systems should be stateless, reading state from models: fn attack(ref self: ContractState, target: ContractAddress) { let mut world = self.world_default(); let attacker = get_caller_address(); // Read current state let attacker_stats: Combat = world.read_model(attacker); let mut target_stats: Combat = world.read_model(target); // Apply logic target_stats.health -= attacker_stats.damage; // Write updated state world.write_model(@target_stats); } Input Validation Validate inputs before modifying state: fn move(ref self: ContractState, direction: Direction) { let mut world = self.world_default(); let player = get_caller_address(); let moves: Moves = world.read_model(player); assert(moves.remaining > 0, 'No moves remaining'); assert(moves.can_move, 'Movement disabled'); // Proceed with movement } Permissions Systems need writer permission to modify models. Configure in dojo_dev.toml : [ writers ] "my_namespace" = [ "my_namespace-actions" ] Or grant specific model access: [ writers ] "my_namespace-Position" = [ "my_namespace-actions" ] "my_namespace-Moves" = [ "my_namespace-actions" ] Next Steps After creating systems: Use dojo-test skill to test system logic Use dojo-review skill to check for issues Use dojo-deploy skill to deploy your world Use dojo-client skill to call systems from frontend