dojo-token
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseDojo Tokens
Dojo代币
Implement ERC20/ERC721 tokens in Cairo, deploy them alongside your Dojo world, and configure Torii to index balances, transfers, and metadata.
在Cairo中实现ERC20/ERC721代币,将其与Dojo世界一同部署,并配置Torii以索引余额、转账记录及元数据。
When to Use This Skill
适用场景
- "Implement ERC20 token for game currency"
- "Create NFT items with ERC721"
- "Deploy an ERC20 token with my world"
- "Index token balances with Torii"
- "Query token transfers"
- "Use Origami for tokens"
- "为游戏货币实现ERC20代币"
- "使用ERC721创建NFT道具"
- "与我的世界一同部署ERC20代币"
- "使用Torii索引代币余额"
- "查询代币转账记录"
- "使用Origami处理代币"
What This Skill Does
技能功能
- Implement ERC20 fungible tokens and ERC721 NFTs in Cairo
- Deploy token contracts as external contracts via
sozo migrate - Configure Torii to index token balances, transfers, and metadata
- Query token data via SQL and client SDKs
- 在Cairo中实现ERC20 fungible代币和ERC721 NFT
- 通过将代币合约作为外部合约部署
sozo migrate - 配置Torii以索引代币余额、转账记录及元数据
- 通过SQL和客户端SDK查询代币数据
Using Origami Library
使用Origami库
Add to :
Scarb.tomltoml
[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.
在中添加:
Scarb.tomltoml
[dependencies]
origami_token = { git = "https://github.com/dojoengine/origami", tag = "v1.0.0" }Origami提供遵循标准接口的可复用代币组件。
请参考Origami文档获取最新API。
Simple Token Implementation
简单代币实现
You can implement tokens using standard Dojo models without Origami.
你可以不使用Origami,通过标准Dojo模型实现代币。
ERC20-like Currency
类ERC20货币
cairo
#[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")
}
}
}cairo
#[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
类ERC721 NFT
cairo
#[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")
}
}
}cairo
#[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")
}
}
}Token Events
代币事件
Emit events so Torii and clients can track token operations:
cairo
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct TokenTransferred {
#[key]
pub from: ContractAddress,
#[key]
pub to: ContractAddress,
pub amount: u256,
}
// Emit in your functions
world.emit_event(@TokenTransferred { from, to, amount });触发事件以便Torii和客户端追踪代币操作:
cairo
#[derive(Copy, Drop, Serde)]
#[dojo::event]
pub struct TokenTransferred {
#[key]
pub from: ContractAddress,
#[key]
pub to: ContractAddress,
pub amount: u256,
}
// 在函数中触发事件
world.emit_event(@TokenTransferred { from, to, amount });Deploying Token Contracts
部署代币合约
Token contracts are deployed as external contracts alongside your Dojo world.
See skill for general deployment workflow.
dojo-deploy代币合约作为外部合约与Dojo世界一同部署。
请参考技能了解通用部署流程。
dojo-deployAdd to Scarb.toml
添加至Scarb.toml
toml
[[target.starknet-contract]]
build-external-contracts = [
"dojo::world::world_contract::world",
"tokens::models::m_ERC20Token"
]toml
[[target.starknet-contract]]
build-external-contracts = [
"dojo::world::world_contract::world",
"tokens::models::m_ERC20Token"
]Configure in Profile
在配置文件中设置
In , define the token as an external contract:
dojo_dev.tomltoml
[[external_contracts]]
contract_name = "ERC20Token"
instance_name = "GoldToken"
salt = "1"
constructor_data = [
"str:Gold Coin", # Token name
"sstr:GOLD", # Symbol
"u256:1000000000000000000", # Total supply (1e18)
"0x1234567890abcdef..." # Owner address
]Add more blocks for additional tokens.
Deploy with .
Note the contract addresses from the output — you need them for Torii.
[[external_contracts]]sozo build && sozo migrate在中,将代币定义为外部合约:
dojo_dev.tomltoml
[[external_contracts]]
contract_name = "ERC20Token"
instance_name = "GoldToken"
salt = "1"
constructor_data = [
"str:Gold Coin", # 代币名称
"sstr:GOLD", # 符号
"u256:1000000000000000000", # 总供应量(1e18)
"0x1234567890abcdef..." # 所有者地址
]添加更多块以部署更多代币。
使用进行部署。
注意输出中的合约地址——你需要用它们配置Torii。
[[external_contracts]]sozo build && sozo migrateIndexing Tokens with Torii
使用Torii索引代币
Torii indexes ERC token contracts separately from Dojo world state.
You must explicitly tell Torii which contracts to watch.
See skill for general Torii configuration.
dojo-indexerTorii会将ERC代币合约与Dojo世界状态分开索引。
你必须明确告知Torii需要监控哪些合约。
请参考技能了解完整的Torii配置。
dojo-indexerConfiguration
配置
Add token contracts to using the or prefix:
[indexing]ERC20:ERC721:toml
undefined使用或前缀将代币合约添加至部分:
ERC20:ERC721:[indexing]toml
undefinedtorii.toml
torii.toml
[indexing]
contracts = [
"ERC20:0xYOUR_GOLD_TOKEN_ADDRESS",
"ERC721:0xYOUR_WEAPON_NFT_ADDRESS",
]
Or via CLI:
```bash
torii --world 0xYOUR_WORLD \
--indexing.contracts "ERC20:0xGOLD_TOKEN" \
--indexing.contracts "ERC721:0xWEAPON_NFT"You can also index well-known tokens on the network:
toml
[indexing]
contracts = [
"ERC20:0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", # ETH
"ERC20:0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", # STRK
][indexing]
contracts = [
"ERC20:0xYOUR_GOLD_TOKEN_ADDRESS",
"ERC721:0xYOUR_WEAPON_NFT_ADDRESS",
]
或通过CLI:
```bash
torii --world 0xYOUR_WORLD \
--indexing.contracts "ERC20:0xGOLD_TOKEN" \
--indexing.contracts "ERC721:0xWEAPON_NFT"你也可以索引网络上的知名代币:
toml
[indexing]
contracts = [
"ERC20:0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", # ETH
"ERC20:0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", # STRK
]Querying Token Data
查询代币数据
Once indexed, three database tables become available:
- — metadata (name, symbol, decimals)
tokens - — per-account balances
balances - — transfer history
erc_transfers
索引完成后,将有三个数据库表可用:
- —— 元数据(名称、符号、小数位数)
tokens - —— 账户余额
balances - —— 转账历史
erc_transfers
SQL
SQL
sql
SELECT * FROM tokens;
SELECT * FROM balances WHERE account_address = '0xPLAYER';
SELECT * FROM erc_transfers ORDER BY rowid DESC LIMIT 20;sql
SELECT * FROM tokens;
SELECT * FROM balances WHERE account_address = '0xPLAYER';
SELECT * FROM erc_transfers ORDER BY rowid DESC LIMIT 20;JavaScript SDK
JavaScript SDK
typescript
import { useTokens } from "@dojoengine/sdk/react";
function TokenBalance({ address }: { address: string }) {
const { tokens, getBalance, toDecimal } = useTokens({
accountAddresses: [address],
});
return (
<div>
{tokens.map((token, idx) => (
<div key={idx}>
{token.symbol}: {toDecimal(token, getBalance(token))}
</div>
))}
</div>
);
}typescript
import { useTokens } from "@dojoengine/sdk/react";
function TokenBalance({ address }: { address: string }) {
const { tokens, getBalance, toDecimal } = useTokens({
accountAddresses: [address],
});
return (
<div>
{tokens.map((token, idx) => (
<div key={idx}>
{token.symbol}: {toDecimal(token, getBalance(token))}
</div>
))}
</div>
);
}Troubleshooting
故障排除
"Empty tokens/balances tables"
"Empty tokens/balances tables"
- Verify the contract address matches what was deployed
- Check the prefix is correct (vs
ERC20:)ERC721: - Ensure the contract implements standard ERC Transfer events
- 验证合约地址与部署的地址一致
- 检查前缀是否正确(vs
ERC20:)ERC721: - 确保合约实现了标准的ERC Transfer事件
"Token not showing in Torii"
"Token not showing in Torii"
- Restart Torii after adding new contracts
- Check Torii logs for indexing errors
- 添加新合约后重启Torii
- 查看Torii日志中的索引错误
"Balance shows 0"
"Balance shows 0"
- Tokens are indexed from transfer events, not storage reads
- Mint or transfer tokens to generate events Torii can index
- 代币通过转账事件索引,而非直接读取存储
- 进行代币铸造或转账操作,生成Torii可索引的事件
Related Skills
相关技能
- dojo-model: Token models extend Dojo models
- dojo-system: Token logic in systems
- dojo-test: Test token operations
- dojo-deploy: General world deployment workflow
- dojo-indexer: Full Torii configuration and queries
- dojo-client: Client SDK integration
- dojo-model: Token models extend Dojo models
- dojo-system: Token logic in systems
- dojo-test: Test token operations
- dojo-deploy: General world deployment workflow
- dojo-indexer: Full Torii configuration and queries
- dojo-client: Client SDK integration