由於 SUI 的合約機制跟 EVM 所使用的技術概念和機制都不同,這篇文章將會介紹如何在 SUI Chain 上發行代幣。
關於 SUI 本身的技術與介紹可以參考 Messari深度報告:Sui 技術優勢在哪?撐出 L1 公鏈新天地 或是 SUI 的官網都有細節的技術介紹,比較特別的是在 SUI 上面都是以 object 作為一個單位與中心進行交互,文中有一段很簡單的介紹:
以物件為中心的資料模型
與其他分散式帳本相區別的關鍵特性是 Sui 的以物件為中心的資料模型。大多數智能合約平臺,如以太坊、Solana 和 Aptos,使用帳戶來追蹤區塊鏈的狀態,其中帳戶是儲存使用者餘額的資料結構。其他平臺如比特幣和 Cardano 使用未消費交易輸出(UTXO)來記錄區塊鏈的狀態,也就是說,UTXO 代表了在交易執行後剩餘的資產數量。
Sui 將這兩種方法結合成一種混合模型,其中其歷史儲存在具有全域性唯一 ID 的物件中。物件還包含元資料,用於確定不同物件的特性,如所有權和交易歷史(部分來源於物件隨機數值,也稱為版本號)。Sui 的以物件為中心資料模型意味著全域性狀態只是所有 Sui 物件的集合。從結構上講,這採用了有向無環圖(DAG)的形式,其中物件對應於頂點,交易對應於邊,稱為 「活動物件」 的物件對應於沒有出邊的頂點。
在 Sui 中,所有交易都將物件作為輸入,並生成新的或修改後的物件作為輸出。每個物件都包含產生它的最後一筆交易的hash值。可用作輸入的物件稱為 「活動」 物件。因此,通過觀察所有活動物件,可以確定全域性狀態。
技術實作
接下來將會介紹如何在 SUI Chain 上發行代幣(Token),Sui wallet 與如何透過 faucet 領取 SUI 這邊就不多作介紹。
- https://docs.sui.io/build/install#prerequisites
- https://docs.sui.io/build/install#install-sui-binaries
這邊用 devnet 作為測試:
$ cargo install --locked --git https://github.com/MystenLabs/sui.git --branch devnet sui
VSCode 整合
https://docs.sui.io/build/install#integrated-development-environment
$ cargo install --git https://github.com/move-language/move move-analyzer --branch sui-move --features "address32"
模塊
開啟模塊
$ sui move new learnsui
建立 move 合約
$ cd learnsui
$ touch /sources/yishconin.move
實作官方的 coin module: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#module-0x2coin
https://examples.sui.io/samples/coin.html
// This is an example module that creates a new cryptocurrency called YISHCOIN. It uses the Sui coin
// standard to do so. This module code was inspired by the Sui Move by Example book
// (https://examples.sui.io/samples/coin.html)
//
// coin module: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#module-0x2coin
module learnsui::yishcoin {
use std::option;
use sui::coin; // https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md
use sui::transfer; // https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/transfer.md
use sui::url::{Self, Url}; // https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/url.md
use sui::tx_context::{Self, TxContext}; // https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/tx_context.md
struct YISHCOIN has drop {}
/// Module initializer is called once on module publish. A treasury cap is sent to the
/// publisher, who then controls minting and burning
//
// coin::create_currency(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#function-create_currency
// transfer::public_freeze_object(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/transfer.md#function-public_freeze_object
// transfer::public_transfer(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/transfer.md#function-public_transfer
fun init( witness: YISHCOIN, ctx: &mut TxContext) {
// Function interface: public fun create_currency<T: drop>(witness: T, decimals: u8, symbol: vector<u8>, name: vector<u8>, description: vector<u8>, icon_url: option::Option<url::Url>, ctx: &mut tx_context::TxContext): (coin::TreasuryCap<T>, coin::CoinMetadata<T>)
let (treasuryCap, metadata) = coin::create_currency(
/*witnes=*/witness,
/*decimals=*/6,
/*symbol=*/b"YISHCOIN",
/*name=*/b"Yish Coin",
/*description=*/b"This is about Yish Coin on Sui move",
/*icon_url=*/option::some<Url>(url::new_unsafe_from_bytes(b"https://yish.dev/512.png")),
/*ctx=*/ctx
);
// Freezes the object. Freezing the object means that the object:
// - Is immutable
// - Cannot be transferred
//
// Note: transfer::freeze_object() cannot be used since CoinMetadata is defined in another
// module
transfer::public_freeze_object(metadata);
// Send the TreasuryCap object to the publisher of the module
//
// Note: transfer::transfer() cannot be used since TreasuryCap is defined in another module
transfer::public_transfer(treasuryCap, tx_context::sender(ctx))
}
// This function is an example of how internal_mint_coin() can be used.
//
// Note that there is coin::mint_and_transfer but this examples shows how
// transfer::public_transfer works
//
// coin::mint_and_transfer(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#function-mint_and_transfer
// transfer::public_transfer(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/transfer.md#function-public_transfer
public entry fun mint
(
cap: &mut coin::TreasuryCap<learnsui::yishcoin::YISHCOIN>,
recipient: address,
value: u64,
ctx: &mut tx_context::TxContext
)
{
// mint the new coin with the given value
let new_coin = internal_mint_coin(cap, value, ctx);
// transfer the new coin to the recipient
transfer::public_transfer(new_coin, recipient);
}
// This function is an example of how internal_burn_coin() can be used.
public entry fun burn
(
cap: &mut coin::TreasuryCap<learnsui::yishcoin::YISHCOIN>,
coin: coin::Coin<learnsui::yishcoin::YISHCOIN>
)
{
// Burn the coin
//
// Note: internal_burn_coin returns a u64 but it can be ignored since u64 has drop
internal_burn_coin(cap, coin);
}
// This is the internal mint function. This function uses the Coin::mint function to create and
// return a new Coin object containing a balance of the given value
//
// coin::mint(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#function-mint
fun internal_mint_coin
(
cap: &mut coin::TreasuryCap<learnsui::yishcoin::YISHCOIN>,
value: u64,
ctx: &mut tx_context::TxContext
): coin::Coin<learnsui::yishcoin::YISHCOIN>
{
coin::mint(cap, value, ctx)
}
// This is the internal burn function. This function uses the Coin::burn function to take a coin
// and destroy it. The function returns the value of the coin that was destroyed.
//
// coin::burn(): https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#function-burn
fun internal_burn_coin
(
cap: &mut coin::TreasuryCap<learnsui::yishcoin::YISHCOIN>,
coin: coin::Coin<learnsui::yishcoin::YISHCOIN>
): u64
{
coin::burn(cap, coin)
}
}
測試合約
$ sui move test
構建合約
$ sui move build
到這邊合約的部分已經完成,接下來進入配置 client 與地址配置、部屬到鏈上運行環節。
生成/佈署
$ sui client addresses
Config file ["/Users/yish/.sui/sui_config/client.yaml"] doesn't exist, do you want to connect to a Sui Full node server [y/N]?y
Sui Full node server URL (Defaults to Sui Devnet if not specified) :
# 這邊選擇 ed25519 方便後續新增到 sui wallet 查看與轉移
# 要把相關註記詞和內容記錄下來
Select key scheme to generate keypair (0 for ed25519, 1 for secp256k1, 2: for secp256r1):
0
Generated new keypair for address with scheme "ed25519" [0xqxokzcw3r3nur3st6gmmqf7kx5jcdokqvsnd7unjvdtxx6vuevnrqm5rtvn5urok]
Secret Recovery Phrase : [hello john doe ...]
╭───────────────┬──────────────────────────────────────────────────────────────────────────╮
│ activeAddress │ 0xqxokzcw3r3nur3st6gmmqf7kx5jcdokqvsnd7unjvdtxx6vuevnrqm5rtvn5urok │
│ addresses │ ╭──────────────────────────────────────────────────────────────────────╮ │
│ │ │ 0xqxokzcw3r3nur3st6gmmqf7kx5jcdokqvsnd7unjvdtxx6vuevnrqm5rtvn5urok │ │
│ │ ╰──────────────────────────────────────────────────────────────────────╯ │
╰───────────────┴──────────────────────────────────────────────────────────────────────────╯
相關對應 client 指令都可以在裡面找到:
$ sui client -h
取得需要的 gas fee:
curl --location --request POST 'https://faucet.devnet.sui.io/gas' \
--header 'Content-Type: application/json' \
--data-raw '{"FixedAmountRequest":{"recipient":"0xqxokzcw3r3nur3st6gmmqf7kx5jcdokqvsnd7unjvdtxx6vuevnrqm5rtvn5urok"}}'
確保有足夠的 gas fee,這邊會注意到 gasCoinId 跟原本地址不同,等等會需要用到這個 gasCoinId:
sui client gas
╭────────────────────────────────────────────────────────────────────┬─────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼─────────────┤
│ 0xephada5juljryk0xou3hlpx13trcs2hlbe9abzoz9k4zz3zhiivohhm0byhqcccf │ 10000000000 │
╰────────────────────────────────────────────────────────────────────┴─────────────╯
發布合約到鏈上,這邊可以看到要填入 gasCoinId 並且配置 gas budget,devnet 配置最高即可:
$ sui client publish learnsui --gas 0xephada5juljryk0xou3hlpx13trcs2hlbe9abzoz9k4zz3zhiivohhm0byhqcccf --gas-budget 100000000 --skip-dependency-verification
會出現很多細節資訊以及對應的內容,這邊都先記錄下來。
接著列出 object 清單,確保 Coin / TreasuryCap / UpgradeCap 已經存在於合約中:
$ sui client objects
╭───────────────────────────────────────────────────────────────────────────────────────╮
│ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ objectId │ 0xvrt0b5onpepysv4jwh0hfqdc30knavvuqxrgjubtt5dep1bhe416gfre0p78scpl │ │
│ │ version │ 18 │ │
│ │ digest │ 77alFykMvAlHGwwU99y8peqdlxERw7o5F14OQluYrl5t │ │
│ │ objectType │ 0x0000..0002:🪙:Coin │ │
│ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │
│ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ objectId │ 0xka8ngiqfod8twbugwppayfyhv2uq3rl9tvjzkx7rshm81zouaxyzq4cdjm6fh9x8 │ │
│ │ version │ 18 │ │
│ │ digest │ u8lLD9Box1R1qrmAKNutW1vLGtswgT1sAPv1cXBcAilZ │ │
│ │ objectType │ 0x0000..0002:🪙:TreasuryCap │ │
│ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │
│ ╭────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ objectId │ 0xqpgzictudtmnhtybuduckaeaa0kro5d1zjzv6dgvycjneg5dhgm6ezvnqcwusyew │ │
│ │ version │ 18 │ │
│ │ digest │ bYCnj95JH4bGqwWpoTkQXQCwGQFV8VKBialCd2OWtE7J │ │
│ │ objectType │ 0x0000..0002:📦:UpgradeCap │ │
│ ╰────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰───────────────────────────────────────────────────────────────────────────────────────╯
接著在 sui wallet 登入剛剛生成的 address,查找 transaction 是否有成功,並且 view on explorer 查看一下:
查找 TreasuryCap 相關內容等等 mint and transfer 需要用到:
$ sui client object 0xka8ngiqfod8twbugwppayfyhv2uq3rl9tvjzkx7rshm81zouaxyzq4cdjm6fh9x8
╭───────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ objectId │ 0x7cbf02bedf3dbdafa9c4f8cedd6adba4546fda6544d03be7d3f23121936177b4 │
│ version │ 20 │
│ digest │ 86xumhjUgcyo56wDMFmiHvQMY8VJ5VeHLMjP5NZ6Gr3T │
│ objType │ 0x2:🪙:TreasuryCap<0x6bc1ddbad255ec35e7a73850b729275ef591a8528e4ab0c67761fcd4fcfc314f::yishcoin::YISHCOIN> │
│ ownerType │ AddressOwner │
│ prevTx │ FZrCVJJvk4ci3xEjaFMa6Bfp4wr3WqBAStZtYpkdhKg7 │
│ storageRebate │ 1763200 │
│ content │ ╭───────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ │ dataType │ moveObject │ │
│ │ │ type │ 0x2:🪙:TreasuryCap<0xgeqngwm1empj8kfozwcqeunkg98yrgr4ty0a0ewpcqy6o7gcwmhpdvycjlsauhij::yishcoin::YISHCOIN> │ │
│ │ │ hasPublicTransfer │ true │ │
│ │ │ fields │ ╭──────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │
│ │ │ │ │ id │ ╭────┬──────────────────────────────────────────────────────────────────────╮ │ │ │
│ │ │ │ │ │ │ id │ 0x7cbf02bedf3dbdafa9c4f8chsyjiola1246fda6544d03be7d3f23121936177b4 │ │ │ │
│ │ │ │ │ │ ╰────┴──────────────────────────────────────────────────────────────────────╯ │ │ │
│ │ │ │ │ total_supply │ ╭────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │
│ │ │ │ │ │ │ type │ 0x2::balance::Supply<0xgeqngwm1empj8kfozwcqeunkg98yrgr4ty0a0ewpcqy6o7gcwmhpdvycjlsauhij::yishcoin::YISHCOIN> │ │ │ │
│ │ │ │ │ │ │ fields │ ╭───────┬─────────────────╮ │ │ │ │
│ │ │ │ │ │ │ │ │ value │ 8958888888888 │ │ │ │ │
│ │ │ │ │ │ │ │ ╰───────┴─────────────────╯ │ │ │ │
│ │ │ │ │ │ ╰────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │ │
│ │ │ │ ╰──────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │ │
│ │ ╰───────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰───────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
接著到合約內部開始進行 mint 和 transfer token to someone:
可以參考官方 Coin module implementation: https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#function-mint
public fun mint<T>(cap: &mut coin::TreasuryCap<T>, value: u64, ctx: &mut tx_context::TxContext): coin::Coin<T>
- Type0: 填入 0xgeqngwm1empj8kfozwcqeunkg98yrgr4ty0a0ewpcqy6o7gcwmhpdvycjlsauhij::yishcoin::YISHCOIN 指定 TreasuryCap type
- Arg0: 填入 TreasuryCap objectId: 0xka8ngiqfod8twbugwppayfyhv2uq3rl9tvjzkx7rshm81zouaxyzq4cdjm6fh9x8
- Arg1: 填入 mint 數量:800000000
- Arg2: 填入 recipient 的 address:0x0028f0b1e2a045163ceaee65ce54b54b820e610cf99463cd909221360f985eda
執行後會顯示如下:
接著就可以到鏈上查找交易數據: https://suiscan.xyz/devnet/coin/0x6bc1ddbad255ec35e7a73850b729275ef591a8528e4ab0c67761fcd4fcfc314f::yishcoin::YISHCOIN/txs
後記
至此整個在 SUI 上發行 token 的流程就完成了,當然還有很多細節需要再去深入理解,作為一條新起的 L1,技術上來說確實是很超前的,也歸功於過去 Diem 以及創始人的技術積累,但就如同其他公鏈一樣,其敘事能力跟技術是要同時一起成長的,未來將會持續觀察。
參考
- https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/docs/coin.md#0x2_coin_mint
- https://github.com/MoveStudioIDE/get-movin/blob/main/sui-coins/sources/mycoin.move
- https://www.youtube.com/watch?v=6-yaql5cGS8&ab_channel=MoveStudioIDE
- https://github.com/MystenLabs/awesome-move
- https://docs.sui.io/learn/sui-bridging
- https://wormhole.com/
- https://suiscan.xyz/mainnet/home
- https://docs.sui.io/testnet/explore/examples
- https://examples.sui.io/samples/coin.html
- https://suiscan.xyz/mainnet/coins
- https://medium.com/@ultrasoundchad/smart-contracts-on-the-sui-blockchain-2e4608028bf7