0%

Coin & Token 在使用中的一些区别

参考资料:

主要区别

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

结构区别

CoinBalanceToken

API区别

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,返回一个treasurymetadate

1
2
3
4
5
6
7
8
9
let (treasury_cap, metadata) = coin::create_currency<HOH>(
otw,
DECIMALS,
SYMBOLS,
NAME,
DESCRIPTION,
option::some(new_unsafe_from_bytes(ICON_URL)),
ctx
);
  • 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. 初始化:

    1
    2
    3
    4
    let (policy, policy_cap) = token::new_policy(&treasury_cap, ctx);
    token::share_policy(policy);
    /// 创建一个新的 `TokenPolicy` 和一个匹配的 `TokenPolicyCap`。
    /// 然后必须使用 `share_policy` 方法共享 `TokenPolicy`。
  2. 设置规则:

    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);
  3. 执行操作:

    1
    2
    let 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,提取其唯一标识符和余额 (idbalance),删除 Token 对象后,调用 new_request 创建 ActionRequest

1
public fun spend<T>(token: Token<T>, ctx: &mut TxContext): ActionRequest<T>;
  1. 实现 Token 的消费逻辑:
    • spend 函数允许用户销毁 Token,并将其余额记录为消费余额。
    • 这是 Token 模块中一个重要的操作,支持多种应用场景。
  2. TokenPolicy 的规则集成:
    • spend 操作必须符合 TokenPolicy 的规则,确保消费操作受到严格控制。
    • 通过规则验证机制,可以灵活定义哪些消费操作是允许的。
  3. 支持供应量管理:
    • 消费的余额会被记录在 TokenPolicy.spent_balance 中。
    • 只有 TreasuryCap 的所有者可以通过 flush 函数减少供应量,从而实现对 Token 总供应量的精细管理。