重要前提
安装AI Skills的关键前提是:必须科学上网,且开启TUN模式,这一点至关重要,直接决定安装能否顺利完成,在此郑重提醒三遍:科学上网,科学上网,科学上网。查看完整安装教程 →
dojo-token by dojoengine/book
npx skills add https://github.com/dojoengine/book --skill dojo-token在 Cairo 中实现 ERC20/ERC721 代币,将其与你的 Dojo 世界一同部署,并配置 Torii 以索引余额、转账和元数据。
sozo migrate 将代币合约作为外部合约部署添加到 Scarb.toml:
[dependencies]
origami_token = { git = "https://github.com/dojoengine/origami", tag = "v1.0.0" }
Origami 提供了遵循标准接口的可重用代币组件。请参阅 Origami 文档 获取最新的 API。
你可以使用标准的 Dojo 模型(无需 Origami)来实现代币。
#[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")
}
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
#[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")
}
}
}
发出事件,以便 Torii 和客户端可以跟踪代币操作:
#[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 });
代币合约作为外部合约与你的 Dojo 世界一起部署。有关通用部署工作流,请参阅 dojo-deploy 技能。
[[target.starknet-contract]]
build-external-contracts = [
"dojo::world::world_contract::world",
"tokens::models::m_ERC20Token"
]
在 dojo_dev.toml 中,将代币定义为外部合约:
[[external_contracts]]
contract_name = "ERC20Token"
instance_name = "GoldToken"
salt = "1"
constructor_data = [
"str:Gold Coin", # 代币名称
"sstr:GOLD", # 符号
"u256:1000000000000000000", # 总供应量 (1e18)
"0x1234567890abcdef..." # 所有者地址
]
为更多代币添加额外的 [[external_contracts]] 块。使用 sozo build && sozo migrate 进行部署。注意输出中的合约地址——你需要它们来配置 Torii。
Torii 将 ERC 代币合约与 Dojo 世界状态分开索引。你必须明确告诉 Torii 要监视哪些合约。有关通用 Torii 配置,请参阅 dojo-indexer 技能。
使用 ERC20: 或 ERC721: 前缀将代币合约添加到 [indexing]:
# torii.toml
[indexing]
contracts = [
"ERC20:0xYOUR_GOLD_TOKEN_ADDRESS",
"ERC721:0xYOUR_WEAPON_NFT_ADDRESS",
]
或通过 CLI:
torii --world 0xYOUR_WORLD \
--indexing.contracts "ERC20:0xGOLD_TOKEN" \
--indexing.contracts "ERC721:0xWEAPON_NFT"
你也可以索引网络上的知名代币:
[indexing]
contracts = [
"ERC20:0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", # ETH
"ERC20:0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", # STRK
]
索引完成后,可以使用三个数据库表:
tokens — 元数据(名称、符号、小数位)balances — 每个账户的余额erc_transfers — 转账历史SELECT * FROM tokens;
SELECT * FROM balances WHERE account_address = '0xPLAYER';
SELECT * FROM erc_transfers ORDER BY rowid DESC LIMIT 20;
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>
);
}
ERC20: 与 ERC721:)每周安装数
56
代码库
GitHub 星标数
53
首次出现
2026年1月30日
安全审计
安装于
codex52
opencode52
kimi-cli50
gemini-cli50
cursor50
github-copilot49
Implement ERC20/ERC721 tokens in Cairo, deploy them alongside your Dojo world, and configure Torii to index balances, transfers, and metadata.
sozo migrateAdd 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.
You can implement tokens using standard Dojo models without Origami.
#[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")
}
}
}
#[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")
}
}
}
Emit events so Torii and clients can track token operations:
#[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 });
Token contracts are deployed as external contracts alongside your Dojo world. See dojo-deploy skill for general deployment workflow.
[[target.starknet-contract]]
build-external-contracts = [
"dojo::world::world_contract::world",
"tokens::models::m_ERC20Token"
]
In dojo_dev.toml, define the token as an external contract:
[[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 [[external_contracts]] blocks for additional tokens. Deploy with sozo build && sozo migrate. Note the contract addresses from the output — you need them for Torii.
Torii indexes ERC token contracts separately from Dojo world state. You must explicitly tell Torii which contracts to watch. See dojo-indexer skill for general Torii configuration.
Add token contracts to [indexing] using the ERC20: or ERC721: prefix:
# torii.toml
[indexing]
contracts = [
"ERC20:0xYOUR_GOLD_TOKEN_ADDRESS",
"ERC721:0xYOUR_WEAPON_NFT_ADDRESS",
]
Or via CLI:
torii --world 0xYOUR_WORLD \
--indexing.contracts "ERC20:0xGOLD_TOKEN" \
--indexing.contracts "ERC721:0xWEAPON_NFT"
You can also index well-known tokens on the network:
[indexing]
contracts = [
"ERC20:0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", # ETH
"ERC20:0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", # STRK
]
Once indexed, three database tables become available:
tokens — metadata (name, symbol, decimals)balances — per-account balanceserc_transfers — transfer historySELECT * FROM tokens;
SELECT * FROM balances WHERE account_address = '0xPLAYER';
SELECT * FROM erc_transfers ORDER BY rowid DESC LIMIT 20;
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>
);
}
ERC20: vs ERC721:)Weekly Installs
56
Repository
GitHub Stars
53
First Seen
Jan 30, 2026
Security Audits
Gen Agent Trust HubWarnSocketPassSnykWarn
Installed on
codex52
opencode52
kimi-cli50
gemini-cli50
cursor50
github-copilot49
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
123,700 周安装
gws-keep 命令行工具:Google Keep API 客户端,管理笔记与附件
1 周安装
Gmail邮件转发命令行工具 - gws-gmail-forward使用指南与教程
1 周安装
GWS Gmail CLI 工具:命令行管理 Gmail 邮件、发送、回复与监控
1 周安装
DOCX文件处理全攻略:创建、编辑、分析.docx文档的实用技术指南
57 周安装
gws-events-subscribe:命令行工具订阅Google Workspace事件流(NDJSON输出)
1 周安装
gws-drive-upload:命令行工具快速上传文件到Google Drive并自动添加元数据
1 周安装