参考资料:
主要区别
特性 | sui::token |
sui::coin |
---|---|---|
系统类型 | 闭环系统 | 开环系统 |
权限控制 | 双层(TokenPolicyCap + TreasuryCap) | 单层(TreasuryCap) |
操作验证 | 必须通过策略规则验证 | 无需验证 |
代币转移 | 需要创建和确认ActionRequest | 直接转移 |
供应量控制 | 更精细(支持暂存余额) | 直接增减 |
灵活性 | 支持自定义规则和操作 | 固定操作集 |
适用场景 | 监管严格的应用 | 通用代币 |
结构区别

API区别

Coin - Open-loop fungible token
💡含义: 可以自由转让,能和其他Coin实现互换,自由流动,key+store(可包装)。类似于rmb,拥有者可以自由地支配他
1 | struct Coin<phantom T> has key, store { id: UID, balance: Balance<T> } |
一般称 Coin 为“币”,称 Token 为 “代币”
怎么理解可包装
1
2
3
4
5
6
7
8 struct Foo has key {
id: UID,
bar: Bar,
}
struct Bar has store {
value: u64,
}
使用
具体使用可以结合官方仓库中sui::coin的模块进行学习
创建代币:采用coin::create_currency,返回一个treasury
和metadate
1 | let (treasury_cap, metadata) = coin::create_currency<HOH>( |
metadate
主要包括一个coin所拥有的字节public struct CoinMetadata<phantom T> has key, store { id: UID, ///定义了如何将链上存储的整数值 N 转换为用户友好的小数格式(如 UI 显示或交易所报价) ///decimals 仅用于显示逻辑,链上存储的 value 始终是整数 decimals: u8, name: string::String, /// 代币符号 symbol: ascii::String, description: string::String, /// 代币图片 icon_url: Option<Url> } //为什么需要 decimals? //1.不同代币可能有不同的最小单位(如 BTC 用 8 位小数,USDC 用 6 位)。//decimals 确保前端能正确解析。 //2.避免浮点数问题 //链上只存整数(如 7002),通过 decimals 在链下转换为小数,避免浮点数计算的复杂性。 //3.兼容性 //与 ERC-20 等标准的小数位设计一致,减少跨链或跨协议时的适配成本
TreasuryCap
代表了一个代币的mint和burn权限,通常由项目方或者治理人拥有,用于初始代币分发、增发代币、代币销毁、代币供应量管理等public struct TreasuryCap<phantom T> has key, store { id: UID, ///代币的总供应量 total_supply: Supply<T> }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
一个比较完整Coin示例如下:
```move
module my_coin::my_coin {
use sui::coin::{Self, TreasuryCap, Coin};
use sui::transfer;
use sui::tx_context::TxContext;
// 1. 定义代币类型(需 one-time witness 模式)
struct MY_COIN has drop {}
// 2. 初始化函数(设置初始供应量)
public entry fun init(
ctx: &mut TxContext
) {
let (treasury_cap, metadata) = coin::create_currency(
MY_COIN {}, // one-time witness
9, // decimals(例如 9 位小数)
b"MYC", // symbol
b"My Coin", // name
b"My custom coin", // description
none(), // icon_url(可选)
ctx
);
// 3. 设置初始供应量:铸造 1_000_000_000 个代币(假设 1 个整单位 = 1 MYC)
let total_supply = 1_000_000_000;
let initial_coins = coin::mint(&mut treasury_cap, total_supply, ctx);
// 4. 将初始代币发送到指定地址(例如项目方地址)
let recipient = @0x123; // 替换为实际地址
transfer::public_transfer(initial_coins, recipient);
// 5. 转移 TreasuryCap 到安全地址(如治理合约)
transfer::public_transfer(treasury_cap, @0x456);
}
}
Token - Closed-Loop Token
💡含义:当你相对用户的权限做一些限制的时候,可以发布一个Token,制定自己的代币经济模型,来限制可以使用代币的应用程序,以及转账、消费和交换的规则;类似于创建一个受到限制和监控的银行账户
1 | struct Token<phantom T> has key { id: UID, balance: Balance<T> } |
🔦与Coin不同:Token 则仅具备key
功能,无法被包装、存储为动态字段或自由转账(除非有自定义策略)。由于此限制,Token只能由账户拥有,无法存储在应用程序中,但可以“spend”,这个下文会讲到。
重要模块:TokenPolicyCap
TreasuryCap
ActionRequest
TokenPolicyCap
:定义Token
的操作规则。它包含了哪些操作是允许的,以及每个操作需要满足的规则集合public struct TokenPolicy<phantom T> has key { id: UID, // `TokenPolicy` 的唯一标识符。 spent_balance: Balance<T>, // 用户在 "spend" 操作中消耗的余额。 rules: VecMap<String, VecSet<TypeName>>, // 定义每个操作的规则集合。 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- `TreasuryCap`:这里和Coin模块中的是一致的,不过多解释
- `ActionRequest`:每一个受监管的行为发生的时候,都会发出一个`ActionRequest`,同时ActionRequest需要一个`confirm_request`来解决 ,在 `confirm_request` 或 `confirm_request_mut` 中,`ActionRequest` 会与 `TokenPolicy` 的规则进行匹配,确保操作符合策略
- ```move
public struct ActionRequest<phantom T> {
name: String, // 操作的名称,例如 "transfer" 或 "spend"。
amount: u64, // 操作涉及的金额。
sender: address, // 操作的发起者地址。
recipient: Option<address>, // 接收者地址,仅在 "transfer" 操作中存在。
spent_balance: Option<Balance<T>>, // 操作中消耗的余额,仅在 "spend" 操作中存在。
approvals: VecSet<TypeName>, // 已完成规则的批准集合。
}
💡Hot_Potato
ActionRequest是一个没有任何能力的结构体也称作”Hot-Potato”,它涉及一个没有任何能力的结构体,称为 “hot potato”。这种结构体只能在其模块内进行打包和解包。Hot Potato Pattern 利用 PTB(Programmatic Transaction Block)机制,通常用于在交易结束之前强制用户完成特定业务逻辑的场景。
简单来说,如果一个交易命令 A 返回一个 hot potato 值,则必须在同一个 PTB 中的任何后续命令 B 中消费它。最常见的用例是闪电贷(flashloan)。
典型工作流程
初始化:
1
2
3
4let (policy, policy_cap) = token::new_policy(&treasury_cap, ctx);
token::share_policy(policy);
/// 创建一个新的 `TokenPolicy` 和一个匹配的 `TokenPolicyCap`。
/// 然后必须使用 `share_policy` 方法共享 `TokenPolicy`。设置规则:
1
2
3
4///允许transfer操作可以自由使用
token::allow(&mut policy, &policy_cap, b"transfer", ctx);
///为transfer操作添加一个规则
token::add_rule_config(rule_witness, &mut policy, &policy_cap, config, ctx);执行操作:
1
2let request = token::transfer(token, recipient, ctx);
let (_, amount, sender, _) = token::confirm_request(&policy, request, ctx);
Public - 公共操作
Token有一系列公共和保护操作用来管理代币。公共操作对所有人可用,不需要任何授权。
- token::keep - 将代币发送给交易发送者
- token::join - 合并两个代币
- token::split - 将一个代币分成两个,指定分割的金额
- token::zero - 创建一个空(零余额)的代币
- token::destroy_zero - 销毁一个零余额的代币
Protected - 受监管的操作
注:默认是禁用的,可以在TokenPolicy中启用,并自定义规则
保护操作是那些发出ActionRequest的操作。有三种主要的方法来解决ActionRequest,最常见的是通过TokenPolicy。
- token::transfer - 将代币转账到指定地址
- token::to_coin - 将token转换为coin
- token::from_coin - 将coin转换为token
- token::spend - 在指定地址花费代币
Spend
💡含义: 由于Token
类型不具备这种store
能力,因此无法将它们存储在另一个对象中。因此,Coin
类似 的消费方式无法实现——接受Token
付款的应用程序无法将其添加到其余额中。为了解决这个问题,Token
有一种spend
方法,允许在一个应用程序中消费它,然后将其作为 spent_balance交付TokenPolicy
或者立即使用 TreasuryCap`进行销毁。
使用
解构 Token
,提取其唯一标识符和余额 (id
和 balance
),删除 Token
对象后,调用 new_request
创建 ActionRequest
1 | public fun spend<T>(token: Token<T>, ctx: &mut TxContext): ActionRequest<T>; |
- 实现
Token
的消费逻辑:spend
函数允许用户销毁Token
,并将其余额记录为消费余额。- 这是
Token
模块中一个重要的操作,支持多种应用场景。
- 与
TokenPolicy
的规则集成:spend
操作必须符合TokenPolicy
的规则,确保消费操作受到严格控制。- 通过规则验证机制,可以灵活定义哪些消费操作是允许的。
- 支持供应量管理:
- 消费的余额会被记录在
TokenPolicy.spent_balance
中。 - 只有
TreasuryCap
的所有者可以通过flush
函数减少供应量,从而实现对Token
总供应量的精细管理。
- 消费的余额会被记录在