← Back to list

dojo-token
by dojoengine
The Dojo Book
⭐ 51🍴 94📅 Jan 22, 2026
SKILL.md
name: dojo-token description: Implement ERC20 and ERC721 token standards in Dojo using Origami library. Use when adding fungible tokens, NFTs, or token-based game mechanics. allowed-tools: Read, Write, Edit, Glob, Grep
Dojo Token Standards
Implement ERC20 fungible tokens and ERC721 NFTs in your Dojo game using the Origami library.
When to Use This Skill
- "Implement ERC20 token for game currency"
- "Create NFT items with ERC721"
- "Add token standard to my game"
- "Use Origami for tokens"
What This Skill Does
Implements token standards:
- ERC20 fungible tokens (currency, resources)
- ERC721 non-fungible tokens (items, characters)
- Token minting and burning
- Transfer mechanics
- Balance tracking
Token Standards
ERC20 - Fungible Tokens
For interchangeable assets:
- Game currency (gold, gems)
- Resources (wood, stone)
- Experience points
Properties:
- Divisible (can have fractions)
- Interchangeable (any token = any other)
- Track balances per account
ERC721 - Non-Fungible Tokens
For unique assets:
- Character NFTs
- Equipment/items
- Land plots
- Achievements
Properties:
- Unique (each has token ID)
- Indivisible (whole units only)
- Individual ownership tracking
Using Origami Library
Installation
Add to Scarb.toml:
[dependencies]
origami_token = { git = "https://github.com/dojoengine/origami", tag = "v1.0.0" }
Origami provides reusable token components following standard interfaces. Refer to the Origami documentation for the latest API.
Simple Token Implementation
You can implement tokens using standard Dojo models without Origami:
ERC20-like Currency
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Gold {
#[key]
pub player: ContractAddress,
pub amount: u256,
}
#[starknet::interface]
trait IGoldToken<T> {
fn mint(ref self: T, to: ContractAddress, amount: u256);
fn transfer(ref self: T, to: ContractAddress, amount: u256);
fn balance_of(self: @T, account: ContractAddress) -> u256;
}
#[dojo::contract]
mod gold_token {
use super::{IGoldToken, Gold};
use starknet::{ContractAddress, get_caller_address};
use dojo::model::ModelStorage;
#[abi(embed_v0)]
impl GoldTokenImpl of IGoldToken<ContractState> {
fn mint(ref self: ContractState, to: ContractAddress, amount: u256) {
let mut world = self.world_default();
let mut balance: Gold = world.read_model(to);
balance.amount += amount;
world.write_model(@balance);
}
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
let mut world = self.world_default();
let from = get_caller_address();
let mut from_balance: Gold = world.read_model(from);
let mut to_balance: Gold = world.read_model(to);
assert(from_balance.amount >= amount, 'insufficient balance');
from_balance.amount -= amount;
to_balance.amount += amount;
world.write_model(@from_balance);
world.write_model(@to_balance);
}
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
let world = self.world_default();
let balance: Gold = world.read_model(account);
balance.amount
}
}
#[generate_trait]
impl InternalImpl of InternalTrait {
fn world_default(self: @ContractState) -> dojo::world::WorldStorage {
self.world(@"my_game")
}
}
}
ERC721-like NFT
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Weapon {
#[key]
pub token_id: u256,
pub owner: ContractAddress,
pub damage: u32,
pub rarity: u8,
}
#[starknet::interface]
trait IWeaponNFT<T> {
fn mint(ref self: T, to: ContractAddress, damage: u32) -> u256;
fn transfer(ref self: T, to: ContractAddress, token_id: u256);
fn owner_of(self: @T, token_id: u256) -> ContractAddress;
}
#[dojo::contract]
mod weapon_nft {
use super::{IWeaponNFT, Weapon};
use starknet::{ContractAddress, get_caller_address};
use dojo::model::ModelStorage;
#[abi(embed_v0)]
impl WeaponNFTImpl of IWeaponNFT<ContractState> {
fn mint(ref self: ContractState, to: ContractAddress, damage: u32) -> u256 {
let mut world = self.world_default();
let token_id: u256 = world.uuid().into();
let weapon = Weapon {
token_id,
owner: to,
damage,
rarity: 1,
};
world.write_model(@weapon);
token_id
}
fn transfer(ref self: ContractState, to: ContractAddress, token_id: u256) {
let mut world = self.world_default();
let from = get_caller_address();
let mut weapon: Weapon = world.read_model(token_id);
assert(weapon.owner == from, 'not owner');
weapon.owner = to;
world.write_model(@weapon);
}
fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress {
let world = self.world_default();
let weapon: Weapon = world.read_model(token_id);
weapon.owner
}
}
#[generate_trait]
impl InternalImpl of InternalTrait {
fn world_default(self: @ContractState) -> dojo::world::WorldStorage {
self.world(@"my_game")
}
}
}
Game Patterns
In-Game Currency
// Award gold for completing quest
fn complete_quest(ref self: ContractState, quest_id: u32) {
let mut world = self.world_default();
let player = get_caller_address();
// Check quest is completed
// ...
// Award gold
let mut gold: Gold = world.read_model(player);
gold.amount += 100;
world.write_model(@gold);
}
// Spend gold to buy item
fn buy_item(ref self: ContractState, item_id: u32, price: u256) {
let mut world = self.world_default();
let player = get_caller_address();
let mut gold: Gold = world.read_model(player);
assert(gold.amount >= price, 'insufficient gold');
gold.amount -= price;
world.write_model(@gold);
// Give item to player
// ...
}
Multiple Resource Types
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Resources {
#[key]
pub player: ContractAddress,
pub wood: u256,
pub stone: u256,
pub iron: u256,
}
Equipment NFTs
fn equip_weapon(ref self: ContractState, token_id: u256) {
let mut world = self.world_default();
let player = get_caller_address();
// Check ownership
let weapon: Weapon = world.read_model(token_id);
assert(weapon.owner == player, 'not owner');
// Equip
let mut equipment: Equipment = world.read_model(player);
equipment.weapon_id = token_id;
world.write_model(@equipment);
}
Token Events
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct TokenTransferred {
#[key]
pub from: ContractAddress,
#[key]
pub to: ContractAddress,
pub amount: u256,
}
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct NFTTransferred {
#[key]
pub token_id: u256,
pub from: ContractAddress,
pub to: ContractAddress,
}
// Emit in your functions
world.emit_event(@TokenTransferred { from, to, amount });
Considerations
- Balance checks: Always verify sufficient balance before transfers
- Ownership: Always verify ownership before NFT operations
- Overflow: Use u256 for token amounts to avoid overflow
- Events: Emit events for all token operations for indexing
- Permissions: Grant writer permission to token contracts
Next Steps
After implementing tokens:
- Test thoroughly with
dojo-testskill - Deploy with
dojo-deployskill - Integrate with client (
dojo-clientskill) - Set up permissions (
dojo-worldskill)
Related Skills
- dojo-model: Token models extend Dojo models
- dojo-system: Token logic in systems
- dojo-test: Test token operations
- dojo-review: Audit token implementation
Score
Total Score
70/100
Based on repository quality metrics
✓SKILL.md
SKILL.mdファイルが含まれている
+20
✓LICENSE
ライセンスが設定されている
+10
○説明文
100文字以上の説明がある
0/10
○人気
GitHub Stars 100以上
0/15
✓最近の活動
1ヶ月以内に更新
+10
✓フォーク
10回以上フォークされている
+5
✓Issue管理
オープンIssueが50未満
+5
✓言語
プログラミング言語が設定されている
+5
✓タグ
1つ以上のタグが設定されている
+5
Reviews
💬
Reviews coming soon
