Implement ERC20 and ERC721 token standards in Dojo using Origami library. Use when adding fungible tokens, NFTs, or token-based game mechanics.
Implements ERC20 and ERC721 token standards in Dojo using the Origami library. Use when adding fungible tokens, NFTs, or token-based game mechanics.
/plugin marketplace add dojoengine/book/plugin install book@dojoengineThis skill is limited to using the following tools:
Implement ERC20 fungible tokens and ERC721 NFTs in your Dojo game using the Origami library.
Implements token standards:
ERC20 (fungible):
"Implement ERC20 token for gold currency"
ERC721 (NFT):
"Create ERC721 for equipment items"
With Origami:
"Use Origami library to add token support"
For interchangeable assets:
Properties:
For unique assets:
Properties:
Add to Scarb.toml:
[dependencies]
origami_token = { git = "https://github.com/dojoengine/origami", tag = "v1.0.0" }
Origami provides reusable token components:
Balance - Token balancesERC20Allowance - Spending approvalsERC20Metadata - Token infoERC721Owner - NFT ownershipERC721TokenApproval - NFT approvalsuse origami_token::components::token::erc20::erc20_balance::{
ERC20Balance, ERC20BalanceTrait
};
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Gold {
#[key]
pub player: ContractAddress,
pub amount: u256,
}
use dojo::model::{ModelStorage, ModelValueStorage};
use origami_token::components::token::erc20::erc20_balance::ERC20Balance;
#[dojo::interface]
trait IGoldToken {
fn mint(ref self: ContractState, to: ContractAddress, amount: u256);
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256);
fn balance_of(self: @ContractState, account: ContractAddress) -> u256;
}
#[dojo::contract]
mod gold_token {
use super::IGoldToken;
#[abi(embed_v0)]
impl GoldTokenImpl of IGoldToken<ContractState> {
fn mint(ref self: ContractState, to: ContractAddress, amount: u256) {
let mut world = self.world_default();
// Get current balance
let mut balance: Gold = world.read_model(to);
// Add amount
balance.amount += amount;
// Save
world.write_model(@balance);
// Emit event
world.emit_event(@TokenMinted { to, amount });
}
fn transfer(
ref self: ContractState,
to: ContractAddress,
amount: u256
) {
let mut world = self.world_default();
let from = get_caller_address();
// Get balances
let mut from_balance: Gold = world.read_model(from);
let mut to_balance: Gold = world.read_model(to);
// Check sufficient balance
assert(from_balance.amount >= amount, 'insufficient balance');
// Transfer
from_balance.amount -= amount;
to_balance.amount += amount;
// Save
world.write_model(@from_balance);
world.write_model(@to_balance);
// Emit event
world.emit_event(@TokenTransferred { from, to, amount });
}
fn balance_of(self: @ContractState, account: ContractAddress) -> u256 {
let world = self.world_default();
let balance: Gold = world.read_model(account);
balance.amount
}
}
}
Using Origami components:
use origami_token::components::token::erc20::erc20_balance::{
ERC20Balance, ERC20BalanceTrait
};
#[dojo::contract]
mod gold_token {
fn transfer(ref self: ContractState, to: ContractAddress, amount: u256) {
let mut world = self.world_default();
let from = get_caller_address();
// Use Origami trait
let mut from_balance: ERC20Balance = world.read_model(from);
let mut to_balance: ERC20Balance = world.read_model(to);
// Origami provides safe transfer
from_balance.transfer(ref to_balance, amount);
world.write_model(@from_balance);
world.write_model(@to_balance);
}
}
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Weapon {
#[key]
pub token_id: u256,
pub owner: ContractAddress,
pub damage: u32,
pub rarity: u8,
}
#[dojo::interface]
trait IWeaponNFT {
fn mint(ref self: ContractState, to: ContractAddress, damage: u32) -> u256;
fn transfer(ref self: ContractState, to: ContractAddress, token_id: u256);
fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress;
}
#[dojo::contract]
mod weapon_nft {
use super::IWeaponNFT;
#[abi(embed_v0)]
impl WeaponNFTImpl of IWeaponNFT<ContractState> {
fn mint(ref self: ContractState, to: ContractAddress, damage: u32) -> u256 {
let mut world = self.world_default();
// Generate unique token ID
let token_id: u256 = world.uuid().into();
// Create NFT
let weapon = Weapon {
token_id,
owner: to,
damage,
rarity: calculate_rarity(damage),
};
world.write_model(@weapon);
// Emit event
world.emit_event(@NFTMinted { to, token_id });
token_id
}
fn transfer(ref self: ContractState, to: ContractAddress, token_id: u256) {
let mut world = self.world_default();
let from = get_caller_address();
// Get NFT
let mut weapon: Weapon = world.read_model(token_id);
// Check ownership
assert(weapon.owner == from, 'not owner');
// Transfer
weapon.owner = to;
world.write_model(@weapon);
// Emit event
world.emit_event(@NFTTransferred { from, to, token_id });
}
fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress {
let world = self.world_default();
let weapon: Weapon = world.read_model(token_id);
weapon.owner
}
}
}
// Gold token model
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Gold {
#[key]
pub player: ContractAddress,
pub amount: u256,
}
// Award gold for completing quest
fn complete_quest(ref self: ContractState, player: ContractAddress) {
let mut world = self.world_default();
// Get current gold
let mut gold: Gold = world.read_model(player);
// Award 100 gold
gold.amount += 100;
world.write_model(@gold);
}
// Spend gold to buy item
fn buy_item(ref self: ContractState, item_id: u32) {
let mut world = self.world_default();
let player = get_caller_address();
// Get gold balance
let mut gold: Gold = world.read_model(player);
// Check price
let item_price = get_item_price(item_id);
assert(gold.amount >= item_price, 'insufficient gold');
// Deduct gold
gold.amount -= item_price;
world.write_model(@gold);
// Give item
give_item(player, item_id);
}
// Weapon NFT model
#[derive(Copy, Drop, Serde)]
#[dojo::model]
pub struct Weapon {
#[key]
pub token_id: u256,
pub owner: ContractAddress,
pub weapon_type: u8, // 0=sword, 1=axe, 2=bow
pub damage: u32,
pub durability: u32,
}
// Craft new weapon
fn craft_weapon(ref self: ContractState, weapon_type: u8) -> u256 {
let mut world = self.world_default();
let player = get_caller_address();
// Check materials
require_materials(player, weapon_type);
// Create weapon NFT
let token_id = world.uuid().into();
let weapon = Weapon {
token_id,
owner: player,
weapon_type,
damage: calculate_damage(weapon_type),
durability: 100,
};
world.write_model(@weapon);
token_id
}
// Equip weapon
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);
}
// 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,
}
// Gather resources
fn gather(ref self: ContractState, resource_type: u8) {
let mut world = self.world_default();
let player = get_caller_address();
let mut resources: Resources = world.read_model(player);
match resource_type {
0 => resources.wood += 10,
1 => resources.stone += 10,
2 => resources.iron += 5,
_ => panic!("invalid resource"),
}
world.write_model(@resources);
}
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct TokenMinted {
pub to: ContractAddress,
pub amount: u256,
}
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct TokenTransferred {
pub from: ContractAddress,
pub to: ContractAddress,
pub amount: u256,
}
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct NFTMinted {
pub to: ContractAddress,
pub token_id: u256,
}
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct NFTTransferred {
pub from: ContractAddress,
pub to: ContractAddress,
pub token_id: u256,
}
#[test]
fn test_gold_transfer() {
let mut world = spawn_test_world(...);
// Mint gold to player1
gold_token.mint(player1, 100);
// Transfer to player2
prank(world, player1);
gold_token.transfer(player2, 50);
// Check balances
assert(gold_token.balance_of(player1) == 50, 'wrong sender balance');
assert(gold_token.balance_of(player2) == 50, 'wrong receiver balance');
}
#[test]
fn test_weapon_nft() {
let mut world = spawn_test_world(...);
// Mint weapon to player1
let token_id = weapon_nft.mint(player1, 50);
// Check ownership
assert(weapon_nft.owner_of(token_id) == player1, 'wrong owner');
// Transfer to player2
prank(world, player1);
weapon_nft.transfer(player2, token_id);
// Check new owner
assert(weapon_nft.owner_of(token_id) == player2, 'transfer failed');
}
After implementing tokens:
dojo-test skilldojo-deploy skilldojo-client skill)dojo-world skill)This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.