sui cli 详细讲解
Usage: sui <COMMAND>
Commands:
start Start sui network
network
genesis Bootstrap and initialize a new sui network
genesis-ceremony
keytool Sui keystore tool
console Start Sui interactive console
client Client for interacting with the Sui network
validator A tool for validators and validator candidates
move Tool to build and test Move applications
bridge-committee-init Command to initialize the bridge committee, usually used when running local bridge cluster
fire-drill Tool for Fire Drill
help Print this message or the help of the given subcommand(s)
Options:
-h, --help Print help
-V, --version Print version
Move
1 命令说明
sui move
的子命令集是用于构建和测试Move
合约的,以下将进行分类并逐一介绍命令的使用。
可通过help/-h
获取完整的子命令:
$ sui move -h
Tool to build and test Move applications
Usage: sui move [OPTIONS] <COMMAND>
Commands:
build
coverage Inspect test coverage for this package. A previous test run with the
`--coverage` flag must have previously been run
disassemble
manage-package Record addresses (Object IDs) for where this package is published on chain (this
command sets variables in Move.lock)
migrate Migrate to Move 2024 for the package at `path`. If no path is provided defaults
to current directory
new Create a new Move package with name `name` at `path`. If `path` is not provided
the package will be created in the directory `name`
test Run Move unit tests in this package
2 命令分类
根据命令实现功能分为以下几类:
2.1 项目类
2.2 测试类
2.3 迁移类
子命令 | 功能说明 |
---|---|
migrate | 老版合约语法迁移到Move 2024 新版语法 |
2.4 其它类
子命令 | 功能说明 |
---|---|
disassemble | 反汇编Move 字节码 |
manage-package | 记录包的发布地址 |
3 命令详解
3.1 项目类
3.1.1 new
: 创建Move
项目
(1) 命令说明
执行该命令可以更具指定名称创建一个新的Move
项目。
Create a new Move package with name `name` at `path`. If `path` is not provided the package will be
created in the directory `name`
Usage: sui move new [OPTIONS] <NAME>
Arguments:
<NAME> The name of the package to be created
(2) 命令使用
命令成功执行后,将会搭建起基本的项目工程。
$ sui move new simple_bank
$ tree simple_bank/
simple_bank/
├── Move.toml
├── sources
│ └── simple_bank.move
└── tests
└── simple_bank_tests.move
3.1.2 build
: 编译并构建Move
项目
(1) 命令说明
Usage: sui move build [OPTIONS]
(2) 命令使用
- 执行编译命令
$ sui move build
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING simple_bank
- 查看编译后输出的结果
可以看到生成的合约字节码文件:
simple_bank.mv
$ tree -L 3 build/
build/
├── locks
└── simple_bank
├── BuildInfo.yaml
├── bytecode_modules
│ ├── dependencies
│ └── simple_bank.mv
├── source_maps
│ ├── dependencies
│ └── simple_bank.mvsm
└── sources
├── dependencies
└── simple_bank.move
3.2 测试类
3.2.1 test
: 运行Move
项目的单元测试
(1) 命令说明
执行该命令可以运行当前Move
项目的单元测试。
Run Move unit tests in this package
Usage: sui move test [OPTIONS] [filter]
Arguments:
[filter] An optional filter string to determine which unit tests to run. A unit test will be run
only if it contains this string in its fully qualified
(<addr>::<module_name>::<fn_name>) name
(2) 命令使用
- 跑全量单元测试
$ sui move test
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING sui_marketplace
Running Move unit tests
[ PASS ] movefans::sui_marketplace_tests::test_add_item
[ PASS ] movefans::sui_marketplace_tests::test_add_item_failure
[ PASS ] movefans::sui_marketplace_tests::test_create_shop
[ PASS ] movefans::sui_marketplace_tests::test_purchase_item
[ PASS ] movefans::sui_marketplace_tests::test_purchase_item_failure
[ PASS ] movefans::sui_marketplace_tests::test_unlist_item
[ PASS ] movefans::sui_marketplace_tests::test_withdraw_from_shop
Test result: OK. Total tests: 7; passed: 7; failed: 0
- 跑指定单元测试
可以根据单元测试名称,跑指定单元测试
$ sui move test test_add_item
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING sui_marketplace
Running Move unit tests
[ PASS ] movefans::sui_marketplace_tests::test_add_item
[ PASS ] movefans::sui_marketplace_tests::test_add_item_failure
Test result: OK. Total tests: 2; passed: 2; failed: 0
3.2.2 coverage
: 查看测试覆盖率
(1) 命令说明
执行该命令可以分析当前项目的测试覆盖率。在之前,需要先运行sui test --coverage
命令,生成测试覆盖率报告。
注:需要使用
debug
模式Sui CLI
,方可使用--coverage
参数,否则会提示:The --coverage flag is currently supported only in debug builds. Please build the Sui CLI from source in debug mode.
Inspect test coverage for this package. A previous test run with the `--coverage` flag
must have previously been run
Usage: sui move coverage [OPTIONS] <COMMAND>
Commands:
summary Display a coverage summary for all modules in this package
source Display coverage information about the module against source code
bytecode Display coverage information about the module against disassembled bytecode
(2) 命令使用
- 执行测试命令,开启覆盖率分析
$ /data/sui/target/debug/sui move test --coverage
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING sui_marketplace
Running Move unit tests
[ PASS ] movefans::sui_marketplace_tests::test_add_item
[ PASS ] movefans::sui_marketplace_tests::test_add_item_failure
[ PASS ] movefans::sui_marketplace_tests::test_create_shop
[ PASS ] movefans::sui_marketplace_tests::test_purchase_item
[ PASS ] movefans::sui_marketplace_tests::test_purchase_item_failure
[ PASS ] movefans::sui_marketplace_tests::test_unlist_item
[ PASS ] movefans::sui_marketplace_tests::test_withdraw_from_shop
Test result: OK. Total tests: 7; passed: 7; failed: 0
- 命令成功执行,将会在当前目录生成以下文件
.trace
.coverage_map.mvcov
- 查看测试覆盖率汇总信息
根据输出内容可见该项目的单测覆盖率为:76.40%
$ sui move coverage summary
+-------------------------+
| Move Coverage Summary |
+-------------------------+
Module 0000000000000000000000000000000000000000000000000000000000000000::sui_marketplace
>>> % Module coverage: 76.40
+-------------------------+
| % Move Coverage: 76.40 |
+-------------------------+
- 查看源代码层面模块单测覆盖率
$ sui move coverage source --module sui_marketplace
将会输出如下信息,显式红色的代码片段表示未被单测覆盖
- 查看字节码层面模块单测覆盖率
$ sui move coverage bytecode --module sui_marketplace
3.3 迁移类
3.3.1 migrate
: 老版合约语法迁移到Move 2024
新版语法
SUI Move
在2024
年迎来重大更新,引入了许多新功能,涵盖新特性,例如:方法语法(method syntax
)、位置域(positional fields
)、循环标签(loop labels
)等,以及不兼容更新,例如:数据类型可见性(Datatype visibility requirements)、 可变性要求(Mutability requirements) 等。这些更新为Move
编程语言引入了新的定义数据和调用函数的方式等,使得在Sui
上构建应用程序更加高效灵活,也为未来要推出的新功能铺平道路。
如何快速将历史传统Move
合约迁移到Sui Move 2024
新版合约语法,使用该命令即可。
(1) 命令说明
执行该命令可以将历史传统Move
合约迁移到Sui Move 2024
新版合约语法。
Migrate to Move 2024 for the package at `path`. If no path is provided defaults to current directory
Usage: sui move migrate [OPTIONS]
(2) 命令使用
命令成功执行后,终端会显示要进行的更改的合约差异,如果接受更改,会自动将现存 历史版本(
legacy
) 的合约,迁移成 新版合约(2024.beta
) 代码,并会更新Move.toml
文件,同时也会生成一个migration.patch
文件,将变更差异记录在其中。 更多介绍可参考: https://blog.sui.io/move-2024-migration-guide/ https://learnblockchain.cn/article/7824
$ sui move migrate
UPDATING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
Package toml does not specify an edition. As of 2024, Move requires all packages to define a language edition.
Please select one of the following editions:
1) 2024.beta
2) legacy
Selection (default=1): 1
Would you like the Move compiler to migrate your code to Move 2024? (Y/n) y
Generated changes . . .
INCLUDING DEPENDENCY Sui
INCLUDING DEPENDENCY MoveStdlib
BUILDING simple_bank
The following changes will be made.
============================================================
--- ./sources/simple_bank.move
+++ ./sources/simple_bank.move
@@ -15 +15 @@
- struct SimpleBank has key {
+ public struct SimpleBank has key {
@@ -20 +20 @@
- struct EventDeposit has copy, drop {
+ public struct EventDeposit has copy, drop {
@@ -26 +26 @@
- struct EventWithdraw has copy, drop {
+ public struct EventWithdraw has copy, drop {
============================================================
Apply changes? (Y/n) y
Updating "./sources/simple_bank.move" . . .
Changes complete
Wrote patchfile out to: ./migration.patch
Recorded edition in 'Move.toml'
3.4 其它类
3.4.1 disassemble
: 反汇编Move
字节码
(1) 命令说明
执行该命令可以反汇编Move
二进制字节码文件为汇编代码。
Usage: sui move disassemble [OPTIONS] <module_path>
Arguments:
<module_path> Path to a .mv file to disassemble
(2) 命令使用
$ sui move disassemble ./build/simple_bank/bytecode_modules/simple_bank.mv
// Move bytecode v6
module 0.simple_bank {
use 0000000000000000000000000000000000000000000000000000000000000002::balance;
use 0000000000000000000000000000000000000000000000000000000000000002::coin;
use 0000000000000000000000000000000000000000000000000000000000000002::event;
use 0000000000000000000000000000000000000000000000000000000000000002::object;
use 0000000000000000000000000000000000000000000000000000000000000002::sui;
use 0000000000000000000000000000000000000000000000000000000000000002::transfer;
use 0000000000000000000000000000000000000000000000000000000000000002::tx_context;
use 0000000000000000000000000000000000000000000000000000000000000002::vec_map;
struct SimpleBank has key {
id: UID,
balances: VecMap<address, Balance<SUI>>
}
struct EventDeposit has copy, drop {
sender: address,
amount: u64,
balance: u64
}
struct EventWithdraw has copy, drop {
sender: address,
amount: u64,
balance: u64
}
init(ctx#0#0: &mut TxContext) {
B0:
0: MoveLoc[0](ctx#0#0: &mut TxContext)
1: Call object::new(&mut TxContext): UID
2: Call vec_map::empty<address, Balance<SUI>>(): VecMap<address, Balance<SUI>>
3: Pack[0](SimpleBank)
4: Call transfer::share_object<SimpleBank>(SimpleBank)
5: Ret
}
entry public deposit(simpleBank#0#0: &mut SimpleBank, amount#0#0: &mut Coin<SUI>, ctx#0#0: &mut TxContext) {
L0: value#1#0: u64
B0:
0: CopyLoc[1](amount#0#0: &mut Coin<SUI>)
1: FreezeRef
2: Call coin::value<SUI>(&Coin<SUI>): u64
3: LdU64(0)
4: Gt
5: BrFalse(7)
B1:
6: Branch(15)
B2:
7: MoveLoc[0](simpleBank#0#0: &mut SimpleBank)
8: Pop
......
51: MoveLoc[6](value#1#0: u64)
52: MoveLoc[5](totalBalance#1#0: u64)
53: Pack[1](EventDeposit)
54: Call event::emit<EventDeposit>(EventDeposit)
55: Ret
}
entry public withdraw(simpleBank#0#0: &mut SimpleBank, amount#0#0: u64, ctx#0#0: &mut TxContext) {
B0:
0: CopyLoc[1](amount#0#0: u64)
1: LdU64(0)
2: Gt
3: BrFalse(5)
B1:
4: Branch(11)
B2:
5: MoveLoc[0](simpleBank#0#0: &mut SimpleBank)
6: Pop
7: MoveLoc[2](ctx#0#0: &mut TxContext)
8: Pop
......
57: FreezeRef
58: Call balance::value<SUI>(&Balance<SUI>): u64
59: Pack[2](EventWithdraw)
60: Call event::emit<EventWithdraw>(EventWithdraw)
61: Ret
}
Constants [
0 => u64: 1
1 => u64: 2
2 => u64: 3
]
}
3.4.2 manage-package
: 记录包的发布地址
(1) 命令说明
执行该命令可以手工区记录包的发布地址。
参数说明:
--environment
: 指定当前链网络环境,可通过命令sui client active-env
查看--network-id
: 指定当前链网络ID,可通过命令sui client chain-identifier
查看--original-id
: 指定包首次发布的地址,若未升级过包,则该地址与Move.toml
中的published-at
地址相同--latest-id
: 指定当前包的最新地址,若未升级过包,则与--original-id
相同;若升级了包,这与当前在Move.toml
中的published-at
地址相同--version-number
: 指定当前包的版本号
Record addresses (Object IDs) for where this package is published on chain (this command sets
variables in Move.lock)
Usage: sui move manage-package [OPTIONS] --environment <ENVIRONMENT> --network-id <CHAIN_ID> --original-id <ORIGINAL_ID> --latest-id <LATEST_ID> --version-number <VERSION_NUMBER>
(2) 命令使用
sui move manage-package --environment "$(sui client active-env)" \
--network-id "$(sui client chain-identifier)" \
--original-id 'ORIGINAL-ADDRESS' \
--latest-id 'LATEST-ADDRESS' \
--version-number 'VERSION-NUMBER'
Client
1 命令说明
sui client
的子命令集是用于跟Sui
网络交互相关的命令,命令非常丰富,以下将进行分类并逐一介绍命令的使用。
可通过help/-h
获取完整的子命令:
$ sui client -h
Client for interacting with the Sui network
Usage: sui client [OPTIONS] [COMMAND]
Commands:
active-address Default address used for commands when none specified
active-env Default environment used for commands when none specified
addresses Obtain the Addresses managed by the client
balance List the coin balance of an address
call Call Move function
chain-identifier Query the chain identifier from the rpc endpoint
dynamic-field Query a dynamic field by its address
envs List all Sui environments
execute-signed-tx Execute a Signed Transaction. This is useful when the user prefers to sign elsewhere and use this command to execute
execute-combined-signed-tx Execute a combined serialized SenderSignedData string
faucet Request gas coin from faucet. By default, it will use the active
address and the active network
gas Obtain all gas objects owned by the address. An address' alias
can be used instead of the address
merge-coin Merge two coin objects into one coin
new-address Generate new address and keypair with keypair scheme flag
{ed25519 | secp256k1 | secp256r1} with optional derivation path,
default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0
for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Word length can
be { word12 | word15 | word18 | word21 | word24} default to
word12 if not specified
new-env Add new Sui environment
object Get object info
objects Obtain all objects owned by the address. It also accepts an
address by its alias
pay Pay coins to recipients following specified amounts, with input
coins. Length of recipients must be the same as that of amounts
pay-all-sui Pay all residual SUI coins to the recipient with input coins,
after deducting the gas cost. The input coins also include the
coin for gas payment, so no extra gas coin is required
pay-sui Pay SUI coins to recipients following following specified
amounts, with input coins. Length of recipients must be the same
as that of amounts. The input coins also include the coin for gas
payment, so no extra gas coin is required
ptb Run a PTB from the provided args
publish Publish Move modules
split-coin Split a coin object into multiple coins
switch Switch active address and network(e.g., devnet, local rpc server)
tx-block Get the effects of executing the given transaction block
transfer Transfer object
transfer-sui Transfer SUI, and pay gas with the same SUI coin object. If
amount is specified, only the amount is transferred; otherwise
the entire object is transferred
upgrade Upgrade Move modules
verify-bytecode-meter Run the bytecode verifier on the package
verify-source Verify local Move packages against on-chain packages, and
optionally their dependencies
profile-transaction Profile the gas usage of a transaction. Unless an output filepath
is not specified, outputs a file
`gas_profile_{tx_digest}_{unix_timestamp}.json` which can be
opened in a flamegraph tool such as speedscope
replay-transaction Replay a given transaction to view transaction effects. Set
environment variable MOVE_VM_STEP=1 to debug
replay-batch Replay transactions listed in a file
replay-checkpoint Replay all transactions in a range of checkpoints
help Print this message or the help of the given subcommand(s)
2 命令分类
根据命令实现功能分为以下几类:
2.1 链网络类
子命令 | 功能说明 |
---|---|
envs | 查看当前客户端所有配置链网络环境 |
active-env | 获取当前默认环境 |
new-env | 添加链网络环境 |
switch | 切换网络 |
chain-identifier | 获取当前链网络标识 |
2.2 地址类
子命令 | 功能说明 |
---|---|
addresses | 获取当前客户端管理的所有地址 |
active-address | 获取当前默认用户地址 |
new-address | 创建新地址 |
switch | 切换地址 |
2.3 对象类
子命令 | 功能说明 |
---|---|
objects | 获取地址拥有的所有对象 |
object | 获取对象信息 |
transfer | 转移对象 |
dynamic-field | 查询对象的动态域(dynamic field ) |
2.4 COIN类
子命令 | 功能说明 |
---|---|
balance | 获取地址代币余额 |
faucet | 领水 |
gas | 获取地址或别名的所有gas 对象 |
merge-coin | 代币合并 |
split-coin | 代币拆分 |
transfer-sui | 转移SUI |
pay | 支付COIN 到指定地址 |
pay-sui | 支付SUI 到指定地址 |
pay-all-sui | 转移所有SUI 到指定地址 |
2.5 合约类
2.6 交易类
子命令 | 功能说明 |
---|---|
execute-signed-tx | 执行签名交易 |
execute-combined-signed-tx | 执行多签交易 |
tx-block | 获取指定交易快的执行影响(effects ) |
2.7 PTB类
子命令 | 功能说明 |
---|---|
ptb | 根据指定参数执行PTB |
2.8 调优类
子命令 | 功能说明 |
---|---|
profile-transaction | 交易gas 消耗分析 |
replay-transaction | 给定交易重放 |
replay-batch | 批量交易重放 |
replay-checkpoint | 重放指定一段检查点区间内的所有交易 |
3 命令详解
3.1 链网络类
3.1.1 envs
: 查看当前客户端所有配置链网络环境
(1) 命令说明
执行该命令会查看当前客户端所有配置链网络环境,包括环境别名、环境地址、是否为活跃环境。
相关信息来自本地的配置文件: ~/.sui/sui_config/client.yaml
List all Sui environments
(2) 命令使用
$ sui client envs
╭──────────┬───────────────────────────────────────┬────────╮
│ alias │ url │ active │
├──────────┼───────────────────────────────────────┼────────┤
│ devnet │ https://fullnode.devnet.sui.io:443 │ │
│ mainnet │ https://sui-mainnet.nodeinfra.com:443 │ * │
│ testnet │ https://fullnode.testnet.sui.io:443 │ │
╰──────────┴───────────────────────────────────────┴────────╯
3.1.2 active-env
: 获取当前默认环境
(1) 命令说明
执行该命令将获取当前客户端使用的链网络环境。
Default environment used for commands when none specified
(2) 命令使用
$ sui client active-env
mainnet
3.1.3 new-env
: 添加链网络环境
(1) 命令说明
执行该命令将添加新的链网络环境。
Add new Sui environment
(2) 命令使用
- 执行添加命令
$ sui client new-env --alias localnet --rpc http://127.0.0.1:9000
Added new Sui env [localnet] to config.
- 在此查看链网络
$ sui client envs
╭──────────┬───────────────────────────────────────┬────────╮
│ alias │ url │ active │
├──────────┼───────────────────────────────────────┼────────┤
│ devnet │ https://fullnode.devnet.sui.io:443 │ │
│ mainnet │ https://sui-mainnet.nodeinfra.com:443 │ * │
│ localnet │ http://127.0.0.1:9000 │ │
│ testnet │ https://fullnode.testnet.sui.io:443 │ │
╰──────────┴───────────────────────────────────────┴────────╯
可见localnet
网络已经添加进去。
3.1.4 switch
: 切换网络
(1) 命令说明
执行该命令使用不同的参数,可以切换地址和网络,这里我们使用--env
命令进行网络切换。
Switch active address and network(e.g., devnet, local rpc server)
Usage: sui client switch [OPTIONS]
Options:
--address <ADDRESS> An address to be used as the active address for subsequent
commands. It accepts also the alias of the address
--env <ENV> The RPC server URL (e.g., local rpc server, devnet rpc
server, etc) to be used for subsequent commands
(2) 命令使用
$ sui client switch --env localnet
Active environment switched to [localnet]
3.1.5 chain-identifier
: 获取当前链网络标识
(1) 命令说明
执行该命令将会获得当前链网络标识。
(2) 命令使用
$ sui client chain-identifier
35834a8a
3.2 地址类
3.2.1 addresses
: 获取当前客户端管理的所有地址
(1) 命令说明
执行该命令将获取当前客户端管理的所有地址,包括地址别名和地址。 数据来源于本地文件~/.sui/sui_config/sui.aliases
。
注:本地文件
~/.sui/sui_config/sui.aliases
中存储的是地址base64
编码的公钥,~/.sui/sui_config/sui.keystore
存储的是地址私钥
Obtain the Addresses managed by the client
(2) 命令使用
$ sui client addresses --json
{
"activeAddress": "0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a",
"addresses": [
[
"jason",
"0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a"
],
[
"yas",
"0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
],
[
"yoy",
"0xf2e6ffef7d0543e258d4c47a53d6fa9872de4630cc186950accbd83415b009f0"
]
]
}
3.2.2 active-address
: 获取当前默认用户地址
(1) 命令说明
执行该命令将获取当前客户端默认使用的用户地址。
Default address used for commands when none specified
(2) 命令使用
$ sui client active-address
0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a
3.2.3 new-address
: 创建新地址
(1) 命令说明
执行该命令将创建一个新的地址,并返回地址和助记词。
Generate new address and keypair with keypair scheme flag {ed25519 | secp256k1 | secp256r1} with optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Word length can be { word12 | word15 | word18 | word21 | word24} default to word12 if not specified
Usage: sui client new-address [OPTIONS] <KEY_SCHEME> [ALIAS] [WORD_LENGTH] [DERIVATION_PATH]
Arguments:
<KEY_SCHEME>
[ALIAS] The alias must start with a letter and can contain only letters, digits, hyphens (-), or underscores (_)
[WORD_LENGTH]
[DERIVATION_PATH]
(2) 命令使用
$ sui client new-address ed25519 js --json
Keys saved as Base64 with 33 bytes `flag || privkey` ($BASE64_STR).
To see Bech32 format encoding, use `sui keytool export $SUI_ADDRESS` where
$SUI_ADDRESS can be found with `sui keytool list`. Or use `sui keytool convert $BASE64_STR`.
{
"alias": "js",
"address": "0xe760ab0e273b4ec996abe76ce67e895e13064611cb4215d7358f2a63188e1a98",
"keyScheme": "ED25519",
"recoveryPhrase": "dismiss split donor onion hour heavy stand work detail issue room pink lion enroll"
}
3.2.4 switch
: 切换地址
(1) 命令说明
执行该命令使用不同的参数,可以切换地址和网络,这里我们使用--address
命令进行地址切换。
Switch active address and network(e.g., devnet, local rpc server)
Usage: sui client switch [OPTIONS]
Options:
--address <ADDRESS> An address to be used as the active address for subsequent
commands. It accepts also the alias of the address
--env <ENV> The RPC server URL (e.g., local rpc server, devnet rpc
server, etc) to be used for subsequent commands
(2) 命令使用
$ sui client switch --address js
Active address switched to 0xe760ab0e273b4ec996abe76ce67e895e13064611cb4215d7358f2a63188e1a98
3.3 对象类
3.3.1 objects
: 获取地址拥有的所有对象
(1) 命令说明
执行该命令将获取地址拥有的所有对象。
Obtain all objects owned by the address. It also accepts an address by its alias
(2) 命令使用
# Usage: sui client objects [OPTIONS] [owner_address]
$ sui client objects --json
[
{
"data": {
"objectId": "0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76",
"version": "25131978",
"digest": "33Ny3AZ3q169QD2uvrso6QtfigyFqN8DccZ1cUJR7v2y",
"type": "0x99bb55e386947db154dcb23b8f39191536c6332b659f3c6aa7596a0cd1f5c4bc::LuckyCafe::Card",
"owner": {
"AddressOwner": "0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a"
},
"previousTransaction": "4qYbEkvQWWshcgyg93NVtHt4hAMqnU9CMt86HJentZ4B",
"storageRebate": "1535200",
"content": {
"dataType": "moveObject",
"type": "0x99bb55e386947db154dcb23b8f39191536c6332b659f3c6aa7596a0cd1f5c4bc::LuckyCafe::Card",
"hasPublicTransfer": true,
"fields": {
"cafe_id": "0x0463f197aa3c90a6c668089267aef6cff0ae3a50ed8144a7da84a49ece45f2e1",
"id": {
"id": "0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76"
}
}
}
}
},
......
3.3.2 object
: 获取对象信息
(1) 命令说明
执行该命令将获取指定对象信息。
Get object info
(2) 命令使用
$ sui client object 0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76 --json
{
"objectId": "0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76",
"version": "25131978",
"digest": "33Ny3AZ3q169QD2uvrso6QtfigyFqN8DccZ1cUJR7v2y",
"type": "0x99bb55e386947db154dcb23b8f39191536c6332b659f3c6aa7596a0cd1f5c4bc::LuckyCafe::Card",
"owner": {
"AddressOwner": "0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a"
},
"previousTransaction": "4qYbEkvQWWshcgyg93NVtHt4hAMqnU9CMt86HJentZ4B",
"storageRebate": "1535200",
"content": {
"dataType": "moveObject",
"type": "0x99bb55e386947db154dcb23b8f39191536c6332b659f3c6aa7596a0cd1f5c4bc::LuckyCafe::Card",
"hasPublicTransfer": true,
"fields": {
"cafe_id": "0x0463f197aa3c90a6c668089267aef6cff0ae3a50ed8144a7da84a49ece45f2e1",
"id": {
"id": "0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76"
}
}
}
}
3.3.3 transfer
: 转移对象
(1) 命令说明
执行该命令将指定对象转移给指定地址。
Transfer object
Usage: sui client transfer [OPTIONS] --to <TO> --object-id <OBJECT_ID>
Options:
--to <TO>
Recipient address (or its alias if it's an address in the keystore)
--object-id <OBJECT_ID>
ID of the object to transfer
(2) 命令使用
$ export CARD=0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76
$ sui client transfer --to alice --object-id $CARD
# 可见AddressOwner已经进行了转移
$ sui client object 0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76 --json
{
"objectId": "0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76",
"version": "28078186",
"digest": "2y7xvkF9fKPM2jdmxrwShtFsJXQhH7C17KLXBu2zepPN",
"type": "0x99bb55e386947db154dcb23b8f39191536c6332b659f3c6aa7596a0cd1f5c4bc::LuckyCafe::Card",
"owner": {
"AddressOwner": "0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19"
},
"previousTransaction": "CUoG2NZT5L94fHqWZNTrAoUs88HDAhyXiSLxL3m9Zn6U",
"storageRebate": "1535200",
"content": {
"dataType": "moveObject",
"type": "0x99bb55e386947db154dcb23b8f39191536c6332b659f3c6aa7596a0cd1f5c4bc::LuckyCafe::Card",
"hasPublicTransfer": true,
"fields": {
"cafe_id": "0x0463f197aa3c90a6c668089267aef6cff0ae3a50ed8144a7da84a49ece45f2e1",
"id": {
"id": "0xcc0b1b68febda7888b7681a70199ab291b38941ee965ed27af1b0bdf8ae79f76"
}
}
}
}
3.3.4 dynamic-field
: 查询对象的动态域
(1) 命令说明
执行该命令将查询指定对象动态域。
Query a dynamic field by its address
Usage: sui client dynamic-field [OPTIONS] <object_id>
Arguments:
<object_id> The ID of the parent object
Options:
--cursor <CURSOR> Optional paging cursor
--limit <LIMIT> Maximum item returned per page [default: 50]
--json Return command outputs in json format
(2) 命令使用
$ sui client dynamic-field 0x9e2d68fb1263c6addde282640438c6910c219922de5e07fd2b1a94fcf40b6214
3.4 COIN类
3.4.1 balance
: 获取地址代币余额
(1) 命令说明
执行该命令将获取当前地址代币余额。
List the coin balance of an address
Usage: sui client balance [OPTIONS] [ADDRESS]
Arguments:
[ADDRESS] Address (or its alias)
Options:
--coin-type <COIN_TYPE> Show balance for the specified coin (e.g.,
0x2::sui::SUI). All coins will be shown if none is
passed
--with-coins Show a list with each coin's object ID and balance
(2) 命令使用
- 获取代币余额
$ sui client balance
╭───────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├───────────────────────────────────────────────┤
│ ╭───────────────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├───────────────────────────────────────────┤ │
│ │ Sui 14915416944 14.91 SUI │ │
│ │ RZX-name 10000 100.00 RZX-sym │ │
│ ╰───────────────────────────────────────────╯ │
╰───────────────────────────────────────────────╯
- 获取指定代币余额
$ sui client balance --coin-type 0x2::sui::SUI
╭────────────────────────────────────────╮
│ Balance of coins owned by this address │
├────────────────────────────────────────┤
│ ╭──────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├──────────────────────────────────┤ │
│ │ Sui 14915416944 14.91 SUI │ │
│ ╰──────────────────────────────────╯ │
╰────────────────────────────────────────╯
- 获取每个代币对象ID和余额
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├─────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ ╭────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ Sui: 1 coin, Balance: 14915416944 (14.91 SUI SUI) │ │
│ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ coinId balance (raw) balance │ │
│ ├────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ 0x043286ba5f16a930c04014e6a8790329a36f01103768055d9daace7cb2466cba 14915416944 14.91 SUI │ │
│ ╰────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ RZX-name: 1 coin, Balance: 10000 (100.00 RZX-sym RZX-sym) │ │
│ ├─────────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ coinId balance (raw) balance │ │
│ ├─────────────────────────────────────────────────────────────────────────────────────────────────────┤ │
│ │ 0x79985fae7d22dffac0a1f31e8bc90d52440af2a06242de55f99fe646df808ff7 10000 100.00 RZX-sym │ │
│ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯ │
│ │
╰─────────────────────
3.4.2 faucet
: 领水
(1) 命令说明
执行该命令将领取开发网或测试网水龙头中的代币
Request gas coin from faucet. By default, it will use the active address and the active
network
Usage: sui client faucet [OPTIONS]
Options:
--address <ADDRESS> Address (or its alias)
(2) 命令使用
$ sui client faucet
Request successful. It can take up to 1 minute to get the coin. Run sui client gas to check your gas coins.
3.4.3 gas
: 获取地址或别名的所有gas
对象
(1) 命令说明
执行该命令将获取当前地址或别名的所有gas
对象。
Obtain all gas objects owned by the address. An address' alias can be used
instead of the address
Usage: sui client gas [OPTIONS] [owner_address]
Arguments:
[owner_address] Address (or its alias) owning the objects
(2) 命令使用
$ sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │ 10000000000 │ 10.00 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
3.4.4 merge-coin
: 代币合并
(1) 命令说明
执行该命令将合并两个代币对象。
Merge two coin objects into one coin
Usage: sui client merge-coin [OPTIONS] --primary-coin <PRIMARY_COIN> --coin-to-merge <COIN_TO_MERGE>
Options:
--primary-coin <PRIMARY_COIN>
The address of the coin to merge into
--coin-to-merge <COIN_TO_MERGE>
The address of the coin to be merged
(2) 命令使用
- 合并前,查询gas
$ sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │ 10000000000 │ 10.00 │
│ 0x9094f1af0b746bc2ee9c4c11621910aa9342bc5b9d1040bdf0dc8886fba40824 │ 10000000000 │ 10.00 │
│ 0xd0c4c7f1d3056114ff378a5504dfb035a9a5254e1e3fd3eb84f562100e7a65b8 │ 10000000000 │ 10.00 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
- 执行合并
$ export COIN1=0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15
$ export COIN2=0x9094f1af0b746bc2ee9c4c11621910aa9342bc5b9d1040bdf0dc8886fba40824
$ sui client merge-coin --primary-coin $COIN1 --coin-to-merge $COIN2
- 合并后,查询gas
可见
COIN2
已经合并到了COIN1
中。
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │ 20000000000 │ 20.00 │
│ 0xd0c4c7f1d3056114ff378a5504dfb035a9a5254e1e3fd3eb84f562100e7a65b8 │ 9999958360 │ 9.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
3.4.5 split-coin
: 代币拆分
(1) 命令说明
执行该命令将拆分一个代币对象。
Split a coin object into multiple coins
Usage: sui client split-coin [OPTIONS] --coin-id <COIN_ID> <--amounts <AMOUNTS>...|--count <COUNT>>
Options:
--coin-id <COIN_ID>
ID of the coin object to split
--amounts <AMOUNTS>...
Specific amounts to split out from the coin
--count <COUNT>
Count of equal-size coins to split into
(2) 命令使用
- 按指定数量拆分
$ export COIN=0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15
$ sui client split-coin --coin-id $COIN --amounts 100000 200000
$ sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │ 19999700000 │ 19.99 │
│ 0xc5ec8a908d629218dc3d01faf04f52f535747c2c40e3d57e62628a5514ad83ff │ 200000 │ 0.00 │
│ 0xfbc39da4b681f4c2824e49dad479b38b8f2890a2996385aef06210094e140697 │ 100000 │ 0.00 │
│ 0xd0c4c7f1d3056114ff378a5504dfb035a9a5254e1e3fd3eb84f562100e7a65b8 │ 9996962600 │ 9.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
- 按份数拆分
$ export COIN=0xfbc39da4b681f4c2824e49dad479b38b8f2890a2996385aef06210094e140697
$ sui client split-coin --coin-id $COIN --count 2
$ sui client gas
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0xfbc39da4b681f4c2824e49dad479b38b8f2890a2996385aef06210094e140697 │ 50000 │ 0.00 │
│ 0x52df2bade5d24245a66782cd0da27dcfa0daaf9a322cd880364c9aaedb14bc41 │ 50000 │ 0.00 │
│ 0xc5ec8a908d629218dc3d01faf04f52f535747c2c40e3d57e62628a5514ad83ff │ 200000 │ 0.00 │
│ 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │ 19997692240 │ 19.99 │
│ 0xd0c4c7f1d3056114ff378a5504dfb035a9a5254e1e3fd3eb84f562100e7a65b8 │ 9996962600 │ 9.99 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
3.4.6 transfer-sui
: 转移SUI
(1) 命令说明
执行该命令将转移SUI
到另一个地址。
Transfer SUI, and pay gas with the same SUI coin object. If amount is
specified, only the amount is transferred; otherwise the entire object is
transferred
Usage: sui client transfer-sui [OPTIONS] --to <TO> --sui-coin-object-id <SUI_COIN_OBJECT_ID>
Options:
--to <TO>
Recipient address (or its alias if it's an address in the keystore)
--sui-coin-object-id <SUI_COIN_OBJECT_ID>
ID of the coin to transfer. This is also the gas object
--amount <AMOUNT>
The amount to transfer, if not specified, the entire coin object
will be transferred
(2) 命令使用
- 转移指定数量
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x05b1b5c60d1b777a5dfc7d5a32874a563f303b1902c0fcd7ea7ab2bd6c0dc29e │ 996016240 │ 0.99 │
│ 0x23accfba5fd1743b042c7c0a1596cf346b986c3f1844585ab1c8058baeca00a8 │ 250000000 │ 0.25 │
│ 0x2fcab6efdd3c96746d3e0bc2dc27995b0c68797bf439fc797b2c96181ab426e3 │ 250000000 │ 0.25 │
│ 0x7d233de155d5a58855447b3ae165e9ec605109861ce14f07f63bd83eb01a4856 │ 250000000 │ 0.25 │
│ 0xd2e2c66fa2d0b0ee4316987a7b1d9e004ce56ef21ee92ba28f8c094097d1bbbf │ 250000000 │ 0.25 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
$ sui client gas bob
No gas coins are owned by this address
$ export COIN=0xd2e2c66fa2d0b0ee4316987a7b1d9e004ce56ef21ee92ba28f8c094097d1bbbf
$ sui client transfer-sui --to bob --sui-coin-object-id $COIN --amount 50000000
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x05b1b5c60d1b777a5dfc7d5a32874a563f303b1902c0fcd7ea7ab2bd6c0dc29e │ 996016240 │ 0.99 │
│ 0x23accfba5fd1743b042c7c0a1596cf346b986c3f1844585ab1c8058baeca00a8 │ 250000000 │ 0.25 │
│ 0x2fcab6efdd3c96746d3e0bc2dc27995b0c68797bf439fc797b2c96181ab426e3 │ 250000000 │ 0.25 │
│ 0x7d233de155d5a58855447b3ae165e9ec605109861ce14f07f63bd83eb01a4856 │ 250000000 │ 0.25 │
│ 0xd2e2c66fa2d0b0ee4316987a7b1d9e004ce56ef21ee92ba28f8c094097d1bbbf │ 198002120 │ 0.19 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
$ sui client gas bob
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x724b02db0f8e50aced87f8871d808a4eca06e132345faa57658cd4d1fd12d655 │ 50000000 │ 0.05 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
- 全部转移
$ export COIN=0x7d233de155d5a58855447b3ae165e9ec605109861ce14f07f63bd83eb01a4856
$ sui client transfer-sui --to bob --sui-coin-object-id $COIN
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x05b1b5c60d1b777a5dfc7d5a32874a563f303b1902c0fcd7ea7ab2bd6c0dc29e │ 996016240 │ 0.99 │
│ 0x23accfba5fd1743b042c7c0a1596cf346b986c3f1844585ab1c8058baeca00a8 │ 250000000 │ 0.25 │
│ 0x2fcab6efdd3c96746d3e0bc2dc27995b0c68797bf439fc797b2c96181ab426e3 │ 250000000 │ 0.25 │
│ 0xd2e2c66fa2d0b0ee4316987a7b1d9e004ce56ef21ee92ba28f8c094097d1bbbf │ 198002120 │ 0.19 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
$ sui client gas bob
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x724b02db0f8e50aced87f8871d808a4eca06e132345faa57658cd4d1fd12d655 │ 50000000 │ 0.05 │
│ 0x7d233de155d5a58855447b3ae165e9ec605109861ce14f07f63bd83eb01a4856 │ 248990120 │ 0.24 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
3.4.7 pay
: 支付COIN
到指定地址
(1) 命令说明
执行该命令将支付COIN
到指定地址,支持批量转账,接受地址个数与转账金额个数必须相同。
Pay coins to recipients following specified amounts, with input
coins. Length of recipients must be the same as that of amounts
Usage: sui client pay [OPTIONS]
Options:
--input-coins <INPUT_COINS>...
The input coins to be used for pay recipients, following
the specified amounts
--recipients <RECIPIENTS>...
The recipient addresses, must be of same length as
amounts. Aliases of addresses are also accepted as input
--amounts <AMOUNTS>...
The amounts to be paid, following the order of
recipients
(2) 命令使用
- 执行转账命令
分别给
Alice
和Bob
转账100
和200 RZX
。
$ export COIN_ID=0x79985fae7d22dffac0a1f31e8bc90d52440af2a06242de55f99fe646df808ff7
$ sui client pay --input-coins $COIN_ID --recipients alice bob --amounts 100 200
- 查看Alice地址当前余额
╭─────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├─────────────────────────────────────────────┤
│ ╭─────────────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├─────────────────────────────────────────┤ │
│ │ Sui 1694018460 1.69 SUI │ │
│ │ RZX-name 100 1.00 RZX-sym │ │
│ ╰─────────────────────────────────────────╯ │
╰─────────────────────────────────────────────╯
- 查看Bob地址当前余额
╭─────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├─────────────────────────────────────────────┤
│ ╭─────────────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├─────────────────────────────────────────┤ │
│ │ Sui 298990320 0.29 SUI │ │
│ │ RZX-name 200 2.00 RZX-sym │ │
│ ╰─────────────────────────────────────────╯ │
╰─────────────────────────────────────────────╯
3.4.8 pay-sui
: 支付SUI
到指定地址
(1) 命令说明
执行该命令将支付SUI
到指定地址,支持批量转账,且可以支持多个COIN
会进行合并后再转币。
Pay SUI coins to recipients following following specified amounts, with input
coins. Length of recipients must be the same as that of amounts. The input
coins also include the coin for gas payment, so no extra gas coin is required
Usage: sui client pay-sui [OPTIONS]
Options:
--input-coins <INPUT_COINS>...
The input coins to be used for pay recipients, including the gas
coin
--recipients <RECIPIENTS>...
The recipient addresses, must be of same length as amounts. Aliases
of addresses are also accepted as input
--amounts <AMOUNTS>...
The amounts to be paid, following the order of recipients
(2) 命令使用
- 单地址转
$ export COIN_ID=0x241d78606cb3b7d2f880854c49781e7c537d318a52c499faef24e101455adf9a
$ sui client pay-sui --input-coins $COIN_ID --recipients alice --amounts 10000
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0x73166aad134319e9c26b19ec6ed0ce419812a7e2612c9ea0e1b93767de423c4d │ 10000 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
$ sui client gas jason
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0x241d78606cb3b7d2f880854c49781e7c537d318a52c499faef24e101455adf9a │ 3760487788 │
│ 0x70b96720fadb6aa45620ab84efd9139e4674057207c93e4375350ec695865fab │ 988221840 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
- 多地址转
$ sui client pay-sui --input-coins $COIN_ID --recipients alice bob --amounts 10000 10000
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0x73166aad134319e9c26b19ec6ed0ce419812a7e2612c9ea0e1b93767de423c4d │ 10000 │
│ 0xb6013d96900f73c43654a48d5c1aef39838513ec33a189593f962e31f5bc7898 │ 10000 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
$ sui client gas bob
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0xedca429699b178481b88e39502ed39bd322e315c582a70520bd32930a25dd758 │ 10000 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
- 合并后转
若指定多个
coin
的话,会先将这些coin
合并后,再进行转币。
$ export COIN_ID2=0x70b96720fadb6aa45620ab84efd9139e4674057207c93e4375350ec695865fab
$ sui client pay-sui --input-coins $COIN_ID $COIN_ID2 --recipients alice bob --amounts 20000
80000
$ sui client gas jason
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0x241d78606cb3b7d2f880854c49781e7c537d318a52c499faef24e101455adf9a │ 4743595988 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0x73166aad134319e9c26b19ec6ed0ce419812a7e2612c9ea0e1b93767de423c4d │ 10000 │
│ 0xb6013d96900f73c43654a48d5c1aef39838513ec33a189593f962e31f5bc7898 │ 10000 │
│ 0xbfe36b88a30be6bf814c26f7aa4ebeedd4c4a3a189238ba8ce2e712fd5eecc31 │ 20000 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
$ sui client gas bob
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0xac824c384f0bab18bfff13da09c8ba9e65607c6918076b0f90005b5034071f92 │ 80000 │
│ 0xedca429699b178481b88e39502ed39bd322e315c582a70520bd32930a25dd758 │ 10000 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
3.4.9 pay-all-sui
: 支付所有SUI
到指定地址
(1) 命令说明
执行该命令会将指定地址的所有SUI
转移到指定地址。
Pay all residual SUI coins to the recipient with input coins, after deducting
the gas cost. The input coins also include the coin for gas payment, so no
extra gas coin is required
Usage: sui client pay-all-sui [OPTIONS] --recipient <RECIPIENT>
Options:
--input-coins <INPUT_COINS>...
The input coins to be used for pay recipients, including the gas
coin
--recipient <RECIPIENT>
The recipient address (or its alias if it's an address in the
keystore)
(2) 命令使用
$ export COIN_ID=0x241d78606cb3b7d2f880854c49781e7c537d318a52c499faef24e101455adf9a
$ sui client pay-all-sui --input-coins $COIN_ID --recipient alice
$ sui client gas jason
No gas coins are owned by this address
$ sui client gas alice
╭────────────────────────────────────────────────────────────────────┬────────────╮
│ gasCoinId │ gasBalance │
├────────────────────────────────────────────────────────────────────┼────────────┤
│ 0x241d78606cb3b7d2f880854c49781e7c537d318a52c499faef24e101455adf9a │ 4742586108 │
│ 0x73166aad134319e9c26b19ec6ed0ce419812a7e2612c9ea0e1b93767de423c4d │ 10000 │
│ 0xb6013d96900f73c43654a48d5c1aef39838513ec33a189593f962e31f5bc7898 │ 10000 │
│ 0xbfe36b88a30be6bf814c26f7aa4ebeedd4c4a3a189238ba8ce2e712fd5eecc31 │ 20000 │
╰────────────────────────────────────────────────────────────────────┴────────────╯
3.5 合约类
3.5.1 publish
: 部署合约
(1) 命令说明
执行该命令会将指定目录(默认当前目录)下的Move
合约编译成字节码,并将字节码部署到链上。
Publish Move modules
Usage: sui client publish [OPTIONS] [package_path]
Arguments:
[package_path] Path to directory containing a Move package [default: .]
(2) 命令使用
- 执行该命令会创建:
PackageID
,后续在调用合约方法或升级合约都会使用到。 - 如果在
init
合约构造方法中有创建Object
,也会输出对应ObjectId
$ sui client publish
3.5.2 call
: 调用合约方法
(1) 命令说明
执行该命令会调用指定合约的指定方法,并返回执行结果。
--package
指定调用的合约包名--module
指定调用的合约模块名--function
指定调用的合约方法名--args
指定调用方法的参数(可选)--type-args
指定泛型参数类型(可选)
Call Move function
Usage: sui client call [OPTIONS] --package <PACKAGE> --module <MODULE> --function <FUNCTION>
Options:
--package <PACKAGE>
Object ID of the package, which contains the module
--module <MODULE>
The name of the module in the package
--function <FUNCTION>
Function name in module
--type-args <TYPE_ARGS>...
Type arguments to the generic function being called. All must be
specified, or the call will fail
--args <ARGS>...
Simplified ordered args like in the function syntax ObjectIDs,
Addresses must be hex strings
(2) 命令使用
$ client call --function claim_red_packet --package $PACKAGE_ID --module red_packet --type-args 0x2::sui::SUI --args $RED_POCKET $WEATHER_ORACLE 0x6
3.5.3 upgrade
: 升级合约
(1) 命令说明
执行该命令会将指定目录(默认当前目录)下的修改后的Move
合约编译成字节码,并将字节码升级链上合约。
Upgrade Move modules
Usage: sui client upgrade [OPTIONS] --upgrade-capability <UPGRADE_CAPABILITY> [package_path]
Arguments:
[package_path] Path to directory containing a Move package [default: .]
(2) 命令使用
$ sui client upgrade --upgrade-capability $UPGRADE_CAP_ID
3.6 交易类
3.6.1 execute-signed-tx
: 执行签名交易
(1) 命令说明
执行该命令可以执行已经签名交易。
Execute a Signed Transaction. This is useful when the user prefers to sign elsewhere and use this command
to execute
Usage: sui client execute-signed-tx [OPTIONS] --tx-bytes <TX_BYTES>
Options:
--tx-bytes <TX_BYTES> BCS serialized transaction data bytes without its type tag, as base64
encoded string. This is the output of sui client command using
--serialize-unsigned-transaction
--signatures <SIGNATURES> A list of Base64 encoded signatures `flag || signature || pubkey`
(2) 命令使用
- 构造待签名交易
$ sui client pay --input-coins $COIN_ID --recipients alice --amounts 500 --serialize-unsigned-transaction
AAADAQB5mF+ufSLf+sCh8x6LyQ1SRAryoGJC3lX5n+ZG34CP95RwrAEAAAAAILvEC/HKvJqm/K/PIvvqw3z+C53wDTkrF2SHvVugWXHFAAj0AQAAAAAAAAAgaYcvxHgfEV4I9y3TfeEhbEMa/qT6pLeU5TJ9pZq85oECAgEAAAEBAQABAQMAAAAAAQIAXFiC1zpuW26hdD+wKO/14NfMi3rhI9J4VsX+Zm2RVpoBBDKGul8WqTDAQBTmqHkDKaNvARA3aAVdnarOfLJGbLqUcKwBAAAAACB0eN1gH6aZ1wcAk4XFWk41okSYHd6bdQQETp27SRBN01xYgtc6bltuoXQ/sCjv9eDXzIt64SPSeFbF/mZtkVaa6AMAAAAAAADYUEQAAAAAAAA=
- 进行交易签名
$ export RAW_TX_DATA=AAADAQB5mF+ufSLf+sCh8x6LyQ1SRAryoGJC3lX5n+ZG34CP95RwrAEAAAAAILvEC/HKvJqm/K/PIvvqw3z+C53wDTkrF2SHvVugWXHFAAj0AQAAAAAAAAAgaYcvxHgfEV4I9y3TfeEhbEMa/qT6pLeU5TJ9pZq85oECAgEAAAEBAQABAQMAAAAAAQIAXFiC1zpuW26hdD+wKO/14NfMi3rhI9J4VsX+Zm2RVpoBBDKGul8WqTDAQBTmqHkDKaNvARA3aAVdnarOfLJGbLqUcKwBAAAAACB0eN1gH6aZ1wcAk4XFWk41okSYHd6bdQQETp27SRBN01xYgtc6bltuoXQ/sCjv9eDXzIt64SPSeFbF/mZtkVaa6AMAAAAAAADYUEQAAAAAAAA=
$ sui keytool sign --address jason --data $RAW_TX_DATA
╭──────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ suiAddress │ 0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a │
│ rawTxData │ AAADAQB5mF+ufSLf+sCh8x6LyQ1SRAryoGJC3lX5n+ZG34CP95RwrAEAAAAAILvEC/HKvJqm/K/PIvvqw3z+C53wDTkrF2SHvVugWXHFAAj0AQAAAAAAAAAgaYcvxHgfEV4I9y3TfeEhbEMa/qT6pLeU5TJ9pZq8 │
│ │ 5oECAgEAAAEBAQABAQMAAAAAAQIAXFiC1zpuW26hdD+wKO/14NfMi3rhI9J4VsX+Zm2RVpoBBDKGul8WqTDAQBTmqHkDKaNvARA3aAVdnarOfLJGbLqUcKwBAAAAACB0eN1gH6aZ1wcAk4XFWk41okSYHd6bdQQE │
│ │ Tp27SRBN01xYgtc6bltuoXQ/sCjv9eDXzIt64SPSeFbF/mZtkVaa6AMAAAAAAADYUEQAAAAAAAA= │
│ intent │ ╭─────────┬─────╮ │
│ │ │ scope │ 0 │ │
│ │ │ version │ 0 │ │
│ │ │ app_id │ 0 │ │
│ │ ╰─────────┴─────╯ │
│ rawIntentMsg │ AAAAAAADAQB5mF+ufSLf+sCh8x6LyQ1SRAryoGJC3lX5n+ZG34CP95RwrAEAAAAAILvEC/HKvJqm/K/PIvvqw3z+C53wDTkrF2SHvVugWXHFAAj0AQAAAAAAAAAgaYcvxHgfEV4I9y3TfeEhbEMa/qT6pLeU5TJ9 │
│ │ pZq85oECAgEAAAEBAQABAQMAAAAAAQIAXFiC1zpuW26hdD+wKO/14NfMi3rhI9J4VsX+Zm2RVpoBBDKGul8WqTDAQBTmqHkDKaNvARA3aAVdnarOfLJGbLqUcKwBAAAAACB0eN1gH6aZ1wcAk4XFWk41okSYHd6b │
│ │ dQQETp27SRBN01xYgtc6bltuoXQ/sCjv9eDXzIt64SPSeFbF/mZtkVaa6AMAAAAAAADYUEQAAAAAAAA= │
│ digest │ m2QJPh0Ez2FsPhhCbkdVpXeXD2PkAQUgJGw3m4hIyTw= │
│ suiSignature │ AMmljxE4qrgfWYsCk00KtAm60f0bCJt1Qpy0kkg8yv997IxnBq0v3YcXCc9lKponq6BztRa5KI8jTpaep8yaPwGIIWyPVESl2t0z8FQnUdfJFGkkHr/YUBwVWQtnZBZycQ== │
╰──────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
- 发送已交易签名
$ export SIGN=AMmljxE4qrgfWYsCk00KtAm60f0bCJt1Qpy0kkg8yv997IxnBq0v3YcXCc9lKponq6BztRa5KI8jTpaep8yaPwGIIWyPVESl2t0z8FQnUdfJFGkkHr/YUBwVWQtnZBZycQ==
$ sui client execute-signed-tx --tx-bytes $RAW_TX_DATA --signatures $SIGN
- 查看Alice地址当前余额,可以看到余额发生的变化
╭─────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├─────────────────────────────────────────────┤
│ ╭─────────────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├─────────────────────────────────────────┤ │
│ │ Sui 3931920823 3.93 SUI │ │
│ │ RZX-name 500 5.00 RZX-sym │ │
│ ╰─────────────────────────────────────────╯ │
╰─────────────────────────────────────────────╯
3.6.2 execute-combined-signed-tx
: 执行联合签名交易
(1) 命令说明
Execute a combined serialized SenderSignedData string
Usage: sui client execute-combined-signed-tx [OPTIONS] --signed-tx-bytes <SIGNED_TX_BYTES>
Options:
--signed-tx-bytes <SIGNED_TX_BYTES>
BCS serialized sender signed data, as base64 encoded string. This is the output of sui client
command using --serialize-signed-transaction
(2) 命令使用
- 构造联合签名交易
$ sui client pay --input-coins $COIN_ID --recipients alice --amounts 800 --serialize-si
gned-transaction
AQAAAAAAAwEAeZhfrn0i3/rAofMei8kNUkQK8qBiQt5V+Z/mRt+Aj/eVcKwBAAAAACCJUku+MB0abKgwUUGVSlr0uGAnfPaY9d40jepHg5vwwAAIIAMAAAAAAAAAIGmHL8R4HxFeCPct033hIWxDGv6k+qS3lOUyfaWavOaBAgIBAAABAQEAAQEDAAAAAAECAFxYgtc6bltuoXQ/sCjv9eDXzIt64SPSeFbF/mZtkVaaAQQyhrpfFqkwwEAU5qh5AymjbwEQN2gFXZ2qznyyRmy6lXCsAQAAAAAgFcDqz+koiG/Vz+IQRUcK59xWHRMppgnk6im7FFK1NptcWILXOm5bbqF0P7Ao7/Xg18yLeuEj0nhWxf5mbZFWmugDAAAAAAAA2FBEAAAAAAAAAWEAmRLC932b4vydYmAhGRKuoQxcd9baeMDh77jK9X2aEKfMKi8kHrmNzCS6QR7PN1vPEZB2AnK9mJIl6rCduUh5DIghbI9URKXa3TPwVCdR18kUaSQev9hQHBVZC2dkFnJx
- 发送联合签名交易
$ export SIGNED_TX_BYTES=AQAAAAAAAwEAeZhfrn0i3/rAofMei8kNUkQK8qBiQt5V+Z/mRt+Aj/eVcKwBAAAAACCJUku+MB0abKgwUUGVSlr0uGAnfPaY9d40jepHg5vwwAAIIAMAAAAAAAAAIGmHL8R4HxFeCPct033hIWxDGv6k+qS3lOUyfaWavOaBAgIBAAABAQEAAQEDAAAAAAECAFxYgtc6bltuoXQ/sCjv9eDXzIt64SPSeFbF/mZtkVaaAQQyhrpfFqkwwEAU5qh5AymjbwEQN2gFXZ2qznyyRmy6lXCsAQAAAAAgFcDqz+koiG/Vz+IQRUcK59xWHRMppgnk6im7FFK1NptcWILXOm5bbqF0P7Ao7/Xg18yLeuEj0nhWxf5mbZFWmugDAAAAAAAA2FBEAAAAAAAAAWEAmRLC932b4vydYmAhGRKuoQxcd9baeMDh77jK9X2aEKfMKi8kHrmNzCS6QR7PN1vPEZB2AnK9mJIl6rCduUh5DIghbI9URKXa3TPwVCdR18kUaSQev9hQHBVZC2dkFnJx
$ sui client execute-combined-signed-tx --signed-tx-bytes $SIGNED_TX_BYTES
- 查看Alice地址当前余额,可以看到余额发生的变化
╭──────────────────────────────────────────────╮
│ Balance of coins owned by this address │
├──────────────────────────────────────────────┤
│ ╭──────────────────────────────────────────╮ │
│ │ coin balance (raw) balance │ │
│ ├──────────────────────────────────────────┤ │
│ │ Sui 3931920823 3.93 SUI │ │
│ │ RZX-name 1300 13.00 RZX-sym │ │
│ ╰──────────────────────────────────────────╯ │
╰──────────────────────────────────────────────╯
3.6.3 tx-block
: 获取指定交易的执行影响
(1) 命令说明
执行该命令可以获取指定交易的执行影响。
Get the effects of executing the given transaction block
Usage: sui client tx-block [OPTIONS] <digest>
Arguments:
<digest> Digest of the transaction block
(2) 命令使用
$ sui client tx-block HzG4Z3vv4MA6rgPVJRUZmLLfNJJhEyE3iWK6LHjTYNBG
3.7 PTB类
PTB
即可编程交易块,是SUI
提供的一个非常强大的一个功能,它允许在一个交易中执行多种操作,甚至去调用不同的合约。- 直接通过命令行脚本的方式去创建和执行
PTB
,可以方便开发人员在不用通过SDK
写代码的情况下便能构建和执行PTB
,也为自动化任务场景提供了很大的灵活性和便利性。 - 由于
ptb
命令比较复杂,单独自成一类,进行讲解
3.7.1 ptb
: 根据指定参数执行PTB
(1) 命令说明
执行该命令可以构建、预览和执行PTB
,下面将对主要的命令行参数逐一进行说明。
Build, preview, and execute programmable transaction blocks. Depending on your
shell, you might have to use quotes around arrays or other passed values. Use
--help to see examples for how to use the core functionality of this command.
Usage: sui client ptb [OPTIONS]
Options:
--assign <NAME> <VALUE>
Assign a value to a variable name to use later in the PTB.
--dry-run
Perform a dry run of the PTB instead of executing it.
--gas-coin <ID>
The object ID of the gas coin to use. If not specified, it will try to
use the first gas coin that it finds that has at least the requested
gas-budget balance.
--gas-budget <MIST>
An optional gas budget for this PTB (in MIST). If gas budget is not
provided, the tool will first perform a dry run to estimate the gas
cost, and then it will execute the transaction. Please note that this
incurs a small cost in performance due to the additional dry run call.
--make-move-vec <TYPE> <[VALUES]>
Given n-values of the same type, it constructs a vector. For non
objects or an empty vector, the type tag must be specified.
--merge-coins <INTO_COIN> <[COIN OBJECTS]>
Merge N coins into the provided coin.
--move-call <PACKAGE::MODULE::FUNCTION> <TYPE_ARGS> <FUNCTION_ARGS>
Make a move call to a function.
--split-coins <COIN> <[AMOUNT]>
Split the coin into N coins as per the given array of amounts.
--transfer-objects <[OBJECTS]> <TO>
Transfer objects to the specified address.
--publish <MOVE_PACKAGE_PATH>
Publish the Move package. It takes as input the folder where the
package exists.
--upgrade <MOVE_PACKAGE_PATH>
Upgrade the Move package. It takes as input the folder where the
package exists.
--preview
Preview the list of PTB transactions instead of executing them.
--serialize-unsigned-transaction
Instead of executing the transaction, serialize the bcs bytes of the
unsigned transaction data using base64 encoding.
--serialize-signed-transaction
Instead of executing the transaction, serialize the bcs bytes of the
signed transaction data using base64 encoding.
--summary
Show only a short summary (digest, execution status, gas cost). Do not
use this flag when you need all the transaction data and the execution
effects.
--warn-shadows
Enable shadow warning when the same variable name is declared multiple
times. Off by default.
(2) 命令使用
(a) 变量绑定(--assign
)
可以使用--assign
参数将值绑定到变量。
- 命令用法
--assign <NAME> <VALUE>
Assign a value to a variable name to use later in the PTB.
-
使用示例
-
用法1:为变量赋值
--assign value 1000
-
用法2:将上一个命令的结果绑定到变量
--split-coins gas [1000] \ --assign coin
-
(b) 地址和对象ID的表达(@
)
- 地址和对象
ID
前需要添加**@
**,以便和普通十六进制数值区分开 - 对于本地钱包中的地址,可以使用别名,不用添加
@
(c) 转移对象(--transfer-objects
)
可以使用--transfer-objects
参数转移对象。
- 命令用法
--transfer-objects <[OBJECTS]> <TO>
Transfer objects to the specified address.
- 使用示例
注:命令中
Coin
对象ID
和地址均需使用@
开头,以跟普通十六进制数值区分开
sui client ptb \
--assign coin @0x75794e86466c09edbe26a814c2e2f46a64f0391a05a7e9725dfad1bed2229b36 \
--transfer-objects [coin] @0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a
(d) 拆分代币(--split-coins
)
使用--split-coins
参数可以拆分代币。
- 命令用法
--split-coins <COIN> <[AMOUNT]>
Split the coin into N coins as per the given array of amounts.
-
使用示例
-
从Gas中拆分
sui client ptb \ --split-coins gas [1000] \ --assign coins \ --transfer-objects [coins] jason
-
从Coin对象中拆分
sui client ptb \ --split-coins @0x1c8bbc2765028d99fbeba11caa5d7a7e13c110bbe0f4c9afb53c9f2f51cb897c [1000] \ --assign coins \ --transfer-objects [coins] jason
-
(e) 合并代币(--merge-coins
)
- 命令用法
--merge-coins <INTO_COIN> <[COIN OBJECTS]>
Merge N coins into the provided coin.
- 使用示例
sui client ptb \
--assign base_coin @0xe8d7bf9b843f4f4adc5962976f3e9afa677f431f9752ac4c3849b86a7798b20c\
--split-coins gas [1000, 2000, 3000] \
--assign coins \
--merge-coins base_coin [coins.0, coins.1, coins.2] \
--transfer-objects [coins] jason
- 执行前
- 执行后
(f) 交易预览(--preview
)
如果构造了复杂的PTB,可以使用--preview
参数来预览PTB交易执行列表而非执行它。
- 命令用法
--preview
Preview the list of PTB transactions instead of executing them.
- 使用示例
sui client ptb \
--assign value1 2024 \
--assign to_address1 jason \
--assign to_address2 @0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19 \
--split-coins gas [value1,4096,8848] \
--assign coins \
--transfer-objects [coins.0] to_address1 \
--transfer-objects [coins.1, coins.2] to_address2 \
--gas-budget 100000000 \
--preview
(g) 摘要展示(--summary
)
使用--summary
参数,可以进行简短的摘要展示,包括:交易哈希、执行状态、Gas
消耗。
- 命令用法
--summary
Show only a short summary (digest, execution status, gas cost). Do not use this flag when you need all the transaction data and the execution effects.
- 使用示例
sui client ptb \
--assign value1 2024 \
--assign to_address1 jason \
--assign to_address2 @0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19 \
--split-coins gas [value1,4096,8848] \
--assign coins \
--transfer-objects [coins.0] to_address1 \
--transfer-objects [coins.1, coins.2] to_address2 \
--gas-budget 100000000 \
--summary

(h) 试运行(dry-run
)
使用--dry-run
参数,可以试运行PTB
,输出执行信息,但非实际上链执行。
- 命令用法
--dry-run
Perform a dry run of the PTB instead of executing it.
- 使用示例
sui client ptb \
--assign value1 2024 \
--assign to_address1 jason \
--assign to_address2 @0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19 \
--split-coins gas [value1,4096,8848] \
--assign coins \
--transfer-objects [coins.0] to_address1 \
--transfer-objects [coins.1, coins.2] to_address2 \
--gas-budget 100000000 \
--dry-run
(i) 对象数组(--make-move-vec
)
使用--make-move-vec
参数,可以构造一个对象数组,可以在MoveCall
中使用。
- 命令用法
--make-move-vec <TYPE> <[VALUES]>
Given n-values of the same type, it constructs a vector. For non objects or an empty vector, the type tag must be specified.
- 使用示例
sui client ptb \
--make-move-vec "<u64>" [1111, 2222] \
--make-move-vec "<address>" [jason, @0x2d178b9704706393d2630fe6cf9415c2c50b181e9e3c7a977237bb2929f82d19]
(j) 方法调用(--move-call
)
使用--move-call
参数,可以调用合约中的方法。
- 命令用法
--move-call <PACKAGE::MODULE::FUNCTION> <TYPE> <FUNCTION_ARGS>
Make a move call to a function.
- 使用示例
以红包合约创建红包接口(
send_new_red_packet
)调用为例(详见:https://learnblockchain.cn/article/7772)。之前我们在命令行下,为了创建一个红包很繁琐的使用了多条命令,现在使用
PTB
只需要一条命令就能轻松搞定。
以下命令,将创建5个拼手气红包,总金额0.1 SUI
sui client ptb \
--assign PACKAGE_ID @0xe5417558cf7edc87840fef347f294dc0fa7bdcd82c043e630c504d233c6b4784 \
--assign COUNT 5 \
--assign AMOUNT 100000000 \
--split-coins gas [AMOUNT] \
--assign coins \
--move-call PACKAGE_ID::red_packet::send_new_red_packet "<0x2::sui::SUI>" COUNT coins.0
(k) 部署包(--publish
)
使用--publish
参数可以部署Move
包,和sui client publish
命令相比,sui client ptb --publish
命令必须显式的转移创建包成功后产生的UpgradeCap
对象。
- 命令用法
--publish <MOVE_PACKAGE_PATH>
Publish the Move package. It takes as input the folder where the package exists.
- 使用示例
sui client ptb \
--move-call sui::tx_context::sender \
--assign sender \
--publish "." \
--assign upgrade_cap \
--transfer-objects [upgrade_cap] sender \
3.8 调优类
3.8.1 profile-transaction
: 交易gas
消耗分析
(1) 命令说明
执行该命令,可以分析指定交易的gas
消耗情况。
注:要启用分析功能,需要编译
Sui
项目时指定--features gas-profiler
。$ cargo build --features gas-profiler --release
或者:
$ cargo install --locked --git https://github.com/MystenLabs/sui.git --branch <BRANCH-NAME> --features gas-profiler sui
Profile the gas usage of a transaction. Unless an output filepath is not specified, outputs a file
`gas_profile_{tx_digest}_{unix_timestamp}.json` which can be opened in a flamegraph tool such as speedscope
Usage: sui client profile-transaction [OPTIONS] --tx-digest <TX_DIGEST>
Options:
-t, --tx-digest <TX_DIGEST> The digest of the transaction to replay
-p, --profile-output <PROFILE_OUTPUT> If specified, overrides the filepath of the output profile, for example --
/temp/my_profile_name.json will write output to
`/temp/my_profile_name_{tx_digest}_{unix_timestamp}.json` If an output filepath is
not specified, it will output a file
`gas_profile_{tx_digest}_{unix_timestamp}.json` to the working directory
(2) 命令使用
$ sui client profile-transaction --tx-digest 6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9
INFO sui_replay: Executing tx: 6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9
INFO sui_replay::replay: Using RPC URL: https://fullnode.devnet.sui.io:443
INFO move_vm_profiler: Gas profile written to file: ./gas_profile.json_6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9_1720628540191496861.json
Execution finished successfully.
命令成功之后后,会输出分析结构到json文件中,这个文件可以在类似speedscope
的火焰图工具中打开,以便对交易gas
使用情况进行可视化分析。
3.8.2 replay-transaction
: 给定交易重放
(1) 命令说明
执行该命令可以重放指定交易,并查看交易执行效果。
Replay a given transaction to view transaction effects. Set environment variable
MOVE_VM_STEP=1 to debug
Usage: sui client replay-transaction [OPTIONS] --tx-digest <TX_DIGEST>
Options:
-t, --tx-digest <TX_DIGEST>
The digest of the transaction to replay
--gas-info
Log extra gas-related information
--ptb-info
Log information about each programmable transaction command
-e, --executor-version <EXECUTOR_VERSION>
Optional version of the executor to use, if not specified defaults to
the one originally used for the transaction
-p, --protocol-version <PROTOCOL_VERSION>
Optional protocol version to use, if not specified defaults to the one
originally used for the transaction
(2) 命令使用
INFO sui_replay: Executing tx: 6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9
INFO sui_replay::replay: Using RPC URL: https://fullnode.devnet.sui.io:443
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Transaction Effects │
├───────────────────────────────────────────────────────────────────────────────────────────────────┤
│ Digest: 6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9 │
│ Status: Success │
│ Executed Epoch: 58 │
│ │
│ Created Objects: │
│ ┌── │
│ │ ID: 0x91ed399242d4b0424280385af7c890477521c469e1e51deada8f8b29876b27bc │
│ │ Owner: Shared( 7 ) │
│ │ Version: 7 │
│ │ Digest: 9XrVCsYjKMXK6dFiRRQLoCTVvS1B5KUJfW9kGrZGADkR │
│ └── │
│ ┌── │
│ │ ID: 0xb9e8c31d1557825b8a93ad695213056a7595e6e546f21b10f794670754375e45 │
│ │ Owner: Account Address ( 0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a ) │
│ │ Version: 7 │
│ │ Digest: CvmwZCtRYqA7hk2qQ4sfWPiqsBaKVqReMbihe8UNFMrD │
│ └── │
│ ┌── │
│ │ ID: 0xc94b31119ae9ab32fb6d72385b5611cfbfcb08a9ba673558345044fd493b202d │
│ │ Owner: Immutable │
│ │ Version: 1 │
│ │ Digest: EM7NCDMp1MYaF9N2tKqUb1sA1BF1pDgp3sNaQqGrybHS │
│ └── │
│ Mutated Objects: │
│ ┌── │
│ │ ID: 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │
│ │ Owner: Account Address ( 0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a ) │
│ │ Version: 7 │
│ │ Digest: DGGDwDw6xdJFiLQGEyCxoFh8wh1WcZvUCaetG7uxwUYF │
│ └── │
│ Gas Object: │
│ ┌── │
│ │ ID: 0x07904d4deba1a822d52a674205548b36b28859620282479144454d3c096ffa15 │
│ │ Owner: Account Address ( 0x5c5882d73a6e5b6ea1743fb028eff5e0d7cc8b7ae123d27856c5fe666d91569a ) │
│ │ Version: 7 │
│ │ Digest: DGGDwDw6xdJFiLQGEyCxoFh8wh1WcZvUCaetG7uxwUYF │
│ └── │
│ Gas Cost Summary: │
│ Storage Cost: 15967600 MIST │
│ Computation Cost: 1000000 MIST │
│ Storage Rebate: 978120 MIST │
│ Non-refundable Storage Fee: 9880 MIST │
│ │
│ Transaction Dependencies: │
│ DeqpG2KUFuu3Rkf6rwKaGeYSjwxM1x9r6HqWKeGaqnQZ │
│ FMmtZmZ6S5oNVUeUuNwvB3fGH44ekyxzzywfSBuHvtjn │
╰───────────────────────────────────────────────────────────────────────────────────────────────────╯
Execution finished successfully. Local and on-chain effects match.
3.8.3 replay-batch
: 批量交易重放
(1) 命令说明
执行该命令可以批量重放指定文件中的交易,并查看交易执行效果。
Replay transactions listed in a file
Usage: sui client replay-batch [OPTIONS] --path <PATH>
Options:
-p, --path <PATH> The path to the file of transaction digests to replay,
with one digest per line
-t, --terminate-early If an error is encountered during a transaction, this
specifies whether to terminate or continue
(2) 命令使用
$ sui client replay-batch --path ./txs.txt
INFO sui_replay::batch_replay: [1/3] Replaying transaction TransactionDigest(6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9)...
INFO sui_replay::batch_replay: [2/3] Replaying transaction TransactionDigest(EME9x9B8iWHhpUa3BpEKkYgmq1P18jUgC9hQN89kWkxP)...
INFO sui_replay::batch_replay: [3/3] Replaying transaction TransactionDigest(zKFQ9qrHVFHcoJnxcYmUCCHhfDv1uwwzzgEErmAKo7z)...
INFO sui_replay::batch_replay: Replaying transaction TransactionDigest(EME9x9B8iWHhpUa3BpEKkYgmq1P18jUgC9hQN89kWkxP) succeeded
INFO sui_replay::batch_replay: Replaying transaction TransactionDigest(6yE93gtnBzyq9XAtKiJ6bQwrs91B6vVRuK4QucRjKhY9) succeeded
INFO sui_replay::batch_replay: Replaying transaction TransactionDigest(zKFQ9qrHVFHcoJnxcYmUCCHhfDv1uwwzzgEErmAKo7z) succeeded
INFO sui_replay::batch_replay: Finished replaying 3 transactions, took 1.957193059s
INFO sui_replay::batch_replay: All transactions passed
3.8.4 replay-checkpoint
: 重放指定一段检查点区间内的所有交易
(1) 命令说明
执行该命令可以重放指定一段检查点区间内的所有交易,并查看交易执行效果。
检查点(checkpoint
)类似传统区块链的区块。
Replay all transactions in a range of checkpoints
Usage: sui client replay-checkpoint [OPTIONS] --start <START> --end <END>
Options:
-s, --start <START> The starting checkpoint sequence number of the range
of checkpoints to replay
-e, --end <END> The ending checkpoint sequence number of the range of
checkpoints to replay
-t, --terminate-early If an error is encountered during a transaction, this
specifies whether to terminate or continue
(2) 命令使用
$ sui client replay-checkpoint --start 87468287 --end 87468289
Keytool
1 命令说明
sui keytool
的子命令集主要是涉及私钥相关的命令行工具,包括私钥创建、签名验签、单签多签及zkLogin
相关命令等。
可通过help/-h
获取完整的子命令:
Sui keystore tool
Usage: sui keytool [OPTIONS] <COMMAND>
Commands:
update-alias Update an old alias to a new one. If a new alias is not provided, a random one
will be generated
convert Convert private key in Hex or Base64 to new format (Bech32 encoded 33 byte flag
|| private key starting with "suiprivkey"). Hex private key format import and
export are both deprecated in Sui Wallet and Sui CLI Keystore. Use `sui keytool
import` if you wish to import a key to Sui Keystore
decode-or-verify-tx Given a Base64 encoded transaction bytes, decode its components. If a signature
is provided, verify the signature against the transaction and output the result
decode-multi-sig Given a Base64 encoded MultiSig signature, decode its components. If tx_bytes is
passed in, verify the multisig
generate Generate a new keypair with key scheme flag {ed25519 | secp256k1 | secp256r1}
with optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or
m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Word length
can be { word12 | word15 | word18 | word21 | word24} default to word12 if not
specified
import Add a new key to Sui CLI Keystore using either the input mnemonic phrase or a
Bech32 encoded 33-byte `flag || privkey` starting with "suiprivkey", the key
scheme flag {ed25519 | secp256k1 | secp256r1} and an optional derivation path,
default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0 for secp256k1 or
m/74'/784'/0'/0/0 for secp256r1. Supports mnemonic phrase of word length 12, 15,
18, 21, 24. Set an alias for the key with the --alias flag. If no alias is
provided, the tool will automatically generate one
export Output the private key of the given key identity in Sui CLI Keystore as Bech32
encoded string starting with `suiprivkey`
list List all keys by its Sui address, Base64 encoded public key, key scheme name in
sui.keystore
load-keypair This reads the content at the provided file path. The accepted format can be
[enum SuiKeyPair] (Base64 encoded of 33-byte `flag || privkey`) or `type
AuthorityKeyPair` (Base64 encoded `privkey`). This prints out the account keypair
as Base64 encoded `flag || privkey`, the network keypair, worker keypair,
protocol keypair as Base64 encoded `privkey`
multi-sig-address To MultiSig Sui Address. Pass in a list of all public keys `flag || pk` in
Base64. See `keytool list` for example public keys
multi-sig-combine-partial-sig Provides a list of participating signatures (`flag || sig || pk` encoded in
Base64), threshold, a list of all public keys and a list of their weights that
define the MultiSig address. Returns a valid MultiSig signature and its sender
address. The result can be used as signature field for `sui client
execute-signed-tx`. The sum of weights of all signatures must be >= the threshold
multi-sig-combine-partial-sig-legacy
show Read the content at the provided file path. The accepted format can be [enum
SuiKeyPair] (Base64 encoded of 33-byte `flag || privkey`) or `type
AuthorityKeyPair` (Base64 encoded `privkey`). It prints its Base64 encoded public
key and the key scheme flag
sign Create signature using the private key for for the given address (or its alias)
in sui keystore. Any signature commits to a [struct IntentMessage] consisting of
the Base64 encoded of the BCS serialized transaction bytes itself and its intent.
If intent is absent, default will be used
sign-kms Creates a signature by leveraging AWS KMS. Pass in a key-id to leverage Amazon
KMS to sign a message and the base64 pubkey. Generate PubKey from pem using
MystenLabs/base64pemkey Any signature commits to a [struct IntentMessage]
consisting of the Base64 encoded of the BCS serialized transaction bytes itself
and its intent. If intent is absent, default will be used
unpack This takes [enum SuiKeyPair] of Base64 encoded of 33-byte `flag || privkey`). It
outputs the keypair into a file at the current directory where the address is the
filename, and prints out its Sui address, Base64 encoded public key, the key
scheme, and the key scheme flag
zk-login-sign-and-execute-tx Given the max_epoch, generate an OAuth url, ask user to paste the redirect with
id_token, call salt server, then call the prover server, create a test
transaction, use the ephemeral key to sign and execute it by assembling to a
serialized zkLogin signature
zk-login-enter-token A workaround to the above command because sometimes token pasting does not work
(for Facebook). All the inputs required here are printed from the command above
zk-login-sig-verify Given a zkLogin signature, parse it if valid. If `bytes` provided, parse it as
either as TransactionData or PersonalMessage based on `intent_scope`. It verifies
the zkLogin signature based its latest JWK fetched. Example request: sui keytool
zk-login-sig-verify --sig $SERIALIZED_ZKLOGIN_SIG --bytes $BYTES --intent-scope 0
--network devnet --curr-epoch 10
zk-login-insecure-sign-personal-message TESTING ONLY: Generate a fixed ephemeral key and its JWT token with test issuer.
Produce a zklogin signature for the given data and max epoch. e.g. sui keytool
zk-login-insecure-sign-personal-message --data "hello" --max-epoch 5
2 命令分类
根据命令实现功能分为以下几类:
2.1 密钥对类
子命令 | 功能说明 |
---|---|
generate | 生成密钥对 |
show | 查看密钥 |
unpack | 解码密钥 |
convert | 密钥格式转换 |
import | 导入私钥到Sui CLI Keystore |
export | 导出私钥 |
list | 列出本地Keystore 所有私钥对应的地址及公钥等信息 |
update-alias | 更新别名 |
load-keypair | 加载多种类型的密钥对 |
2.2 单签类
子命令 | 功能说明 |
---|---|
sign | 交易签名 |
decode-or-verify-tx | 交易解码或签名交易验签 |
2.3 多签类
子命令 | 功能说明 |
---|---|
multi-sig-address | 创建多重签名地址 |
multi-sig-combine-partial-sig | 合并签名 |
decode-multi-sig | 多签解码 |
2.4 zkLogin类
子命令 | 功能说明 |
---|---|
zk-login-sign-and-execute-tx | zkLogin登录、验签、执行接口 |
zk-login-sig-verify | zkLogin签名验证 |
3 命令详解
3.1 链网络类
3.1.1 generate
:生成密钥对
(1) 命令说明
执行该命令可以生成新密钥对,用户可以指定密钥方案和派生路径,若不指定将使用默认值,默认助记词长度为12个单词
Generate a new keypair with key scheme flag {ed25519 | secp256k1 | secp256r1} with optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Word length can be { word12 | word15 | word18 | word21 | word24} default to word12 if not specified
Usage: sui keytool generate [OPTIONS] <KEY_SCHEME> [DERIVATION_PATH] [WORD_LENGTH]
Arguments:
<KEY_SCHEME>
[DERIVATION_PATH]
[WORD_LENGTH]
KEY SCHEME | DEFAULT DERIVATION PATH |
---|---|
ed25519 | m/44'/784'/0'/0'/0' |
secp256k1 | m/54'/784'/0'/0/0 |
secp256r1 | m/74'/784'/0'/0/0 |
(2) 命令使用
- 直接创建
$ sui keytool generate ed25519
╭─────────────────┬────────────────────────────────────────────────────────────────────────────────────────────╮
│ alias │ │
│ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │
│ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │
│ keyScheme │ ed25519 │
│ flag │ 0 │
│ mnemonic │ fetch mushroom celery actress capital clip update neutral kangaroo deal picture identify │
│ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │
╰─────────────────┴────────────────────────────────────────────────────────────────────────────────────────────╯
# 本地生成私钥文件
$ cat 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4.key
AEXleFEKuJrNh9E3kpvwLqdsfeQfuliUQQ61cvJVfq4N
- 指定派生路径和助记词长度创建
$ sui keytool generate secp256k1 "m/54'/784'/0'/0/0" word15
╭─────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ alias │ │
│ suiAddress │ 0x5f5bc9a9d12da2210ad43be0d8120989c280a409e2bed53691a05ca505c8f93a │
│ publicBase64Key │ AQNxN3EAZbPLFUNT9z8HgnaNlRumUvGSmawdr1igYMVCrg== │
│ keyScheme │ secp256k1 │
│ flag │ 1 │
│ mnemonic │ over shaft village combine puzzle muscle fantasy relief early select acquire boy tourist hybrid fine │
│ peerId │ │
╰─────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────────────╯
# 本地生成私钥文件
$ cat 0x5f5bc9a9d12da2210ad43be0d8120989c280a409e2bed53691a05ca505c8f93a.key
Af+2Y9EdQuFUUePBcUfbJHduivR+cg794FrmrvBS1rXf
3.1.2 show
:查看密钥
(1)命令说明
执行该命令可以读取指定路径的私钥文件,并打印Base64
编码公钥、密钥方案等信息。
Read the content at the provided file path. The accepted format can be [enum SuiKeyPair] (Base64 encoded of 33-byte `flag || privkey`) or `type AuthorityKeyPair` (Base64 encoded `privkey`). It prints its Base64 encoded public key and the key scheme flag
Usage: sui keytool show [OPTIONS] <FILE>
Arguments:
<FILE>
(2)命令使用
输出内容跟
generate
生成的一致。
$ sui keytool show ./0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4.key
╭─────────────────┬──────────────────────────────────────────────────────────────────────╮
│ alias │ │
│ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │
│ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │
│ keyScheme │ ed25519 │
│ flag │ 0 │
│ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │
╰─────────────────┴──────────────────────────────────────────────────────────────────────╯
3.1.3 unpack
:解码密钥
(1)命令说明
该命令跟show
命令输出内容一致,只不过show
是查看密钥文件,而unpack
是直接解析密钥。
This takes [enum SuiKeyPair] of Base64 encoded of 33-byte `flag || privkey`). It outputs the keypair into a file at the current directory where the address is the filename, and prints out its Sui address, Base64 encoded public key, the key scheme, and the key scheme flag
Usage: sui keytool unpack [OPTIONS] <KEYPAIR>
Arguments:
<KEYPAIR>
(2)命令使用
内容跟
show
命令输出完全一致。
$ sui keytool unpack AEXleFEKuJrNh9E3kpvwLqdsfeQfuliUQQ61cvJVfq4N
╭─────────────────┬──────────────────────────────────────────────────────────────────────╮
│ alias │ │
│ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │
│ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │
│ keyScheme │ ed25519 │
│ flag │ 0 │
│ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │
╰─────────────────┴──────────────────────────────────────────────────────────────────────╯
3.1.4 convert
:密钥格式转换
(1)命令说明
执行该命令可以将私钥从十六进制或Base64
编码的格式,转换为新格式。
Convert private key in Hex or Base64 to new format (Bech32 encoded 33 byte flag || private key starting with "suiprivkey"). Hex private key format import and export are both deprecated in Sui Wallet and Sui
CLI Keystore. Use `sui keytool import` if you wish to import a key to Sui Keystore
Usage: sui keytool convert [OPTIONS] <VALUE>
Arguments:
<VALUE>
(2)命令使用
$ sui keytool convert AEXleFEKuJrNh9E3kpvwLqdsfeQfuliUQQ61cvJVfq4N
╭────────────────┬──────────────────────────────────────────────────────────────────────────╮
│ bech32WithFlag │ suiprivkey1qpz727z3p2uf4nv86yme9xls96nkcl0yr7a939zpp66h9uj406hq6myd3l7 │
│ base64WithFlag │ AEXleFEKuJrNh9E3kpvwLqdsfeQfuliUQQ61cvJVfq4N │
│ hexWithoutFlag │ 45e578510ab89acd87d137929bf02ea76c7de41fba5894410eb572f2557eae0d │
│ scheme │ ed25519 │
╰────────────────┴──────────────────────────────────────────────────────────────────────────╯
3.1.5 import
:导入私钥到Sui CLI Keystore
(1)命令说明
执行该命令可以添加私钥到Sui
命令行的Keystore
中。可以使用助记词、编码后的私钥作为输入字符串,并指定密钥方案,及可选的派生路径。还可支持指定别名,若未指定,将随机生成别名。
Add a new key to Sui CLI Keystore using either the input mnemonic phrase or a Bech32 encoded 33-byte `flag || privkey` starting with "suiprivkey", the key scheme flag {ed25519 | secp256k1 | secp256r1} and an
optional derivation path, default to m/44'/784'/0'/0'/0' for ed25519 or m/54'/784'/0'/0/0 for secp256k1 or m/74'/784'/0'/0/0 for secp256r1. Supports mnemonic phrase of word length 12, 15, 18, 21, 24. Set an
alias for the key with the --alias flag. If no alias is provided, the tool will automatically generate one
Usage: sui keytool import [OPTIONS] <INPUT_STRING> <KEY_SCHEME> [DERIVATION_PATH]
Arguments:
<INPUT_STRING>
<KEY_SCHEME>
[DERIVATION_PATH]
Options:
--alias <ALIAS> Sets an alias for this address. The alias must start with a letter and can contain only letters, digits, hyphens (-), or underscores (_)
(2)命令使用
- 使用已编码私钥导入
注:需要使用新格式的私钥,可以使用
convert
命令进行转换得到。
$ sui keytool import --alias yas suiprivkey1qpz727z3p2uf4nv86yme9xls96nkcl0yr7a939zpp66h9uj406hq6myd3l7 ed25519
$ sui keytool list
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ yas │ │
│ │ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │ │
│ │ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │ │
│ │ keyScheme │ ed25519 │ │
│ │ flag │ 0 │ │
│ │ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
- 使用助记词导入
注:当前版本(
04ed3b62103cf5cef07bba9416679cf55715c22b
)如果导入助记词,--alias
参数不生效,已提PR
: #17935
$ sui keytool import --alias yoy "over shaft village combine puzzle muscle fantasy relief early select acquire boy tourist hybrid fine" secp256k1
$ sui keytool list
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ yas │ │
│ │ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │ │
│ │ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │ │
│ │ keyScheme │ ed25519 │ │
│ │ flag │ 0 │ │
│ │ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ yoy │ │
│ │ suiAddress │ 0x5f5bc9a9d12da2210ad43be0d8120989c280a409e2bed53691a05ca505c8f93a │ │
│ │ publicBase64Key │ AQNxN3EAZbPLFUNT9z8HgnaNlRumUvGSmawdr1igYMVCrg== │ │
│ │ keyScheme │ secp256k1 │ │
│ │ flag │ 1 │ │
│ │ peerId │ │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
3.1.6 export
:导出私钥
(1)命令说明
执行该命令可以根据密钥标识(别名or地址)导出Sui CLI Keystore
中私钥,以及公钥等信息。
Output the private key of the given key identity in Sui CLI Keystore as Bech32 encoded string starting with `suiprivkey`
Usage: sui keytool export [OPTIONS] --key-identity <KEY_IDENTITY>
Options:
--key-identity <KEY_IDENTITY>
(2)命令使用
- 使用别名
$ sui keytool export --key-identity yas
╭────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────╮
│ exportedPrivateKey │ suiprivkey1qpz727z3p2uf4nv86yme9xls96nkcl0yr7a939zpp66h9uj406hq6myd3l7 │
│ key │ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ │ alias │ yas │ │
│ │ │ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │ │
│ │ │ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │ │
│ │ │ keyScheme │ ed25519 │ │
│ │ │ flag │ 0 │ │
│ │ │ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │ │
│ │ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────╯
- 使用地址
$ sui keytool export --key-identity 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4
╭────────────────────┬────────────────────────────────────────────────────────────────────────────────────────────╮
│ exportedPrivateKey │ suiprivkey1qpz727z3p2uf4nv86yme9xls96nkcl0yr7a939zpp66h9uj406hq6myd3l7 │
│ key │ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ │ alias │ yas │ │
│ │ │ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │ │
│ │ │ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │ │
│ │ │ keyScheme │ ed25519 │ │
│ │ │ flag │ 0 │ │
│ │ │ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │ │
│ │ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────┴────────────────────────────────────────────────────────────────────────────────────────────╯
3.1.7 list
:列出本地Keystore
所有私钥对应的地址及公钥等信息
(1)命令说明
执行该命令可以列出本地Sui Keystore
中,默认路径:~/.sui/sui_config/sui.keystore
,所有私钥对应的地址、公钥等信息。
List all keys by its Sui address, Base64 encoded public key, key scheme name in sui.keystore
Usage: sui keytool list [OPTIONS]
Options:
-s, --sort-by-alias Sort by alias
(2)命令使用
$ sui keytool list
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ yas │ │
│ │ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │ │
│ │ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │ │
│ │ keyScheme │ ed25519 │ │
│ │ flag │ 0 │ │
│ │ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
3.1.8 update-alias
:更新别名
(1)命令说明
命令该命令可以修改别名,如果没有指定,将使用随机别名。
Update an old alias to a new one. If a new alias is not provided, a random one will be generated
Usage: sui keytool update-alias [OPTIONS] <OLD_ALIAS> [NEW_ALIAS]
Arguments:
<OLD_ALIAS>
[NEW_ALIAS] The alias must start with a letter and can contain only letters, digits, dots, hyphens (-), or underscores (_)
(2)命令使用
- 修改为指定别名
$ sui keytool update-alias yas yasmine
Old alias yas was updated to yasmine
$ sui keytool list
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ yasmine │ │
│ │ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │ │
│ │ publicBase64Key │ AGwzSkAPgy3Pkgl+ZFVZWfdFQz9COw46z4RX2WKCRY4a │ │
│ │ keyScheme │ ed25519 │ │
│ │ flag │ 0 │ │
│ │ peerId │ 6c334a400f832dcf92097e64555959f745433f423b0e3acf8457d96282458e1a │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
- 修改为随机别名
$ sui keytool update-alias yoy
Old alias yoy was updated to festive-quartz
$ sui keytool list
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ festive-quartz │ │
│ │ suiAddress │ 0x5f5bc9a9d12da2210ad43be0d8120989c280a409e2bed53691a05ca505c8f93a │ │
│ │ publicBase64Key │ AQNxN3EAZbPLFUNT9z8HgnaNlRumUvGSmawdr1igYMVCrg== │ │
│ │ keyScheme │ secp256k1 │ │
│ │ flag │ 1 │ │
│ │ peerId │ │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
3.1.9 load-keypair
:加载多种类型的密钥对
(1)命令说明
执行该命令可以加载多种类型的密钥对。
This reads the content at the provided file path. The accepted format can be [enum SuiKeyPair] (Base64 encoded of 33-byte `flag || privkey`) or `type AuthorityKeyPair` (Base64 encoded `privkey`).
This prints out the account keypair as Base64 encoded `flag || privkey`, the network keypair, worker keypair, protocol keypair as Base64 encoded `privkey`
Usage: sui keytool load-keypair [OPTIONS] <FILE>
Arguments:
<FILE>
(2)命令使用
$ sui keytool load-keypair ~/0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4.key
╭────────────────┬────────────────────────────────────────────────╮
│ accountKeypair │ AEXleFEKuJrNh9E3kpvwLqdsfeQfuliUQQ61cvJVfq4N │
│ networkKeypair │ ReV4UQq4ms2H0TeSm/Aup2x95B+6WJRBDrVy8lV+rg0= │
│ workerKeypair │ ReV4UQq4ms2H0TeSm/Aup2x95B+6WJRBDrVy8lV+rg0= │
│ keyScheme │ ed25519 │
╰────────────────┴────────────────────────────────────────────────╯
3.2 单签类
3.2.1 sign
:交易签名
(1)命令说明
执行该命令会使用Keystone
中地址或别名对应的私钥进行交易签名。
Create signature using the private key for for the given address (or its alias) in sui keystore.
Any signature commits to a [struct IntentMessage] consisting of the Base64 encoded of the BCS serialized transaction bytes itself and its intent. If intent is absent, default will be used
Usage: sui keytool sign [OPTIONS] --address <ADDRESS> --data <DATA>
Options:
--address <ADDRESS>
--data <DATA>
--json Return command outputs in json format
--intent <INTENT>
(2)命令使用
- 构造待签名数据
$ sui client pay-all-sui --input-coins 0xaeda80a73b6eca09e705f3fe4c0980ff6bbd11682822770506d985eefeeb9175 --recip
ient yoy --gas-budget 10000000 --serialize-unsigned-transaction
AAABACBfW8mp0S2iIQrUO+DYEgmJwoCkCeK+1TaRoFylBcj5OgEBAQABAAAKtkW20cU2U0pqLPRhY5Co9JIrD4sSfklKfrn93nCsxAGu2oCnO27KCecF8/5MCYD/a70RaCgidwUG2YXu/uuRdR3TDwAAAAAAIKFBk8eQNjePP9XUGoGEheBe+6FWVN6F3cpznptxrIprCrZFttHFNlNKaiz0YWOQqPSSKw+LEn5JSn65/d5wrMToAwAAAAAAAICWmAAAAAAAAA==
- 交易签名
$ sui keytool sign --address yas --data AAABACBfW8mp0S2iIQrUO+DYEgmJwoCkCeK+1TaRoFylBcj5OgEBAQABAAAKtkW20cU2U0pqLPRhY5Co9JIrD4sSfklKfrn93nCsxAGu2oCnO27KCecF8/5MCYD/a70RaCgidwUG2YXu/uuRdR3TDwAAAAAAIKFBk8eQNjePP9XUGoGEheBe+6FWVN6F3cpznptxrIprCrZFttHFNlNKaiz0YWOQqPSSKw+LEn5JSn65/d5wrMToAwAAAAAAAICWmAAAAAAAAA==
╭──────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ suiAddress │ 0x0ab645b6d1c536534a6a2cf4616390a8f4922b0f8b127e494a7eb9fdde70acc4 │
│ rawTxData │ AAABACBfW8mp0S2iIQrUO+DYEgmJwoCkCeK+1TaRoFylBcj5OgEBAQABAAAKtkW20cU2U0pqLPRhY5Co9JIrD4sSfklKfrn93nCsxAGu2oCnO27KCecF8/5MCYD/a70RaCgidwUG2YXu/uuRdR3TDwAAAAAAIKFB │
│ │ k8eQNjePP9XUGoGEheBe+6FWVN6F3cpznptxrIprCrZFttHFNlNKaiz0YWOQqPSSKw+LEn5JSn65/d5wrMToAwAAAAAAAICWmAAAAAAAAA== │
│ intent │ ╭─────────┬─────╮ │
│ │ │ scope │ 0 │ │
│ │ │ version │ 0 │ │
│ │ │ app_id │ 0 │ │
│ │ ╰─────────┴─────╯ │
│ rawIntentMsg │ AAAAAAABACBfW8mp0S2iIQrUO+DYEgmJwoCkCeK+1TaRoFylBcj5OgEBAQABAAAKtkW20cU2U0pqLPRhY5Co9JIrD4sSfklKfrn93nCsxAGu2oCnO27KCecF8/5MCYD/a70RaCgidwUG2YXu/uuRdR3TDwAAAAAA │
│ │ IKFBk8eQNjePP9XUGoGEheBe+6FWVN6F3cpznptxrIprCrZFttHFNlNKaiz0YWOQqPSSKw+LEn5JSn65/d5wrMToAwAAAAAAAICWmAAAAAAAAA== │
│ digest │ ZeckWeZmL+knRiNvYByrgOg9ce/g1N2JoFT5w86nw7I= │
│ suiSignature │ AEM7o1w9vXOzUwbT2jxmZDLHViVwUDVh0vHk14ykTwQzBI7LVrrv6kdllbXO/rpvVfxjwL9H6EU4uWK5E+8yDwJsM0pAD4Mtz5IJfmRVWVn3RUM/QjsOOs+EV9ligkWOGg== │
╰──────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
3.2.2 decode-or-verify-tx
:交易解码或签名交易验签
(1)命令说明
执行该命令会对Base64
编码的交易进行解码,如果提供了签名信息,还会进行交易验签,并输出验签结果。
Given a Base64 encoded transaction bytes, decode its components.
If a signature is provided, verify the signature against the transaction and output the result
Usage: sui keytool decode-or-verify-tx [OPTIONS] --tx-bytes <TX_BYTES>
Options:
--tx-bytes <TX_BYTES>
--json Return command outputs in json format
--sig <SIG>
--cur-epoch <CUR_EPOCH> [default: 0]
(2)命令使用
- 交易解码
$ sui keytool decode-or-verify-tx --tx-bytes AAABACBfW8mp0S2iIQrUO+DYEgmJwoCkCeK+1TaRoFylBcj5OgEBAQABAAAKtkW20cU2U0pqLPRhY5Co9JIrD4sSfklKfrn93nCsxAGu2oCnO27KCecF8/5MCYD/a70RaCgidwUG2YXu/uuRdR3TDwAAAAAAIKFBk8eQNjePP9XUGoGEheBe+6FWVN6F3cpznptxrIprCrZFttHFNlNKaiz0YWOQqPSSKw+LEn5JSn65/d5wrMToAwAAAAAAAICWmAAAAAAAAA==

- 交易解码及验签
$ sui keytool decode-or-verify-tx --tx-bytes AAABACBfW8mp0S2iIQrUO+DYEgmJwoCkCeK+1TaRoFylBcj5OgEBAQABAAAKtkW20cU2U0pqLPRhY5Co9JIrD4sSfklKfrn93nCsxAGu2oCnO27KCecF8/5MCYD/a70RaCgidwUG2YXu/uuRdR3TDwAAAAAAIKFBk8eQNjePP9XUGoGEheBe+6FWVN6F3cpznptxrIprCrZFttHFNlNKaiz0YWOQqPSSKw+LEn5JSn65/d5wrMToAwAAAAAAAICWmAAAAAAAAA== --sig AEM7o1w9vXOzUwbT2jxmZDLHViVwUDVh0vHk14ykTwQzBI7LVrrv6kdllbXO/rpvVfxjwL9H6EU4uWK5E+8yDwJsM0pAD4Mtz5IJfmRVWVn3RUM/QjsOOs+EV9ligkWOGg==
输出中上半部分是交易解码后的内容,下半部分是验签结果:

3.3 多签类
Sui
支持多重签名(multisig
)交易,这需要多个密钥进行授权,而不是单密钥签名。
Sui
支持k
对n
多重签名交易,其中k
是阈值,n
是所有参与方的总权重。最大参与方数量为10
。多重签名的有效参与密钥是纯
Ed25519
、ECDSA Secp256k1
和ECDSA Secp256r1
。如果序列化的多重签名包含足够数量的有效签名,其权重之和超过阈值,
SUI
将视为多重签名有效,并执行交易。
3.3.1 multi-sig-address
:创建多重签名地址
(1)命令说明
To MultiSig Sui Address. Pass in a list of all public keys `flag || pk` in Base64. See `keytool list` for example public keys
Usage: sui keytool multi-sig-address [OPTIONS] --threshold <THRESHOLD>
Options:
--threshold <THRESHOLD>
--json Return command outputs in json format
--pks <PKS>...
--weights <WEIGHTS>...
(2)命令使用
- 创建3个不同密钥方案的地址
$ sui client new-address ed25519 js1
╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Created new keypair and saved it to keystore. │
├────────────────┬─────────────────────────────────────────────────────────────────────────────────────┤
│ alias │ js1 │
│ address │ 0x1a142b16f802832d5ab2247ab89fffae686168088b16d93aabb2e53f1cdc99f0 │
│ keyScheme │ ed25519 │
│ recoveryPhrase │ wait virtual cushion tornado cover grain excuse warfare dwarf runway satisfy crater │
╰────────────────┴─────────────────────────────────────────────────────────────────────────────────────╯
$ sui client new-address secp256k1 js2
╭───────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Created new keypair and saved it to keystore. │
├────────────────┬──────────────────────────────────────────────────────────────────────────────────┤
│ alias │ js2 │
│ address │ 0x895d63a8db8bdb07bf450adcaa47fea03cfb1082f4c379b74493a1386b730d6f │
│ keyScheme │ secp256k1 │
│ recoveryPhrase │ disorder fiber mention december scrap wreck curtain option emotion any keen away │
╰────────────────┴──────────────────────────────────────────────────────────────────────────────────╯
$ sui client new-address secp256r1 js3
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Created new keypair and saved it to keystore. │
├────────────────┬────────────────────────────────────────────────────────────────────────┤
│ alias │ js3 │
│ address │ 0x60b8928150fe7a43bbb83dc156b315d3624dd3d2a4178026892e64fe1ab0fd43 │
│ keyScheme │ secp256r1 │
│ recoveryPhrase │ steak lake dune ski banner eternal debate pond figure tell leave faith │
╰────────────────┴────────────────────────────────────────────────────────────────────────╯
- 将地址添加到环境变量
export JS1=0x1a142b16f802832d5ab2247ab89fffae686168088b16d93aabb2e53f1cdc99f0
export JS2=0x895d63a8db8bdb07bf450adcaa47fea03cfb1082f4c379b74493a1386b730d6f
export JS3=0x60b8928150fe7a43bbb83dc156b315d3624dd3d2a4178026892e64fe1ab0fd43
- 查看本地keystore
sui keytool list
╭────────────────────────────────────────────────────────────────────────────────────────────╮
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ js1 │ │
│ │ suiAddress │ 0x1a142b16f802832d5ab2247ab89fffae686168088b16d93aabb2e53f1cdc99f0 │ │
│ │ publicBase64Key │ AOMfccBag19KFImrHhmJK+8e4W96vTHIqeuvojzQbU3h │ │
│ │ keyScheme │ ed25519 │ │
│ │ flag │ 0 │ │
│ │ peerId │ e31f71c05a835f4a1489ab1e19892bef1ee16f7abd31c8a9ebafa23cd06d4de1 │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ js3 │ │
│ │ suiAddress │ 0x60b8928150fe7a43bbb83dc156b315d3624dd3d2a4178026892e64fe1ab0fd43 │ │
│ │ publicBase64Key │ AgI7OJmvbQSHY1GKBIEoA/kiZt/PtPJEBM+hBGVCOw0Vzw== │ │
│ │ keyScheme │ secp256r1 │ │
│ │ flag │ 2 │ │
│ │ peerId │ │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
│ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │
│ │ alias │ js2 │ │
│ │ suiAddress │ 0x895d63a8db8bdb07bf450adcaa47fea03cfb1082f4c379b74493a1386b730d6f │ │
│ │ publicBase64Key │ AQLX+tlpa2ylw057I7nBjyxza9QZQIi0n83KMBSrP7qL2A== │ │
│ │ keyScheme │ secp256k1 │ │
│ │ flag │ 1 │ │
│ │ peerId │ │ │
│ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │
╰────────────────────────────────────────────────────────────────────────────────────────────╯
- 创建多重签名
export PK1=AOMfccBag19KFImrHhmJK+8e4W96vTHIqeuvojzQbU3h
export PK2=AQLX+tlpa2ylw057I7nBjyxza9QZQIi0n83KMBSrP7qL2A==
export PK3=AgI7OJmvbQSHY1GKBIEoA/kiZt/PtPJEBM+hBGVCOw0Vzw==
$ sui keytool multi-sig-address --pks $PK1 $PK2 $PK3 --weights 2 1 1 --threshold 2
╭─────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────╮
│ multisigAddress │ 0xdb4aad8bb29537e7cdbfca302a8f08fae33413d911bb38ae723dd022834976d0 │
│ multisig │ ╭────────────────────────────────────────────────────────────────────────────────────────────╮ │
│ │ │ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │ │
│ │ │ │ address │ 0x1a142b16f802832d5ab2247ab89fffae686168088b16d93aabb2e53f1cdc99f0 │ │ │
│ │ │ │ publicBase64Key │ AOMfccBag19KFImrHhmJK+8e4W96vTHIqeuvojzQbU3h │ │ │
│ │ │ │ weight │ 2 │ │ │
│ │ │ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │ │
│ │ │ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │ │
│ │ │ │ address │ 0x895d63a8db8bdb07bf450adcaa47fea03cfb1082f4c379b74493a1386b730d6f │ │ │
│ │ │ │ publicBase64Key │ AQLX+tlpa2ylw057I7nBjyxza9QZQIi0n83KMBSrP7qL2A== │ │ │
│ │ │ │ weight │ 1 │ │ │
│ │ │ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │ │
│ │ │ ╭─────────────────┬──────────────────────────────────────────────────────────────────────╮ │ │
│ │ │ │ address │ 0x60b8928150fe7a43bbb83dc156b315d3624dd3d2a4178026892e64fe1ab0fd43 │ │ │
│ │ │ │ publicBase64Key │ AgI7OJmvbQSHY1GKBIEoA/kiZt/PtPJEBM+hBGVCOw0Vzw== │ │ │
│ │ │ │ weight │ 1 │ │ │
│ │ │ ╰─────────────────┴──────────────────────────────────────────────────────────────────────╯ │ │
│ │ ╰────────────────────────────────────────────────────────────────────────────────────────────╯ │
╰─────────────────┴────────────────────────────────────────────────────────────────────────────────────────────────╯
$ export MULT_ADDR=0xdb4aad8bb29537e7cdbfca302a8f08fae33413d911bb38ae723dd022834976d0
- 查看余额
通过领水或其他账户转入资金到多签地址。
$ sui client gas $MULT_ADDR
╭────────────────────────────────────────────────────────────────────┬────────────────────┬──────────────────╮
│ gasCoinId │ mistBalance (MIST) │ suiBalance (SUI) │
├────────────────────────────────────────────────────────────────────┼────────────────────┼──────────────────┤
│ 0x4e550a9a10d92954a1d874aa286a909ae4027647deaad2b884cdd730f0094670 │ 1000000000 │ 1.00 │
│ 0x73f172daa2de2f65d139f0e4d29536a5949bdd37149ae5a8eacc769064da3139 │ 1000000000 │ 1.00 │
╰────────────────────────────────────────────────────────────────────┴────────────────────┴──────────────────╯
3.3.2 multi-sig-combine-partial-sig
:合并签名
(1)命令说明
Provides a list of participating signatures (`flag || sig || pk` encoded in Base64), threshold, a list of all public keys and a list of their weights that define the MultiSig address.
Returns a valid MultiSig signature and its sender address. The result can be used as signature field for `sui client execute-signed-tx`. The sum of weights of all signatures must be >= the threshold
Usage: sui keytool multi-sig-address [OPTIONS] --threshold <THRESHOLD>
Options:
--threshold <THRESHOLD>
--json Return command outputs in json format
--pks <PKS>...
--weights <WEIGHTS>...
(2)命令使用
- 序列化交易
使用
--serialize-unsigned-transaction
标志以输出Base64
编码的交易注:需要通过
--gas
参数指定多签地址支付Gas的对象,否则会报错:Cannot find gas coin for signer address 0x4acf728ebb248340e135386c7e030a29dfc002b7383819b792b2d79c3e897986 with amount sufficient for the required gas budget 100000000. If you are using the pay or transfer commands, you can use pay-sui or transfer-sui commands instead, which will use the only object as gas payment.
$ export COIN=0x73f172daa2de2f65d139f0e4d29536a5949bdd37149ae5a8eacc769064da3139
$ export GAS=0x4e550a9a10d92954a1d874aa286a909ae4027647deaad2b884cdd730f0094670
$ sui client split-coin --coin-id $COIN --amounts 10000 20000 --gas-budget 100000000 --gas $GAS --serialize-unsigned-transaction
AAACAQBz8XLaot4vZdE58OTSlTallJvdNxSa5ajqzHaQZNoxOWfXDQAAAAAAILYJ4ZUHGvglO3LahPvKGQ1oA0F7Z0wcJthFl37yCe5XABECECcAAAAAAAAgTgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDcGF5CXNwbGl0X3ZlYwEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQACAQAAAQEA20qti7KVN+fNv8owKo8I+uM0E9kRuziucj3QIoNJdtABTlUKmhDZKVSh2HSqKGqQmuQCdkfeqtK4hM3XMPAJRnDJAA8AAAAAACDh/18LfCtSaf0IBHcvy1GsTrIhjZCzLdn/V01COkNOE9tKrYuylTfnzb/KMCqPCPrjNBPZEbs4rnI90CKDSXbQ6AMAAAAAAAAA4fUFAAAAAAA=
- 记录序列化后的交易到环境变量
$ export TX_BYTES=AAACAQBz8XLaot4vZdE58OTSlTallJvdNxSa5ajqzHaQZNoxOWfXDQAAAAAAILYJ4ZUHGvglO3LahPvKGQ1oA0F7Z0wcJthFl37yCe5XABECECcAAAAAAAAgTgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDcGF5CXNwbGl0X3ZlYwEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQACAQAAAQEA20qti7KVN+fNv8owKo8I+uM0E9kRuziucj3QIoNJdtABTlUKmhDZKVSh2HSqKGqQmuQCdkfeqtK4hM3XMPAJRnDJAA8AAAAAACDh/18LfCtSaf0IBHcvy1GsTrIhjZCzLdn/V01COkNOE9tKrYuylTfnzb/KMCqPCPrjNBPZEbs4rnI90CKDSXbQ6AMAAAAAAAAA4fUFAAAAAAA=
- JS2签名交易
根据前面我们设置的权重和阀值,要么
JS1
单签名即可,要么JS2
和JS3
多签。我们看多签的情形,分别使用JS2
和JS3
的私钥进行多签。
$ sui keytool sign --address $JS2 --data $TX_BYTES --json
{
"suiAddress": "0x895d63a8db8bdb07bf450adcaa47fea03cfb1082f4c379b74493a1386b730d6f",
"rawTxData": "AAACAQBz8XLaot4vZdE58OTSlTallJvdNxSa5ajqzHaQZNoxOWfXDQAAAAAAILYJ4ZUHGvglO3LahPvKGQ1oA0F7Z0wcJthFl37yCe5XABECECcAAAAAAAAgTgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDcGF5CXNwbGl0X3ZlYwEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQACAQAAAQEA20qti7KVN+fNv8owKo8I+uM0E9kRuziucj3QIoNJdtABTlUKmhDZKVSh2HSqKGqQmuQCdkfeqtK4hM3XMPAJRnDJAA8AAAAAACDh/18LfCtSaf0IBHcvy1GsTrIhjZCzLdn/V01COkNOE9tKrYuylTfnzb/KMCqPCPrjNBPZEbs4rnI90CKDSXbQ6AMAAAAAAAAA4fUFAAAAAAA=",
"intent": {
"scope": 0,
"version": 0,
"app_id": 0
},
"rawIntentMsg": "AAAAAAACAQBz8XLaot4vZdE58OTSlTallJvdNxSa5ajqzHaQZNoxOWfXDQAAAAAAILYJ4ZUHGvglO3LahPvKGQ1oA0F7Z0wcJthFl37yCe5XABECECcAAAAAAAAgTgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDcGF5CXNwbGl0X3ZlYwEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQACAQAAAQEA20qti7KVN+fNv8owKo8I+uM0E9kRuziucj3QIoNJdtABTlUKmhDZKVSh2HSqKGqQmuQCdkfeqtK4hM3XMPAJRnDJAA8AAAAAACDh/18LfCtSaf0IBHcvy1GsTrIhjZCzLdn/V01COkNOE9tKrYuylTfnzb/KMCqPCPrjNBPZEbs4rnI90CKDSXbQ6AMAAAAAAAAA4fUFAAAAAAA=",
"digest": "umxieFeADI2TcONrVrVYfohzEAndWuxX1UMvqKwojJ0=",
"suiSignature": "Acn184bDbxjv0RVJSaW0WGqG/IJmOSmNun2rLppfaSRdM5rqNepFgP91dWS/HxtqxHOpLMqV46sq0jsVnA+NYmEC1/rZaWtspcNOeyO5wY8sc2vUGUCItJ/NyjAUqz+6i9g="
}
- JS3签名交易
$ sui keytool sign --address $JS3 --data $TX_BYTES --json
sui keytool sign --address $JS3 --data $TX_BYTES --json
{
"suiAddress": "0x60b8928150fe7a43bbb83dc156b315d3624dd3d2a4178026892e64fe1ab0fd43",
"rawTxData": "AAACAQBz8XLaot4vZdE58OTSlTallJvdNxSa5ajqzHaQZNoxOWfXDQAAAAAAILYJ4ZUHGvglO3LahPvKGQ1oA0F7Z0wcJthFl37yCe5XABECECcAAAAAAAAgTgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDcGF5CXNwbGl0X3ZlYwEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQACAQAAAQEA20qti7KVN+fNv8owKo8I+uM0E9kRuziucj3QIoNJdtABTlUKmhDZKVSh2HSqKGqQmuQCdkfeqtK4hM3XMPAJRnDJAA8AAAAAACDh/18LfCtSaf0IBHcvy1GsTrIhjZCzLdn/V01COkNOE9tKrYuylTfnzb/KMCqPCPrjNBPZEbs4rnI90CKDSXbQ6AMAAAAAAAAA4fUFAAAAAAA=",
"intent": {
"scope": 0,
"version": 0,
"app_id": 0
},
"rawIntentMsg": "AAAAAAACAQBz8XLaot4vZdE58OTSlTallJvdNxSa5ajqzHaQZNoxOWfXDQAAAAAAILYJ4ZUHGvglO3LahPvKGQ1oA0F7Z0wcJthFl37yCe5XABECECcAAAAAAAAgTgAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDcGF5CXNwbGl0X3ZlYwEHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIDc3VpA1NVSQACAQAAAQEA20qti7KVN+fNv8owKo8I+uM0E9kRuziucj3QIoNJdtABTlUKmhDZKVSh2HSqKGqQmuQCdkfeqtK4hM3XMPAJRnDJAA8AAAAAACDh/18LfCtSaf0IBHcvy1GsTrIhjZCzLdn/V01COkNOE9tKrYuylTfnzb/KMCqPCPrjNBPZEbs4rnI90CKDSXbQ6AMAAAAAAAAA4fUFAAAAAAA=",
"digest": "umxieFeADI2TcONrVrVYfohzEAndWuxX1UMvqKwojJ0=",
"suiSignature": "AmDOU/g+mjPsT7dL91nt9APYZwyHuP4Is59A4saH1mWgMwJKdnJpepg6I3hJl8K5eE7nf2IYXxLnVUiw2YOSgSsCOziZr20Eh2NRigSBKAP5Imbfz7TyRATPoQRlQjsNFc8="
}
- 将签名信息记录到环境变量
export SIG2=Acn184bDbxjv0RVJSaW0WGqG/IJmOSmNun2rLppfaSRdM5rqNepFgP91dWS/HxtqxHOpLMqV46sq0jsVnA+NYmEC1/rZaWtspcNOeyO5wY8sc2vUGUCItJ/NyjAUqz+6i9g=
export SIG3=AmDOU/g+mjPsT7dL91nt9APYZwyHuP4Is59A4saH1mWgMwJKdnJpepg6I3hJl8K5eE7nf2IYXxLnVUiw2YOSgSsCOziZr20Eh2NRigSBKAP5Imbfz7TyRATPoQRlQjsNFc8=
- 合并签名
执行该命令需要提供需要超过阀值的所有签名,以及所有公钥、权重、定义多签地址的阈值
$ sui keytool multi-sig-combine-partial-sig --pks $PK1 $PK2 $PK3 --weights 2 1 1 --threshold 2 --sigs $SIG2 $SIG3 --json
{
"multisigAddress": "0xdb4aad8bb29537e7cdbfca302a8f08fae33413d911bb38ae723dd022834976d0",
"multisigParsed": "AwIByfXzhsNvGO/RFUlJpbRYaob8gmY5KY26fasuml9pJF0zmuo16kWA/3V1ZL8fG2rEc6ksypXjqyrSOxWcD41iYQJgzlP4Ppoz7E+3S/dZ7fQD2GcMh7j+CLOfQOLGh9ZloDMCSnZyaXqYOiN4SZfCuXhO539iGF8S51VIsNmDkoErBgADAOMfccBag19KFImrHhmJK+8e4W96vTHIqeuvojzQbU3hAgEC1/rZaWtspcNOeyO5wY8sc2vUGUCItJ/NyjAUqz+6i9gBAgI7OJmvbQSHY1GKBIEoA/kiZt/PtPJEBM+hBGVCOw0VzwECAA==",
"multisigSerialized": "AwIByfXzhsNvGO/RFUlJpbRYaob8gmY5KY26fasuml9pJF0zmuo16kWA/3V1ZL8fG2rEc6ksypXjqyrSOxWcD41iYQJgzlP4Ppoz7E+3S/dZ7fQD2GcMh7j+CLOfQOLGh9ZloDMCSnZyaXqYOiN4SZfCuXhO539iGF8S51VIsNmDkoErBgADAOMfccBag19KFImrHhmJK+8e4W96vTHIqeuvojzQbU3hAgEC1/rZaWtspcNOeyO5wY8sc2vUGUCItJ/NyjAUqz+6i9gBAgI7OJmvbQSHY1GKBIEoA/kiZt/PtPJEBM+hBGVCOw0VzwECAA=="
}
- 执行多签交易
export MULTI_SIGN=AwIByfXzhsNvGO/RFUlJpbRYaob8gmY5KY26fasuml9pJF0zmuo16kWA/3V1ZL8fG2rEc6ksypXjqyrSOxWcD41iYQJgzlP4Ppoz7E+3S/dZ7fQD2GcMh7j+CLOfQOLGh9ZloDMCSnZyaXqYOiN4SZfCuXhO539iGF8S51VIsNmDkoErBgADAOMfccBag19KFImrHhmJK+8e4W96vTHIqeuvojzQbU3hAgEC1/rZaWtspcNOeyO5wY8sc2vUGUCItJ/NyjAUqz+6i9gBAgI7OJmvbQSHY1GKBIEoA/kiZt/PtPJEBM+hBGVCOw0VzwECAA==
sui client execute-signed-tx --tx-bytes $TX_BYTES --signatures $MULTI_SIGN

- 执行成功
多签交易成功执行,代币已经拆分。
3.3.3 decode-multi-sig
:多签解码
(1)命令说明
使用该命令可以将多重签名进行反序列化成人类可读的信息,如果传入了tx_bytes
信息,还会进行验签。
Given a Base64 encoded MultiSig signature, decode its components. If tx_bytes is passed in, verify the multisig
Usage: sui keytool decode-multi-sig [OPTIONS] --multisig <MULTISIG>
Options:
--multisig <MULTISIG>
--json Return command outputs in json format
--tx-bytes <TX_BYTES>
--cur-epoch <CUR_EPOCH> [default: 0]
(2)命令使用
- 多签解码
$ sui keytool decode-multi-sig --multisig $MULTI_SIG
- 多签解码及验签
$ sui keytool decode-multi-sig --multisig $MULTI_SIGN --tx-bytes $TX_BYTES
3.4 zkLogin类
3.4.1 zk-login-sign-and-execute-tx
:zkLogin登录、验签、执行接口
(1)命令说明
该命令没有实际作用,主要是用来体验zkLogin
的整个流程,包括:
- 创建
OAuth
授权连接 - 用户登录后,获取并填入回调连接
- 访问官方盐值服务器获取盐值
- 访问零知识证明服务器获取零知识证明
- 领水
- 创建测试转账交易
- 使用临时私钥签名交易
- 获取零知识证明签名(
partialZkLoginSignature
) - 组装成
zkLogin
签名(zkLoginSignature
)
Given the max_epoch, generate an OAuth url, ask user to paste the redirect with id_token, call salt server, then call the prover server, create a test transaction, use the ephemeral key to sign and execute it by assembling to a serialized zkLogin signature
Usage: sui keytool zk-login-sign-and-execute-tx [OPTIONS] --max-epoch <MAX_EPOCH>
Options:
--max-epoch <MAX_EPOCH>
--json Return command outputs in json format
--network <NETWORK> [default: devnet]
--fixed
--test-multisig
--sign-with-sk
- 若设置
--test-multisig
将使用多签地址(zkLogin
地址+普通公钥地址) - 若设置
--sign-with-sk
将使用传统私钥签名,否则使用zkLogin
签名
(2)命令使用
执行命令后,会依次经历以下步骤:
$ sui keytool zk-login-sign-and-execute-tx --max-epoch 70
- 创建本地临时公私钥对
Ephemeral keypair: Ok("suiprivkey1qr45fwuwmjnjehprdxkvhwkyjmf6qk00mhqdl3xk267q74rgfy7c7er5xmx")
Ephemeral key identifier: 0x73a6b62b367842dfabd4e504b3a0908e41556031a82ec0ca7a4a8be02653ec35
Keys saved as Base64 with 33 bytes `flag || privkey` ($BASE64_STR).
To see Bech32 format encoding, use `sui keytool export $SUI_ADDRESS` where
$SUI_ADDRESS can be found with `sui keytool list`. Or use `sui keytool convert $BASE64_STR`.
Ephemeral pubkey (BigInt): 28052518987115222205950885460171773646293850437629977637885534225349525629480
- 创建随机数
Jwt randomness: 101460194016860753987736196028199365769
- 创建OAuth认证URL
Visit URL (Google): https://accounts.google.com/o/oauth2/v2/auth?client_id=25769832374-famecqrhe2gkebt5fvqms2263046lj96.apps.googleusercontent.com&response_type=id_token&redirect_uri=https://sui.io/&scope=openid&nonce=3-QOndCJH1FxFKKWagDYtp55tNY
Visit URL (Twitch): https://id.twitch.tv/oauth2/authorize?client_id=rs1bh065i9ya4ydvifixl4kss0uhpt&force_verify=true&lang=en&login_type=login&redirect_uri=https://sui.io/&response_type=id_token&scope=openid&nonce=3-QOndCJH1FxFKKWagDYtp55tNY
Visit URL (Facebook): https://www.facebook.com/v17.0/dialog/oauth?client_id=233307156352917&redirect_uri=https://sui.io/&scope=openid&nonce=3-QOndCJH1FxFKKWagDYtp55tNY&response_type=id_token
Visit URL (Kakao): https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=aa6bddf393b54d4e0d42ae0014edfd2f&redirect_uri=https://sui.io/&nonce=3-QOndCJH1FxFKKWagDYtp55tNY
Token exchange URL (Kakao): https://kauth.kakao.com/oauth/token?grant_type=authorization_code&client_id=aa6bddf393b54d4e0d42ae0014edfd2f&redirect_uri=https://sui.io/&code=$YOUR_AUTH_CODE
Visit URL (Apple): https://appleid.apple.com/auth/authorize?client_id=nl.digkas.wallet.client&redirect_uri=https://sui.io/&scope=email&response_mode=form_post&response_type=code%20id_token&nonce=3-QOndCJH1FxFKKWagDYtp55tNY
Visit URL (Slack): https://slack.com/openid/connect/authorize?response_type=code&client_id=2426087588661.5742457039348&redirect_uri=https://sui.io/&nonce=3-QOndCJH1FxFKKWagDYtp55tNY&scope=openid
Token exchange URL (Slack): https://slack.com/api/openid.connect.token?code=$YOUR_AUTH_CODE&client_id=2426087588661.5742457039348&client_secret=39b955a118f2f21110939bf3dff1de90
Visit URL (AWS): https://zklogin-example.auth.us-east-1.amazoncognito.com/login?response_type=token&client_id=6c56t7re6ekgmv23o7to8r0sic&redirect_uri=https://www.sui.io/&nonce=3-QOndCJH1FxFKKWagDYtp55tNY
Visit URL (Microsoft): https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=2e3e87cb-bf24-4399-ab98-48343d457124&scope=openid&response_type=id_token&redirect_uri=https://www.sui.io&nonce=3-QOndCJH1FxFKKWagDYtp55tNY
Visit URL (KarrierOne): https://openid.karrier.one/Account/PhoneLogin?ReturnUrl=/connect/authorize?nonce=3-QOndCJH1FxFKKWagDYtp55tNY&redirect_uri=https://sui.io/&response_type=id_token&scope=openid&client_id=kns-dev
Visit URL (Credenza3): https://accounts.credenza3.com/oauth2/authorize?client_id=65954ec5d03dba0198ac343a&response_type=token&scope=openid+profile+email+phone&redirect_uri=https://example.com/callback&nonce=3-QOndCJH1FxFKKWagDYtp55tNY&state=state
Finish login and paste the entire URL here (e.g. https://sui.io/#id_token=...):
- 拷贝地址在浏览器上完成登录授权,将回调URL拷贝到终端
https://sui.io/#id_token=eyJhbGciOiJSUzI1NiIsImtpZCI6IjY3MTk2NzgzNTFhNWZhZWRjMmU3MDI3NGJiZWE2MmRhMmE4YzRhMTIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5j~~~~~~Zjc1YmIwIn0.SjG7u1Sl~~~~~~FMUpRM0UyWjNsUFQwUjVUUV8
- 创建用户盐值
User salt: 129390038577185583942388216820280642146
- 创建零知识证明
ZkLogin inputs:
"{\"proofPoints\":{\"a\":[\"3464122124523047189471302918932521913058101296434097879073230534482478015348\",\"7834220540079467494418627192539806336472950063825875562138714746024255848251\",\"1\"],\"b\":[[\"19149492441362199307992882783995410707830125405171666381869005049428683081245\",\"14693022937282666003361508079034885255225159928091721231467803006565525373800\"],[\"7759544580258454506271810736350416707977134016192790189783348675168151967611\",\"5929837073147710682722028159163622033159597640618145251073419791030505497940\"],[\"1\",\"0\"]],\"c\":[\"12652371454998480582495208481033029272511889587526425501593640172098172893482\",\"13857954280172815217181847487498445155102759737509673968759142141602304595134\",\"1\"]},\"issBase64Details\":{\"value\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"indexMod4\":1},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjY3MTk2NzgzNTFhNWZhZWRjMmU3MDI3NGJiZWE2MmRhMmE4YzRhMTIiLCJ0eXAiOiJKV1QifQ\"}"
test_multisig false
- 创建zkLogin地址,领水并发送测试交易
这里会输出交易字节和
zkLogin
签名,在下一个命令将会用到。
Use single zklogin address as sender
Sender: 0x04b739c3ec5cfc2472a9fda71bbfcf261e40c9c2f43b5ebae25eaa5a16969dff
Faucet requested and created test transaction: "AAACACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAhGXLkarOWmgp60zvUy95QCT9D5A+7TsN8WhKxF7oRmdDAAAAAAAAACDhMXD7Qkf8UQ26YbD5oNs05wGgpH+7a487cyX8W52kgQEBAQEBAAEAAAS3OcPsXPwkcqn9pxu/zyYeQMnC9DteuuJeqloWlp3/Aep5YrfKI3J4QvSJ94ZhG2IDOi+C2wd4UYy+xoMf6cOiQwAAAAAAAAAgVXax1zcQvqU+P64xElQ/xBcbAuiNeGu9UHh4GhgugIgEtznD7Fz8JHKp/acbv88mHkDJwvQ7XrriXqpaFpad/+gDAAAAAAAAQEtMAAAAAAAA"
Single zklogin sig Serialized: "BQNMMzQ2NDEyMjEyNDUyMzA0NzE4OTQ3MTMwMjkxODkzMjUyMTkxMzA1ODEwMTI5NjQzNDA5Nzg3OTA3MzIzMDUzNDQ4MjQ3ODAxNTM0OEw3ODM0MjIwNTQwMDc5NDY3NDk0NDE4NjI3MTkyNTM5ODA2MzM2NDcyOTUwMDYzODI1ODc1NTYyMTM4NzE0NzQ2MDI0MjU1ODQ4MjUxATEDAk0xOTE0OTQ5MjQ0MTM2MjE5OTMwNzk5Mjg4Mjc4Mzk5NTQxMDcwNzgzMDEyNTQwNTE3MTY2NjM4MTg2OTAwNTA0OTQyODY4MzA4MTI0NU0xNDY5MzAyMjkzNzI4MjY2NjAwMzM2MTUwODA3OTAzNDg4NTI1NTIyNTE1OTkyODA5MTcyMTIzMTQ2NzgwMzAwNjU2NTUyNTM3MzgwMAJMNzc1OTU0NDU4MDI1ODQ1NDUwNjI3MTgxMDczNjM1MDQxNjcwNzk3NzEzNDAxNjE5Mjc5MDE4OTc4MzM0ODY3NTE2ODE1MTk2NzYxMUw1OTI5ODM3MDczMTQ3NzEwNjgyNzIyMDI4MTU5MTYzNjIyMDMzMTU5NTk3NjQwNjE4MTQ1MjUxMDczNDE5NzkxMDMwNTA1NDk3OTQwAgExATADTTEyNjUyMzcxNDU0OTk4NDgwNTgyNDk1MjA4NDgxMDMzMDI5MjcyNTExODg5NTg3NTI2NDI1NTAxNTkzNjQwMTcyMDk4MTcyODkzNDgyTTEzODU3OTU0MjgwMTcyODE1MjE3MTgxODQ3NDg3NDk4NDQ1MTU1MTAyNzU5NzM3NTA5NjczOTY4NzU5MTQyMTQxNjAyMzA0NTk1MTM0ATExeUpwYzNNaU9pSm9kSFJ3Y3pvdkwyRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMGlMQwFmZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklqWTNNVGsyTnpnek5URmhOV1poWldSak1tVTNNREkzTkdKaVpXRTJNbVJoTW1FNFl6UmhNVElpTENKMGVYQWlPaUpLVjFRaWZRTDI0Mjg2Nzg5NDMzOTE4NTUwMjE5MTg1NTk5MDY3NjMxODcyMDkxMzk0ODE3Njc4NDMyOTU2NDgwNjExMDU3NDUyMDA2ODExMjEyMDhGAAAAAAAAAGEAWPfJxn9M85jNim3INjPwqj/C0goLgyBZyR4LNAq8FBEZji3WvqrKYoQGYapOMUlz1vKU+RpX56ejFSStaKIEBD4FKb+xXZrNwC96Ju1eTtfwLqNgpDHD7DrgMHlWT+oo"
╭──────────┬────────────────────────────────────────────────╮
│ txDigest │ FAbz83Q1PhKHJSw1Kj1oWBfCTegzFoHnMdRM88dE2A64 │
╰──────────┴────────────────────────────────────────────────╯
- 浏览器查看交易
https://devnet.suivision.xyz/txblock/FAbz83Q1PhKHJSw1Kj1oWBfCTegzFoHnMdRM88dE2A64?tab=User+Signatures
3.4.2 zk-login-sig-verify
:zkLogin签名验证
(1)命令说明
Given a zkLogin signature, parse it if valid.
If `bytes` provided, parse it as either as TransactionData or PersonalMessage based on `intent_scope`. It verifies the zkLogin signature based its latest JWK fetched.
Example request: sui
keytool zk-login-sig-verify --sig $SERIALIZED_ZKLOGIN_SIG --bytes $BYTES --intent-scope 0 --network devnet --curr-epoch 10
Usage: sui keytool zk-login-sig-verify [OPTIONS] --sig <SIG> --intent-scope <INTENT_SCOPE>
Options:
--sig <SIG> The Base64 of the serialized zkLogin signature
--bytes <BYTES> The Base64 of the BCS encoded TransactionData or PersonalMessage
--json Return command outputs in json format
--intent-scope <INTENT_SCOPE> Either 0 for TransactionData or 3 for PersonalMessage
--cur-epoch <CUR_EPOCH> The current epoch for the network to verify the signature's max_epoch against
--network <NETWORK> The network to verify the signature for, determines ZkLoginEnv [default: devnet]
(2)命令使用
将
zk-login-sign-and-execute-tx
命令输出的交易字节和签名作为参数传入。
sui keytool zk-login-sig-verify --sig BQNMMzQ2NDEyMjEyNDUyMzA0NzE4OTQ3MTMwMjkxODkzMjUyMTkxMzA1ODEwMTI5NjQzNDA5Nzg3OTA3MzIzMDUzNDQ4MjQ3ODAxNTM0OEw3ODM0MjIwNTQwMDc5NDY3NDk0NDE4NjI3MTkyNTM5ODA2MzM2NDcyOTUwMDYzODI1ODc1NTYyMTM4NzE0NzQ2MDI0MjU1ODQ4MjUxATEDAk0xOTE0OTQ5MjQ0MTM2MjE5OTMwNzk5Mjg4Mjc4Mzk5NTQxMDcwNzgzMDEyNTQwNTE3MTY2NjM4MTg2OTAwNTA0OTQyODY4MzA4MTI0NU0xNDY5MzAyMjkzNzI4MjY2NjAwMzM2MTUwODA3OTAzNDg4NTI1NTIyNTE1OTkyODA5MTcyMTIzMTQ2NzgwMzAwNjU2NTUyNTM3MzgwMAJMNzc1OTU0NDU4MDI1ODQ1NDUwNjI3MTgxMDczNjM1MDQxNjcwNzk3NzEzNDAxNjE5Mjc5MDE4OTc4MzM0ODY3NTE2ODE1MTk2NzYxMUw1OTI5ODM3MDczMTQ3NzEwNjgyNzIyMDI4MTU5MTYzNjIyMDMzMTU5NTk3NjQwNjE4MTQ1MjUxMDczNDE5NzkxMDMwNTA1NDk3OTQwAgExATADTTEyNjUyMzcxNDU0OTk4NDgwNTgyNDk1MjA4NDgxMDMzMDI5MjcyNTExODg5NTg3NTI2NDI1NTAxNTkzNjQwMTcyMDk4MTcyODkzNDgyTTEzODU3OTU0MjgwMTcyODE1MjE3MTgxODQ3NDg3NDk4NDQ1MTU1MTAyNzU5NzM3NTA5NjczOTY4NzU5MTQyMTQxNjAyMzA0NTk1MTM0ATExeUpwYzNNaU9pSm9kSFJ3Y3pvdkwyRmpZMjkxYm5SekxtZHZiMmRzWlM1amIyMGlMQwFmZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNklqWTNNVGsyTnpnek5URmhOV1poWldSak1tVTNNREkzTkdKaVpXRTJNbVJoTW1FNFl6UmhNVElpTENKMGVYQWlPaUpLVjFRaWZRTDI0Mjg2Nzg5NDMzOTE4NTUwMjE5MTg1NTk5MDY3NjMxODcyMDkxMzk0ODE3Njc4NDMyOTU2NDgwNjExMDU3NDUyMDA2ODExMjEyMDhGAAAAAAAAAGEAWPfJxn9M85jNim3INjPwqj/C0goLgyBZyR4LNAq8FBEZji3WvqrKYoQGYapOMUlz1vKU+RpX56ejFSStaKIEBD4FKb+xXZrNwC96Ju1eTtfwLqNgpDHD7DrgMHlWT+oo --bytes AAACACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAhGXLkarOWmgp60zvUy95QCT9D5A+7TsN8WhKxF7oRmdDAAAAAAAAACDhMXD7Qkf8UQ26YbD5oNs05wGgpH+7a487cyX8W52kgQEBAQEBAAEAAAS3OcPsXPwkcqn9pxu/zyYeQMnC9DteuuJeqloWlp3/Aep5YrfKI3J4QvSJ94ZhG2IDOi+C2wd4UYy+xoMf6cOiQwAAAAAAAAAgVXax1zcQvqU+P64xElQ/xBcbAuiNeGu9UHh4GhgugIgEtznD7Fz8JHKp/acbv88mHkDJwvQ7XrriXqpaFpad/+gDAAAAAAAAQEtMAAAAAAAA --intent-scope 0 --json
{
"data": null,
"parsed": "{\"inputs\":{\"proofPoints\":{\"a\":[\"3464122124523047189471302918932521913058101296434097879073230534482478015348\",\"7834220540079467494418627192539806336472950063825875562138714746024255848251\",\"1\"],\"b\":[[\"19149492441362199307992882783995410707830125405171666381869005049428683081245\",\"14693022937282666003361508079034885255225159928091721231467803006565525373800\"],[\"7759544580258454506271810736350416707977134016192790189783348675168151967611\",\"5929837073147710682722028159163622033159597640618145251073419791030505497940\"],[\"1\",\"0\"]],\"c\":[\"12652371454998480582495208481033029272511889587526425501593640172098172893482\",\"13857954280172815217181847487498445155102759737509673968759142141602304595134\",\"1\"]},\"issBase64Details\":{\"value\":\"yJpc3MiOiJodHRwczovL2FjY291bnRzLmdvb2dsZS5jb20iLC\",\"indexMod4\":1},\"headerBase64\":\"eyJhbGciOiJSUzI1NiIsImtpZCI6IjY3MTk2NzgzNTFhNWZhZWRjMmU3MDI3NGJiZWE2MmRhMmE4YzRhMTIiLCJ0eXAiOiJKV1QifQ\",\"addressSeed\":\"2428678943391855021918559906763187209139481767843295648061105745200681121208\"},\"maxEpoch\":70,\"userSignature\":\"AFj3ycZ/TPOYzYptyDYz8Ko/wtIKC4MgWckeCzQKvBQRGY4t1r6qymKEBmGqTjFJc9bylPkaV+enoxUkrWiiBAQ+BSm/sV2azcAveibtXk7X8C6jYKQxw+w64DB5Vk/qKA==\"}",
"jwks": null,
"res": null
}
string
ascii
模块说明
ascii
模块定义了Move
的ASCII
编码字符串(String
)和字符(Char
)类型。并提供了一系列的操作方法,用于字符串与字节数组、字符与u8
之间的相互转换;以及字符串的常用操作,包括:追加、截取、计算长度、判断是否为空、判断是否合法等等。
源码路径
https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/move-stdlib/sources/ascii.move
方法图解
方法说明
分类 | 方法 | 说明 |
---|---|---|
类型转换方法 | as_bytes() | ASCII 编码字符串引用转字节数组引用 |
into_bytes() | ASCII 编码字符串转字节数组 | |
string() | 字节数组转ASCII 编码字符串 | |
try_string() | 字节数组转Option ASCII 编码字符串 | |
to_string() | ASCII 编码字符串转UTF-8 编码字符串 | |
byte() | 字符转u8 数字 | |
char() | u8 数字转字符 | |
字符串方法 | length() | 计算字符串长度 |
substring() | 从字符串中取子串 | |
is_empty() | 判断字符串是否为空 | |
index_of() | 获取子串首次出现位置 | |
to_uppercase() | 字符串转大些 | |
to_lowercase() | 字符串转小写 | |
all_characters_printable() | 判断字符串是否均为可打印字符 | |
insert() | 在字符串指定位置插入字符串 | |
append() | 在字符串最后插入字符串 | |
pop_char() | 从字符串尾部弹出字符 | |
字符方法 | is_valid_char | 判断是否有有效字符 |
is_printable_char | 判断是否是可打印字符 |
代码示例
module cookbook::ascii_demo {
use std::ascii::{Self, String, Char};
const EInvalidBookIdx: u64 = 1;
public struct Bookshelf has key {
id: UID,
books: vector<Book>,
book_count: u64
}
public struct Book has store {
idx: u64,
title: String,
category: Char,
}
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: vector::empty(),
book_count: 0,
});
}
public fun add_book(
bookshelf: &mut Bookshelf,
title: vector<u8>,
category: u8
) {
let book_id = bookshelf.books.length();
let book = Book {
idx: book_id,
title: ascii::string(title),
category: ascii::char(category),
};
bookshelf.books.push_back(book);
bookshelf.book_count = bookshelf.book_count + 1;
}
public fun get_books(bookshelf: &Bookshelf): &vector<Book> {
&bookshelf.books
}
public fun get_book(bookshelf: &Bookshelf, idx: u64): &Book {
assert!(bookshelf.books.length() > idx, EInvalidBookIdx);
&bookshelf.books[idx]
}
public fun get_book_mut_ref(bookshelf: &mut Bookshelf, idx: u64): &mut Book {
assert!(bookshelf.books.length() > idx, EInvalidBookIdx);
&mut bookshelf.books[idx]
}
public fun get_title(book: &Book): String {
book.title
}
public fun get_title_ref(book: &Book): &String {
&book.title
}
public fun get_title_mut_ref(book: &mut Book): &mut String {
&mut book.title
}
public fun get_category(book: &Book): Char {
book.category
}
}
#[test_only]
module cookbook::ascii_demo_test {
use std::ascii;
use std::string;
use sui::test_scenario as ts;
use cookbook::ascii_demo::{Bookshelf, create_bookshelf, add_book, get_books};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_ascii() {
let alice = @0xa;
let mut ts = ts::begin(alice);
{
create_bookshelf(ts.ctx());
};
let expected_title = b"Mastering Bitcoin";
let expected_category = 0x41;
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_category
);
ts::return_shared(bookshelf);
};
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let books = bookshelf.get_books();
assert_eq(books.length(), 1);
{
let book = bookshelf.get_book(0);
let title = book.get_title();
assert_eq(title.into_bytes(), expected_title);
assert_eq(title, ascii::string(expected_title));
assert_eq(option::some(title), ascii::try_string(expected_title));
assert_eq(title.to_string(), string::utf8(expected_title));
};
{
let book = bookshelf.get_book(0);
let title_ref = book.get_title_ref();
assert_eq(title_ref.length(), expected_title.length());
assert_eq(title_ref.substring(10, 17), b"Bitcoin".to_ascii_string());
assert_eq(title_ref.is_empty(), false);
assert_eq(title_ref.index_of(&b"Bitcoin".to_ascii_string()), 10);
assert_eq(title_ref.to_uppercase(), b"MASTERING BITCOIN".to_ascii_string());
assert_eq(title_ref.to_lowercase(), b"mastering bitcoin".to_ascii_string());
assert_eq(title_ref.all_characters_printable(), true);
assert_eq(*title_ref.as_bytes(), expected_title); // OK
assert!(title_ref.as_bytes() == &expected_title); // OK
// assert_eq(title_ref.as_bytes(), &expected_title); // ERROR assert_eq不支持引用
};
ts::return_shared(bookshelf);
};
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
let book = bookshelf.get_book_mut_ref(0);
let title_mut_ref = book.get_title_mut_ref();
{
title_mut_ref.insert(17, b" and ".to_ascii_string());
assert!(title_mut_ref == b"Mastering Bitcoin and ".to_ascii_string());
title_mut_ref.append(b"Move".to_ascii_string());
assert!(title_mut_ref == b"Mastering Bitcoin and Move".to_ascii_string());
assert_eq(title_mut_ref.pop_char(), ascii::char(0x65)); // e
assert_eq(title_mut_ref.pop_char(), ascii::char(0x76)); // v
assert_eq(title_mut_ref.pop_char(), ascii::char(0x6F)); // o
assert_eq(title_mut_ref.pop_char(), ascii::char(0x4D)); // M
assert!(title_mut_ref == b"Mastering Bitcoin and ".to_ascii_string());
};
ts::return_shared(bookshelf);
};
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
{
let book = bookshelf.get_book(0);
let category = book.get_category();
assert_eq(category.byte(), 0x41);
assert_eq(category, ascii::char(0x41));
assert_eq(ascii::is_valid_char(0x41), true);
assert_eq(ascii::is_printable_char(0x41), true);
assert_eq(ascii::is_printable_char(0x7F), false);
};
ts::return_shared(bookshelf);
};
ts.end();
}
}
utf8
模块说明
string
模块定义了Move
的UTF-8
编码字符串(String
)。并提供了一系列的操作方法,用于UTF-8
编码字符串与字节数组以及ASCII
编码字符串之间相互转换;以及字符串的常用操作,包括:追加、截取、计算长度、判断是否为空等等。
源码路径
https://github.com/MystenLabs/sui/blob/main/crates/sui-framework/packages/move-stdlib/sources/string.move
方法图解
方法说明
分类 | 方法 | 说明 |
---|---|---|
类型转换方法 | as_bytes() | UTF8 编码字符串引用转字节数组引用 |
into_bytes() | UTF8 编码字符串转字节数组 | |
utf8() | 字节数组转UTF8 编码字符串 | |
try_utf8() | 字节数组转Option UTF8 编码字符串 | |
to_ascii() | UTF8 编码字符串转ASCII 编码字符串 | |
from_ascii() | ASCII 编码字符串转UTF8 编码字符串 | |
字符串方法 | length() | 计算字符串长度 |
substring() | 从字符串中取子串 | |
is_empty() | 判断字符串是否为空 | |
index_of() | 获取子串首次出现位置 | |
insert() | 在字符串指定位置插入UTF8 编码字符串,指定位置必须位于有效的UTF8 字符边界 | |
append() | 在UTF8 编码字符串最后追加UTF8 编码字符串 | |
append_utf8() | 在UTF8 编码字符串最后追加字节数组,该字节数组需要为有效的UTF8 编码 |
代码示例
#![allow(unused)] fn main() { module cookbook::utf8_demo { use std::string::{Self, String}; const EInvalidBookIdx: u64 = 1; public struct Bookshelf has key { id: UID, books: vector<Book>, book_count: u64 } public struct Book has store { idx: u64, title: String, } public fun create_bookshelf(ctx: &mut TxContext) { transfer::share_object(Bookshelf { id: object::new(ctx), books: vector::empty(), book_count: 0, }); } public fun add_book( bookshelf: &mut Bookshelf, title: vector<u8>, ) { let book_id = bookshelf.books.length(); let book = Book { idx: book_id, title: string::utf8(title), }; bookshelf.books.push_back(book); bookshelf.book_count = bookshelf.book_count + 1; } public fun get_books(bookshelf: &Bookshelf): &vector<Book> { &bookshelf.books } public fun get_book(bookshelf: &Bookshelf, idx: u64): &Book { assert!(bookshelf.books.length() > idx, EInvalidBookIdx); &bookshelf.books[idx] } public fun get_book_mut_ref(bookshelf: &mut Bookshelf, idx: u64): &mut Book { assert!(bookshelf.books.length() > idx, EInvalidBookIdx); &mut bookshelf.books[idx] } public fun get_title(book: &Book): String { book.title } public fun get_title_ref(book: &Book): &String { &book.title } public fun get_title_mut_ref(book: &mut Book): &mut String { &mut book.title } } #[test_only] module cookbook::utf8_demo_test { use std::string; use sui::test_scenario as ts; use cookbook::utf8_demo::{Bookshelf, create_bookshelf, add_book, get_books}; #[test_only] use sui::test_utils::assert_eq; #[test] public fun test_utf8() { let alice = @0xa; let mut ts = ts::begin(alice); { create_bookshelf(ts.ctx()); }; let expected_title = b"Mastering Bitcoin"; let expected_title2 = b"精通比特币"; { ts.next_tx(alice); let mut bookshelf: Bookshelf = ts.take_shared(); add_book( &mut bookshelf, expected_title, ); add_book( &mut bookshelf, expected_title2, ); ts::return_shared(bookshelf); }; { ts.next_tx(alice); let bookshelf: Bookshelf = ts.take_shared(); let books = bookshelf.get_books(); assert_eq(books.length(), 2); { let book = bookshelf.get_book(0); let title = book.get_title(); assert_eq(title.into_bytes(), expected_title); assert_eq(title, string::utf8(expected_title)); assert_eq(option::some(title), string::try_utf8(expected_title)); assert_eq(title.to_ascii(), expected_title.to_ascii_string()); assert_eq(title, expected_title.to_ascii_string().to_string()); }; { let book = bookshelf.get_book(1); let title_ref = book.get_title_ref(); assert_eq(title_ref.is_empty(), false); assert_eq(title_ref.length(), expected_title2.length()); assert_eq(title_ref.index_of(&b"比特币".to_string()), 6); assert_eq(title_ref.substring(6, 15), b"比特币".to_string()); }; ts::return_shared(bookshelf); }; { ts.next_tx(alice); let mut bookshelf: Bookshelf = ts.take_shared(); let book = bookshelf.get_book_mut_ref(1); let title_mut_ref = book.get_title_mut_ref(); { title_mut_ref.append_utf8(b"和"); assert!(title_mut_ref == b"精通比特币和".to_string()); title_mut_ref.append(b"Move".to_string()); assert!(title_mut_ref == b"精通比特币和Move".to_string()); title_mut_ref.insert(15, b" ".to_string()); title_mut_ref.insert(19, b" ".to_string()); assert!(title_mut_ref == b"精通比特币 和 Move".to_string()); }; ts::return_shared(bookshelf); }; ts.end(); } } }
string_to_num
以下示例将数字字符串转换成数字,若遇到非数字的字符,将提前结束转换。
- string_2_num
module cookbook::string_2_num {
public fun string_to_num(bytes: vector<u8>): u64 {
let mut num: u64 = 0;
let len = bytes.length();
let mut idx = 0;
while (len - idx > 0) {
// ASCII value of 0 is 48, ASCII value of 9 is 57
if (bytes[idx] >= 48 && bytes[idx] <= 57) {
num = num * 10 + ((bytes[idx] - 48) as u64);
} else {
break // Stop parsing when encountering a non-digit character
};
idx = idx + 1;
};
num
}
}
- string_2_num_tests
module cookbook::string_2_num_test {
use cookbook::string_2_num::{string_to_num};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
fun test_string_2_num() {
let bytes = b"123456789";
let num = string_to_num(bytes);
assert_eq(num, 123456789);
let bytes = b"123456789123456789";
let num = string_to_num(bytes);
assert_eq(num, 123456789123456789);
let bytes = b"123456789ABCD";
let num = string_to_num(bytes);
assert_eq(num, 123456789);
}
}
num_to_string
以下示例将数字转换成字节数组,再通过调用to_ascii_string()
方法转换成数字字符串。
- num_2_string
module cookbook::num_2_string {
public fun u8_to_string(num: u8): vector<u8> {
return num_to_string(num as u64)
}
public fun u16_to_string(num: u16): vector<u8> {
return num_to_string(num as u64)
}
public fun u32_to_string(num: u32): vector<u8> {
return num_to_string(num as u64)
}
public fun u64_to_string(num: u64): vector<u8> {
return num_to_string(num)
}
public fun num_to_string(mut num: u64): vector<u8> {
if (num == 0) {
return b"0"
};
let mut bytes = vector::empty<u8>();
while (num > 0) {
let remainder = num % 10; // get the last digit
num = num / 10; // remove the last digit
vector::push_back(&mut bytes, (remainder as u8) + 48); // ASCII value of 0 is 48
};
vector::reverse(&mut bytes);
return bytes
}
}
- num_2_string_tests
module cookbook::num_2_string_test {
use cookbook::num_2_string::{u8_to_string, u16_to_string, u32_to_string, u64_to_string};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
fun test_num_2_string() {
let num: u8 = 255;
let output = u8_to_string(num);
assert_eq(output.to_ascii_string(), b"255".to_ascii_string());
let num: u16 = 12345;
let output = u16_to_string(num);
assert_eq(output.to_ascii_string(), b"12345".to_ascii_string());
let num: u32 = 123456789;
let output = u32_to_string(num);
assert_eq(output.to_ascii_string(), b"123456789".to_ascii_string());
let num: u64 = 123456789123456789;
let output = u64_to_string(num);
assert_eq(output.to_ascii_string(), b"123456789123456789".to_ascii_string());
}
}
random
模块说明
随机数常见于区块链游戏、彩票类、抽奖类等智能合约中,以确保公正性和公平性。
场景
- NFT:在铸造 NFT 时随机分配属性,如稀有度或独特特征等
- 游戏:游戏战斗中的随机元素,如:开启宝箱随机宝物,随机进入游戏场景等
- 彩票或抽奖:彩票随机开奖或抽取中奖者
- 协议:随机选举领导者或随机分配收益等
分类
其中链下随机数需要依赖外部第三方的服务,安全性和可靠性受第三方服务限制。 链上随机数包括不安全随机数如:时间戳、交易哈希等,它们是可预测和可操控的,以及
Sui
原生的链上安全随机数random。
- 链上随机数
- 时间戳
- 交易哈希
- random
- 链下随机数
- 预言机(如:
weather-oracle
) - 第三方服务(如:
drand
)
- 预言机(如:
特点
Sui
原生的链上随机数random(0x8),它具备以下特点:
- 安全
- 快速
- 去中心化
- 简单易用
- 无需信任第三方
源码路径
方法图解
最佳实践
(1)使用私有的entry
函数,不要使用public entry
(2)使用函数内直接创建的RandomGenerator
,不要作为参数传递
(3)确保意外路径(unhappy path
)不会比预期路径(happy path
)消耗更多gas
(4)将获取随机数和使用随机数函数分开
(5)防止PTB
级别的组合攻击(编译器)
Sui
在使用Random
作为输入的MoveCall
命令之后拒绝了具有非TransferObjects
或MergeCoins
命令的PTB
代码示例
以下是一个投骰子的示例,每次调用将会得到一个 1~6 的随机数 NFT。
module cookbook::random{
use sui::random::{Self, Random, new_generator};
use sui::event;
public struct Dice has key, store {
id: UID,
value: u8,
}
public struct DiceEvent has copy, drop {
value: u8,
}
entry fun roll_dice(r: &Random, ctx: &mut TxContext): u8 {
let mut generator = new_generator(r, ctx);
let result = random::generate_u8_in_range(&mut generator, 1, 6);
result
}
entry fun roll_dice_nft(r: &Random, ctx: &mut TxContext) {
let value = roll_dice(r, ctx);
let dice = Dice {
id: object::new(ctx),
value,
};
event::emit(DiceEvent { value });
transfer::transfer(dice, tx_context::sender(ctx));
}
}
合约操作
合约部署
$ sui client publish
- 记录合约包 ID
export PACKAGE_ID=0xc80da11fbee3b9b74f3cf3fc7f013b6c12041b6ba68261019cf77fa3cbdc0966
浏览器调用
https://suiscan.xyz/testnet/tx/BAYfpsCCSkrgRoLFy6EaSNNann9PJDuk6n592A6UrbKC
可见得到的骰子点数为:4。
命令行调用
$ sui client call --package $PACKAGE_ID --module random --function roll_dice_nft --args 0x8
可见得到的骰子点数为:4。
代码调用
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";
import dotenv from "dotenv";
dotenv.config();
const MNEMONIC = process.env.MNEMONIC!;
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);
const PACKAGE_ID =
"0xc80da11fbee3b9b74f3cf3fc7f013b6c12041b6ba68261019cf77fa3cbdc0966";
const MODULE_NAME = "random";
const FUNCTION_NAME = "roll_dice_nft";
const FULLNODE_URL = "https://fullnode.testnet.sui.io:443";
const SUI_CLIENT = new SuiClient({ url: FULLNODE_URL });
(async () => {
let tx = new Transaction();
tx.moveCall({
target: `${PACKAGE_ID}::${MODULE_NAME}::${FUNCTION_NAME}`,
arguments: [tx.object("0x8")],
});
try {
const result = await SUI_CLIENT.signAndExecuteTransaction({
transaction: tx,
signer: keypair,
options: {
showEvents: true,
},
});
console.log(
`signAndExecuteTransactionBlock result: ${JSON.stringify(
result,
null,
2
)}`
);
} catch (e) {
console.error(e);
}
})();
- 执行输出
可见得到的骰子点数为:6。
$ ts-node client.ts
signAndExecuteTransactionBlock result: {
"digest": "FfG1UQHC3CiZg4aUsGjUA788gG6ZtpwXd5LKY12xYq5z",
"events": [
{
"id": {
"txDigest": "FfG1UQHC3CiZg4aUsGjUA788gG6ZtpwXd5LKY12xYq5z",
"eventSeq": "0"
},
"packageId": "0xc80da11fbee3b9b74f3cf3fc7f013b6c12041b6ba68261019cf77fa3cbdc0966",
"transactionModule": "random",
"sender": "0xa244617bc05e4122fb825d3b9c63dbad96dd06fae8183c2f03027b1feff12028",
"type": "0xc80da11fbee3b9b74f3cf3fc7f013b6c12041b6ba68261019cf77fa3cbdc0966::random::DiceEvent",
"parsedJson": {
"value": 6
},
"bcs": "7"
}
],
"confirmedLocalExecution": false
}
更多资料
https://docs.sui.io/guides/developer/advanced/randomness-onchain
https://www.youtube.com/watch?v=GxWk5WDnoD0
oracle
模块说明
本文将介绍和使用Sui
官方提供的 天气预言机weather-oracle 进行随机数的创建。
注:因目前官方提供的部署在主网上的天气预言机版本比较老,已经无法正常使用了,故本示例中将在测试网上部署一个简化版本来演示。期待官方后续进行下更新。
源码路径
方法图解
代码示例
以下同样是投骰子的示例,每次调用将通过天气预言机得到一个 1~6 的随机数 NFT。
module cookbook::weather_oracle {
use sui::event;
use simple_weather_oracle::simple_weather::{WeatherOracle};
public struct Dice has key, store {
id: UID,
value: u32,
}
public struct DiceEvent has copy, drop {
value: u32,
}
entry fun roll_dice(weather_oracle: &WeatherOracle, geoname_id: u32): u32 {
let random_pressure_sz =
simple_weather_oracle::simple_weather::city_weather_oracle_pressure(weather_oracle, geoname_id);
let result = random_pressure_sz % 6 + 1;
result
}
entry fun roll_dice_nft(weather_oracle: &WeatherOracle, geoname_id: u32, ctx: &mut TxContext) {
let value = roll_dice(weather_oracle, geoname_id);
let dice = Dice {
id: object::new(ctx),
value,
};
event::emit(DiceEvent { value });
transfer::transfer(dice, tx_context::sender(ctx));
}
}
合约操作
前置准备
因官方的天气预言机已经无法正常使用,故个人在测试网部署一个简化版本,进行测试。待官方升级后,使用方式类似。
具体步骤如下:
(1)编写简化版weather-oracle合约
module oracle::simple_weather {
use std::string::{Self, String};
use sui::dynamic_object_field as dof;
use sui::package;
/// Define a capability for the admin of the oracle.
public struct AdminCap has key, store { id: UID }
/// // Define a one-time witness to create the `Publisher` of the oracle.
public struct SIMPLE_WEATHER has drop {}
// Define a struct for the weather oracle
public struct WeatherOracle has key {
id: UID,
/// The address of the oracle.
address: address,
/// The name of the oracle.
name: String,
/// The description of the oracle.
description: String,
}
// Define a struct for each city that the oracle covers
public struct CityWeatherOracle has key, store {
id: UID,
geoname_id: u32, // The unique identifier of the city
name: String, // The name of the city
country: String, // The country of the city
pressure: u32, // The atmospheric pressure in hPa
// ...
}
/// Module initializer. Uses One Time Witness to create Publisher and transfer it to sender.
fun init(otw: SIMPLE_WEATHER, ctx: &mut TxContext) {
package::claim_and_keep(otw, ctx); // Claim ownership of the one-time witness and keep it
let cap = AdminCap { id: object::new(ctx) }; // Create a new admin capability object
transfer::share_object(WeatherOracle {
id: object::new(ctx),
address: tx_context::sender(ctx),
name: string::utf8(b"SuiMeteo"),
description: string::utf8(b"A weather oracle for posting weather updates (temperature, pressure, humidity, visibility, wind metrics and cloud state) for major cities around the world. Currently the data is fetched from https://openweathermap.org. SuiMeteo provides the best available information, but it does not guarantee its accuracy, completeness, reliability, suitability, or availability. Use it at your own risk and discretion."),
});
transfer::public_transfer(cap, tx_context::sender(ctx)); // Transfer the admin capability to the sender.
}
// Public function for adding a new city to the oracle
public fun add_city(
_: &AdminCap, // The admin capability
oracle: &mut WeatherOracle, // A mutable reference to the oracle object
geoname_id: u32, // The unique identifier of the city
name: String, // The name of the city
country: String, // The country of the city
ctx: &mut TxContext // A mutable reference to the transaction context
) {
dof::add(&mut oracle.id, geoname_id, // Add a new dynamic object field to the oracle object with the geoname ID as the key and a new city weather oracle object as the value.
CityWeatherOracle {
id: object::new(ctx), // Assign a unique ID to the city weather oracle object
geoname_id, // Set the geoname ID of the city weather oracle object
name, // Set the name of the city weather oracle object
country, // Set the country of the city weather oracle object
pressure: 0, // Initialize the pressure to be zero
}
);
}
// Public function for removing an existing city from the oracle
public fun remove_city(_: &AdminCap, oracle: &mut WeatherOracle, geoname_id: u32) {
let CityWeatherOracle { id, geoname_id: _, name: _, country: _, pressure: _} = dof::remove(&mut oracle.id, geoname_id);
object::delete(id);
}
// Public function for updating the weather conditions of a city
public fun update(
_: &AdminCap,
oracle: &mut WeatherOracle,
geoname_id: u32,
pressure: u32,
) {
let city_weather_oracle_mut = dof::borrow_mut<u32, CityWeatherOracle>(&mut oracle.id, geoname_id); // Borrow a mutable reference to the city weather oracle object with the geoname ID as the key
city_weather_oracle_mut.pressure = pressure;
}
/// Returns the `pressure` of the `CityWeatherOracle` with the given `geoname_id`.
public fun city_weather_oracle_pressure(
weather_oracle: &WeatherOracle,
geoname_id: u32
): u32 {
let city_weather_oracle = dof::borrow<u32, CityWeatherOracle>(&weather_oracle.id, geoname_id);
city_weather_oracle.pressure
}
// This function updates the name of a weather oracle contract.
// It takes an admin capability, a mutable reference to the weather oracle, and a new name as arguments.
// It assigns the new name to the weather oracle's name field.
public fun update_name(_: &AdminCap, weather_oracle: &mut WeatherOracle, name: String) {
weather_oracle.name = name;
}
// This function updates the description of a weather oracle contract.
// It takes an admin capability, a mutable reference to the weather oracle, and a new description as arguments.
// It assigns the new description to the weather oracle's description field.
public fun update_description(_: &AdminCap, weather_oracle: &mut WeatherOracle, description: String) {
weather_oracle.description = description;
}
}
(2)部署简化版weather-oracle合约
$ sui client publish
- 记录关键对象
export PACKAGE_ID=0xd12ea4bb23747bbf18b54e3ce4857aa84a89f053e677d79f84642e99a1753f06
export WEATHER_ORACLE=0x50fe253f3913d4c120f1cc96a69db10b9bebed01a4fcb4e3957336f33d9fa4c3
export ADMIN_CAP=0x02714caa89b4d990321035c0d946c650bb5e20f3a73679cfc3ce2de15bb00103
(3)修改Move.toml
- 将PACKAGE_ID更新到Move.toml
[package]
name = "simple_weather_oracle"
edition = "2024.beta"
version = "1.0.0"
published-at = "0xd12ea4bb23747bbf18b54e3ce4857aa84a89f053e677d79f84642e99a1753f06"
[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" }
[addresses]
oracle = "0xd12ea4bb23747bbf18b54e3ce4857aa84a89f053e677d79f84642e99a1753f06"
(4)提交简化版weather-oracle到github
(5)添加城市
$ export GEO_NAME_ID_SZ=1795566
$ sui client call --package $PACKAGE_ID --module simple_weather --function add_city --args $ADMIN_CAP $WEATHER_ORACLE $GEO_NAME_ID_SZ Shenzhen CN
(6)更新气压值
$ sui client call --package $PACKAGE_ID --module simple_weather --function update --args $ADMIN_CAP $WEATHER_ORACLE $GEO_NAME_ID_SZ 99700
(7)获取气压值
$ sui client call --package $PACKAGE_ID --module simple_weather --function city_weather_oracle_pressure --args $WEATHER_ORACLE $GEO_NAME_ID_SZ
业务合约部署
$ sui client publish
- 记录关键信息
export PACKAGE_ID=0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e
浏览器调用
https://suiscan.xyz/testnet/object/0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e/contracts
可见得到的骰子点数为:5:
https://suiscan.xyz/testnet/tx/F1XkZ4VmyajJrwuuj6ooQFAnR2sGPna8LMwyf3a3yGuQ
命令行调用
$ sui client call --package $PACKAGE_ID --module weather_oracle --function roll_dice_nft --args $WEATHER_ORACLE $GEO_NAME_ID_SZ
可见得到的骰子点数为:5。
代码调用
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";
import dotenv from "dotenv";
dotenv.config();
const MNEMONIC = process.env.MNEMONIC!;
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);
const FULLNODE_URL = "https://fullnode.testnet.sui.io:443";
const PACKAGE_ID =
"0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e";
const WEATHER_ORACLE =
"0x50fe253f3913d4c120f1cc96a69db10b9bebed01a4fcb4e3957336f33d9fa4c3";
const MODULE_NAME = "weather_oracle";
const FUNCTION_NAME = "roll_dice_nft";
const GEO_NAME_ID_SZ = 1795566;
const SUI_CLIENT = new SuiClient({ url: FULLNODE_URL });
(async () => {
let tx = new Transaction();
tx.moveCall({
target: `${PACKAGE_ID}::${MODULE_NAME}::${FUNCTION_NAME}`,
arguments: [tx.object(WEATHER_ORACLE), tx.pure.u32(Number(GEO_NAME_ID_SZ))],
});
try {
const result = await SUI_CLIENT.signAndExecuteTransaction({
transaction: tx,
signer: keypair,
options: {
showEvents: true,
},
});
console.log(
`signAndExecuteTransactionBlock result: ${JSON.stringify(
result,
null,
2
)}`
);
} catch (e) {
console.error(e);
}
})();
- 执行输出
可见得到的骰子点数为:5。
$ ts-node client_weather_oracle.ts
signAndExecuteTransactionBlock result: {
"digest": "E9MYantGeDx4XaWDhpBzCMELsLm3WeB5uq2SqGHfiaWb",
"events": [
{
"id": {
"txDigest": "E9MYantGeDx4XaWDhpBzCMELsLm3WeB5uq2SqGHfiaWb",
"eventSeq": "0"
},
"packageId": "0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e",
"transactionModule": "weather_oracle",
"sender": "0xa244617bc05e4122fb825d3b9c63dbad96dd06fae8183c2f03027b1feff12028",
"type": "0xb5b69526e8c37cc195b4ca881c60a897194ffd11b8adb3638b5909fbdb44119e::weather_oracle::DiceEvent",
"parsedJson": {
"value": 5
},
"bcs": "8QwQj"
}
],
"confirmedLocalExecution": false
}
更多资料
https://docs.sui.io/guides/developer/app-examples/weather-oracle
https://blog.sui.io/sui-weather-oracle/
drand
模块说明
-
定义
Drand是一个分布式随机性信标(Distributed randomness beacon),提供公开可验证、不可预测且无偏的随机数服务。
-
获取方式
可以通过向
Drand
服务发送HTTP GET
请求来获取随机数,还可以使用客户端库(GO、JS
)为应用程序获取随机数。 -
公共端点
Provider URL Protocol Labs https://api.drand.sh/ https://api2.drand.sh/ https://api3.drand.sh/ Cloudflare https://drand.cloudflare.com/ StorSwift https://api.drand.secureweb3.com:6875/ -
网络分类
分类 随机数产生频率 网络模式 生成方式 安全性 性能 默认网络 30s 链式模式( chained mode
)每个随机数依赖于前一个随机数,形成随机数链 较高 较低 快速网络 3s 非链式模式( unchained mode
)每个随机数独立生成,不依赖于之前的随机数 较低 较高
方法图解
https://drand.love/docs/http-api-reference
代码示例
以下同样是投骰子的示例,每次调用将通过传入drand
随机数得到一个 1~6 的随机数 NFT。
module cookbook::drand{
use sui::event;
use cookbook::drand_lib::{derive_randomness,
verify_drand_signature, safe_selection};
public struct Dice has key, store {
id: UID,
value: u8,
}
public struct DiceEvent has copy, drop {
value: u8,
}
entry fun roll_dice(current_round: u64, drand_sig: vector<u8>): u8 {
verify_drand_signature(drand_sig, current_round);
let digest = derive_randomness(drand_sig);
let random_index = safe_selection(6, &digest);
(random_index as u8) + 1
}
entry fun roll_dice_nft(current_round: u64, drand_sig: vector<u8>, ctx: &mut TxContext) {
let value = roll_dice(current_round, drand_sig);
let dice = Dice {
id: object::new(ctx),
value,
};
event::emit(DiceEvent { value });
transfer::transfer(dice, tx_context::sender(ctx));
}
}
合约操作
业务合约部署
$ sui client publish
- 记录关键信息
export PACKAGE_ID=0xf023a2832843d1d983fd91a25bdb36f43d45d0728bfc2df9c26c655e58ca09e2
命令行调用
# 获取最新轮次和随机数签名
$ curl -s https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest > output.txt
$ export CURRENT_ROUND=`jq '.round' output.txt`
$ export SIGNATURE=0x`jq -r '.signature' output.txt`
$ cat output.txt | jq
{
"round": 10938210,
"signature": "a23596a3f17b8c3553cbb590b6f11081603ab909d5603db45134c7aa9977c3bf9dfa0681144b8dfa8fed4452e9c2204d",
"randomness": "d6e5474b7c1e27b09791bb9b483d16d9e01a7df2f1645b847ddae381ef0c6647"
}
$ sui client call --package $PACKAGE_ID --module drand --function roll_dice_nft --args $CURRENT_ROUND $SIGNATURE
可见得到的骰子点数为:4。
代码调用
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
import { SuiClient } from "@mysten/sui/client";
import { Transaction } from "@mysten/sui/transactions";
import axios from "axios";
import dotenv from "dotenv";
dotenv.config();
const MNEMONIC = process.env.MNEMONIC!;
const keypair = Ed25519Keypair.deriveKeypair(MNEMONIC);
const FULLNODE_URL = "https://fullnode.testnet.sui.io:443";
const PACKAGE_ID =
"0xe94fae15e81744ec0d64c45de6efe8feeb7e14d9894b20316d7e57b7a8274ad0";
const MODULE_NAME = "drand";
const FUNCTION_NAME = "roll_dice_nft";
const SUI_CLIENT = new SuiClient({ url: FULLNODE_URL });
function hexStringToU8Vector(hexString: string): number[] {
const u8Vector: number[] = [];
for (let i = 0; i < hexString.length; i += 2) {
u8Vector.push(parseInt(hexString.slice(i, i + 2), 16));
}
return u8Vector;
}
(async () => {
const url =
"https://drand.cloudflare.com/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/latest";
const response = await axios.get(url);
const data = response.data;
const round = data.round; // eg: 10934869
const signature = data.signature; // eg: "b2916323d2a94f95648f8cc72c3462352bcca735391f5d29c141166da03526b5ab6c63b0cc5905251f9d06e5e0420e9f";
let tx = new Transaction();
let signatureVec = hexStringToU8Vector(signature);
tx.moveCall({
target: `${PACKAGE_ID}::${MODULE_NAME}::${FUNCTION_NAME}`,
arguments: [tx.pure.u64(round), tx.pure.vector("u8", signatureVec)],
});
try {
const result = await SUI_CLIENT.signAndExecuteTransaction({
transaction: tx,
signer: keypair,
options: {
showEvents: true,
},
});
console.log(
`signAndExecuteTransactionBlock result: ${JSON.stringify(
result,
null,
2
)}`
);
} catch (e) {
console.error(e);
}
})();
- 执行输出
可见得到的骰子点数为:6。
$ ts-node client_drand.ts
signAndExecuteTransactionBlock result: {
"digest": "GwP6FhHwLbS1LvSP4r7YzqZ9ucPYuQKqiXdEmpkNEBQf",
"events": [
{
"id": {
"txDigest": "GwP6FhHwLbS1LvSP4r7YzqZ9ucPYuQKqiXdEmpkNEBQf",
"eventSeq": "0"
},
"packageId": "0xe94fae15e81744ec0d64c45de6efe8feeb7e14d9894b20316d7e57b7a8274ad0",
"transactionModule": "drand",
"sender": "0xa244617bc05e4122fb825d3b9c63dbad96dd06fae8183c2f03027b1feff12028",
"type": "0xe94fae15e81744ec0d64c45de6efe8feeb7e14d9894b20316d7e57b7a8274ad0::drand::DiceEvent",
"parsedJson": {
"value": 6
},
"bcs": "7"
}
],
"confirmedLocalExecution": false
}
collection
dynamic_field
模块说明
dynamic_field(动态字段)
模块定义将结构体和值组合在一起- 可以使用任意名字做字段,也可以在运行时动态进行添加和删除
- 名称可以是任何拥有
copy
、drop
和store
能力的值,这些值包括基本类型以及拥有相应能力的结构体 - 任何具有
store
能力的值都可以被存储 - 可以不具备
key
能力,即不可直接从外部进行访问
源码路径
方法图解
方法说明
分类 | 方法 | 说明 |
---|---|---|
增 | add<...>(object: &mut UID, name: Name, value: Value) | 向对象object 添加名为name 的值为value 的动态字段 |
删 | remove<...>(object: &mut UID, name: Name): Value | 从对象object 中删除名为name 的动态字段,若不存在将会报错 |
remove_if_exists<...>(object: &mut UID, name: Name): Option<Value> | 从对象object 中删除名为name 的动态字段,若存在则已option::some 包装后返回,若不存在返回option::none() | |
改 | borrow_mut<...>(object: &mut UID, name: Name): &mut Value | 从对象object 中获取名为name 的动态字段的可变引用,以便进行对动态字段的修改 |
查 | borrow<...>(object: &UID, name: Name): &Value | 从对象object 中获取名为name 的动态字段的只读引用,用于进行信息查看 |
exists_<...>(object: &UID, name: Name): bool | 若对象object 中存在名为name 的动态字段则返回true ,无需指定value 类型 | |
exists_with_type<...>(object: &UID, name: Name): bool | 若对象object 中存在名为name 的动态字段则返回true ,需指定value 类型 |
代码示例
采用书架和书本的示例,书本对象作为动态字段添加到书架上。
结构定义
// 书架结构定义
public struct Bookshelf has key {
id: UID,
// 书本数量
book_count: u64
}
// 书本结构定义
public struct Book has store {
// 书本标题
title: String,
// 书本描述
description: String,
}
创建书架共享对象
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
book_count: 0,
});
}
添加书本到书架
调用
dynamic_field::add
方法。
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book = Book {
title: ascii::string(title),
description: ascii::string(description)
};
dynamic_field::add(&mut bookshelf.id,title, book);
bookshelf.book_count = bookshelf.book_count + 1;
}
获取书本
调用
dynamic_field::borrow
方法。
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
dynamic_field::borrow(&bookshelf.id, title)
}
设置书本的描述信息
调用
dynamic_field::borrow_mut
方法。
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = dynamic_field::borrow_mut(&mut bookshelf.id, title);
book_mut_ref.description = ascii::string(description);
}
判断书本是否存在
调用
dynamic_field::exists_
和dynamic_field::exists_with_type
方法。
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_field::exists_(&bookshelf.id, title)
}
public fun is_book_exists_with_type(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_field::exists_with_type<vector<u8>, Book>(&bookshelf.id, title)
}
从书架上移除书本
调用
dynamic_field::remove
和dynamic_field::remove_if_exists
方法。
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_count = bookshelf.book_count - 1;
dynamic_field::remove<vector<u8>, Book>(&mut bookshelf.id, title)
}
public fun remove_if_book_exists(bookshelf: &mut Bookshelf, title: vector<u8>): Option<Book> {
bookshelf.book_count = bookshelf.book_count - 1;
dynamic_field::remove_if_exists<vector<u8>, Book>(&mut bookshelf.id, title)
}
完整代码
- dynamic_field
module cookbook::dynamic_field{
use std::ascii::{Self, String};
use sui::dynamic_field;
public struct Bookshelf has key {
id: UID,
book_count: u64
}
public struct Book has store {
title: String,
description: String,
}
// 创建书架共享对象
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
book_count: 0,
});
}
// 放置书本到书架
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book = Book {
title: ascii::string(title),
description: ascii::string(description)
};
dynamic_field::add<vector<u8>, Book>(&mut bookshelf.id,title, book);
bookshelf.book_count = bookshelf.book_count + 1;
}
public fun add_book_obj(bookshelf: &mut Bookshelf, book: Book) {
dynamic_field::add<vector<u8>, Book>(&mut bookshelf.id,
book.title.into_bytes(), book);
bookshelf.book_count = bookshelf.book_count + 1;
}
// 拿取书本
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
dynamic_field::borrow(&bookshelf.id, title)
}
// 设置书本的描述信息
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = dynamic_field::borrow_mut(&mut bookshelf.id, title);
book_mut_ref.description = ascii::string(description);
}
// 判断书本是否存在
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_field::exists_(&bookshelf.id, title)
}
public fun is_book_exists_with_type(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_field::exists_with_type<vector<u8>, Book>(&bookshelf.id, title)
}
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_count = bookshelf.book_count - 1;
dynamic_field::remove<vector<u8>, Book>(&mut bookshelf.id, title)
}
// 如果存在指定标题书本,则从书架上移除书本
public fun remove_if_book_exists(bookshelf: &mut Bookshelf, title: vector<u8>): Option<Book> {
bookshelf.book_count = bookshelf.book_count - 1;
dynamic_field::remove_if_exists<vector<u8>, Book>(&mut bookshelf.id, title)
}
public fun get_book_count(bookshelf: &Bookshelf): u64{
bookshelf.book_count
}
public fun get_book_title(book: &Book): String {
book.title
}
public fun get_book_desc(book: &Book): String {
book.description
}
}
- dynamic_field_tests
#[test_only]
module cookbook::dynamic_field_tests {
use std::ascii;
use sui::test_scenario as ts;
use cookbook::dynamic_field::{Bookshelf, create_bookshelf, add_book, get_book,
set_book_desc, is_book_existed, is_book_exists_with_type, remove_book,
get_book_count, get_book_title, get_book_desc};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_dynamic_field() {
let alice = @0xa;
let mut ts = ts::begin(alice);
// 创建书架
{
create_bookshelf(ts.ctx());
};
// 放置书本到书架
let expected_title = b"Mastering Bitcoin";
let expected_description= b"1st Edition";
let expected_new_description= b"3rd Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_description,
);
assert_eq(bookshelf.get_book_count(), 1);
ts::return_shared(bookshelf);
};
// 拿取书本
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_description));
ts::return_shared(bookshelf);
};
// 设置书本的描述信息
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
set_book_desc(
&mut bookshelf,
expected_title,
expected_new_description,
);
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
ts::return_shared(bookshelf);
};
// 判断书本是否存在
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let is_existed = is_book_existed(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
let is_existed = is_book_exists_with_type(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
ts::return_shared(bookshelf);
};
// 从书架上移除书本
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
assert_eq(bookshelf.get_book_count(), 1);
let book = remove_book(
&mut bookshelf,
expected_title,
);
assert_eq(bookshelf.get_book_count(), 0);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
bookshelf.add_book_obj(book);
ts::return_shared(bookshelf);
};
ts.end();
}
}
dynamic_object_field
模块说明
dynamic_object_field(动态对象字段)
特性同dynamic_field(动态字段)
- 与之不同的是值必须具备
key
能力,对象仍然能从外部通过UID
进行访问
源码路径
方法图解
方法说明
分类 | 方法 | 说明 |
---|---|---|
增 | add<...>(object: &mut UID, name: Name, value: Value) | 向对象object 添加名为name 的值为value 的动态字段 |
删 | remove<...>(object: &mut UID, name: Name): Value | 从对象object 中删除名为name 的动态字段,若不存在将会报错 |
改 | borrow_mut<...>(object: &mut UID, name: Name): &mut Value | 从对象object 中获取名为name 的动态字段的可变引用,以便进行对动态字段的修改 |
查 | borrow<...>(object: &UID, name: Name): &Value | 从对象object 中获取名为name 的动态字段的只读引用,用于进行信息查看 |
exists_<...>(object: &UID, name: Name): bool | 若对象object 中存在名为name 的动态字段则返回true ,无需指定value 类型 | |
exists_with_type<...>(object: &UID, name: Name): bool | 若对象object 中存在名为name 的动态字段则返回true ,需指定value 类型 | |
id<Name: copy + drop + store>(object: &UID, name: Name,): Option<ID> | 返回动态对象字段中的对象ID |
代码示例
同样采用书架(Bookshelf
)和书本(Book
)的示例,书本对象作为动态字段添加到书架上,其中书本需要具备key
能力,该对象可以从外部通过 UID
进行访问。
结构定义
注:
Book
结构需要具备key
能力,结构体中也必须具备UID
public struct Bookshelf has key {
id: UID,
book_count: u64
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
创建书架共享对象
// 创建书架共享对象
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
book_count: 0,
});
}
添加书本到书架
调用
dynamic_field::add
方法。
// 添加书本到书架
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
dynamic_object_field::add<vector<u8>, Book>(&mut bookshelf.id,title, book);
bookshelf.book_count = bookshelf.book_count + 1;
}
public fun add_book_obj(bookshelf: &mut Bookshelf, book: Book) {
dynamic_object_field::add<vector<u8>, Book>(&mut bookshelf.id,
book.title.into_bytes(), book);
bookshelf.book_count = bookshelf.book_count + 1;
}
获取书本
调用
dynamic_field::borrow
方法。
// 获取书本
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
dynamic_object_field::borrow(&bookshelf.id, title)
}
设置书本的描述信息
调用
dynamic_field::borrow_mut
方法。
// 设置书本的描述信息
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = dynamic_object_field::borrow_mut(&mut bookshelf.id, title);
book_mut_ref.description = ascii::string(description);
}
判断书本是否存在
调用
dynamic_field::exists_
和dynamic_field::exists_with_type
方法。
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_object_field::exists_(&bookshelf.id, title)
}
public fun is_book_exists_with_type(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_object_field::exists_with_type<vector<u8>, Book>(&bookshelf.id, title)
}
从书架上移除书本
调用
dynamic_field::remove
和dynamic_field::remove_if_exists
方法。
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_count = bookshelf.book_count - 1;
dynamic_object_field::remove<vector<u8>, Book>(&mut bookshelf.id, title)
}
完整代码
- dynamic_object_field
module cookbook::dynamic_object_field{
use std::ascii::{Self, String};
use sui::dynamic_object_field;
public struct Bookshelf has key {
id: UID,
book_count: u64
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
// 创建书架共享对象
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
book_count: 0,
});
}
// 放置书本到书架
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
dynamic_object_field::add<vector<u8>, Book>(&mut bookshelf.id,title, book);
bookshelf.book_count = bookshelf.book_count + 1;
}
public fun add_book_obj(bookshelf: &mut Bookshelf, book: Book) {
dynamic_object_field::add<vector<u8>, Book>(&mut bookshelf.id,
book.title.into_bytes(), book);
bookshelf.book_count = bookshelf.book_count + 1;
}
// 拿取书本
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
dynamic_object_field::borrow(&bookshelf.id, title)
}
// 设置书本的描述信息
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = dynamic_object_field::borrow_mut(&mut bookshelf.id, title);
book_mut_ref.description = ascii::string(description);
}
// 判断书本是否存在
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_object_field::exists_(&bookshelf.id, title)
}
public fun is_book_exists_with_type(bookshelf: &Bookshelf, title: vector<u8>): bool {
dynamic_object_field::exists_with_type<vector<u8>, Book>(&bookshelf.id, title)
}
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_count = bookshelf.book_count - 1;
dynamic_object_field::remove<vector<u8>, Book>(&mut bookshelf.id, title)
}
public fun get_book_count(bookshelf: &Bookshelf): u64{
bookshelf.book_count
}
public fun get_book_title(book: &Book): String {
book.title
}
public fun get_book_desc(book: &Book): String {
book.description
}
}
- dynamic_field_tests
#[test_only]
module cookbook::dynamic_object_field_tests {
use std::ascii;
use sui::test_scenario as ts;
use cookbook::dynamic_object_field::{Bookshelf, create_bookshelf, add_book, get_book,
set_book_desc, is_book_existed, is_book_exists_with_type, remove_book,
get_book_count, get_book_title, get_book_desc};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_dynamic_object_field() {
let alice = @0xa;
let mut ts = ts::begin(alice);
// 创建书架
{
create_bookshelf(ts.ctx());
};
// 放置书本到书架
let expected_title = b"Mastering Bitcoin";
let expected_description= b"1st Edition";
let expected_new_description= b"3rd Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_description,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 1);
ts::return_shared(bookshelf);
};
// 拿取书本
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_description));
ts::return_shared(bookshelf);
};
// 设置书本的描述信息
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
set_book_desc(
&mut bookshelf,
expected_title,
expected_new_description,
);
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
ts::return_shared(bookshelf);
};
// 判断书本是否存在
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let is_existed = is_book_existed(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
let is_existed = is_book_exists_with_type(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
ts::return_shared(bookshelf);
};
// 从书架上移除书本
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
assert_eq(bookshelf.get_book_count(), 1);
let book = remove_book(
&mut bookshelf,
expected_title,
);
assert_eq(bookshelf.get_book_count(), 0);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
bookshelf.add_book_obj(book);
ts::return_shared(bookshelf);
};
ts.end();
}
}
vec_map & vec_set
模块说明
Sui
的 映射(vec_map) 和 集合(vec_set) 是基于vector实现的数据结构vec_map
是一种映射结构,保证不包含重复的键,但是条目按照插入顺序排列,而不是按键的顺序。vec_set
是一种集合结构,保证不包含重复的键。- 所有的操作时间复杂度为
O(N)
,N
为映射或集合的大小 - 该结构提供方便的操作映射或集合的接口,如何需要按键的顺序排序的映射都需要手工额外处理
源码路径
方法图解
结构定义
VecMap
public struct VecMap<K: copy, V> has copy, drop, store {
contents: vector<Entry<K, V>>,
}
/// An entry in the map
public struct Entry<K: copy, V> has copy, drop, store {
key: K,
value: V,
}
VecSet
public struct VecSet<K: copy + drop> has copy, drop, store {
contents: vector<K>,
}
方法说明
功能 | vec_map | vec_set |
---|---|---|
创建空结构 | empty<K: copy, V>(): VecMap<K,V> | empty<K: copy + drop>(): VecSet<K> |
创建单元素结构 | ❎ | singleton<K: copy + drop>(key: K): VecSet<K> |
插入元素 | insert<K: copy, V>(self: &mut VecMap<K,V>, key: K, value: V) | insert<K: copy + drop>(self: &mut VecSet<K>, key: K) |
移除元素 | remove<K: copy, V>(self: &mut VecMap<K,V>, key: &K): (K, V) | remove<K: copy + drop>(self: &mut VecSet<K>, key: &K) |
是否包含元素 | contains<K: copy, V>(self: &VecMap<K, V>, key: &K): bool | contains<K: copy + drop>(self: &VecSet<K>, key: &K): bool |
结构大小 | size<K: copy, V>(self: &VecMap<K,V>): u64 | size<K: copy + drop>(self: &VecSet<K>): u64 |
判断结构是否为空 | is_empty<K: copy, V>(self: &VecMap<K,V>): bool | is_empty<K: copy + drop>(self: &VecSet<K>): bool |
转换为元素数组 | into_keys_values<K: copy, V>(self: VecMap<K, V>): (vector<K>, vector<V>) | into_keys<K: copy + drop>(self: VecSet<K>): vector<K> |
根据元素数组进行构造 | from_keys_values<K: copy, V>( mut keys: vector<K>, mut values: vector<V>,) | from_keys<K: copy + drop>(mut keys: vector<K>): VecSet<K> |
获取键数组 | keys<K: copy, V>(self: &VecMap<K, V>): vector<K> | keys<K: copy + drop>(self: &VecSet<K>): &vector<K> |
按插入顺序获取指定键的坐标 | ① get_idx<K: copy, V>(self: &VecMap<K,V>, key: &K): u64 ② get_idx_opt<K: copy, V>(self: &VecMap<K,V>, key: &K): Option<u64> | ① get_idx<K: copy + drop>(self: &VecSet<K>, key: &K): u64 ② get_idx_opt<K: copy + drop>(self: &VecSet<K>, key: &K): Option<u64> |
销毁空结构 | destroy_empty<K: copy, V>(self: VecMap<K, V>) | ❎ |
弹出最新插入元素 | pop<K: copy, V>(self: &mut VecMap<K,V>): (K, V) | ❎ |
获取键对应值 | ① get<K: copy, V>(self: &VecMap<K,V>, key: &K): &V ② get_mut<K: copy, V>(self: &mut VecMap<K,V>, key: &K): &mut V ③ try_get<K: copy, V: copy>(self: &VecMap<K,V>, key: &K): Option<V> | ❎ |
按插入顺序索引键值对 | ① get_entry_by_idx<K: copy, V>(self: &VecMap<K, V>, idx: u64): (&K, &V) ② get_entry_by_idx_mut<K: copy, V>(self: &mut VecMap<K, V>, idx: u64): (&K, &mut V) | ❎ |
根据索引坐标移除键值对 | remove_entry_by_idx<K: copy, V>(self: &mut VecMap<K, V>, idx: u64): (K, V) | ❎ |
代码示例
示例中定义了一个书架结构(Bookshelf
),其中包含一个Bag
类型的items
,可以放入任意类型的键值对。示例中以书籍和玩具为例。
结构定义
public struct Bookshelf has key {
id: UID,
books: VecMap<String, Book>,
book_names: VecSet<String>,
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
创建书架共享对象
调用
vec_map::empty()
和vec_set::empty()
方法
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: vec_map::empty(),
book_names: vec_set::empty(),
});
}
放置书本到书架
调用
vec_map::insert()
和vec_set::insert()
方法
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>,
description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.books.insert(ascii::string(title), book);
bookshelf.book_names.insert(ascii::string(title));
}
获取书本
调用
vec_map::get()
和vec_map::pop()
方法。
public fun get_book(bookshelf: &Bookshelf, title: &String): &Book {
bookshelf.books.get(title)
}
public fun get_last_book(bookshelf: &mut Bookshelf): Book {
let (_, book) = bookshelf.books.pop();
book
}
设置书本的描述信息
调用
vec_map::get_mut()
方法。
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = bookshelf.books.get_mut(&ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
判断书本是否存在
调用
vec_map::contains()
或vec_set::contains()
方法
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
// bookshelf.book_names.contains(&ascii::string(title))
bookshelf.books.contains(&ascii::string(title))
}
从书架上移除书本
调用
vec_map::remove()
和vec_map::remove()
方法。
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_names.remove(&ascii::string(title));
let (_, book) = bookshelf.books.remove(&ascii::string(title));
book
}
判断书架是否为空
调用
vec_map::is_empty()
或vec_map::is_empty()
方法。
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
// bookshelf.book_names.is_empty()
bookshelf.books.is_empty()
}
获取书本数量
调用
vec_map::size()
或vec_map::size()
方法。
public fun get_book_count(bookshelf: &Bookshelf): u64{
// bookshelf.book_names.size()
bookshelf.books.size()
}
销毁空书架
调用
vec_map::destroy_empty
方法。
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, books, book_names:_} = bookshelf;
books.destroy_empty();
id.delete()
}
完整代码
- vec_map_and_vec_set
module cookbook::vec_map_and_vec_set{
use std::ascii::{Self, String};
use sui::vec_map::{Self, VecMap};
use sui::vec_set::{Self, VecSet};
public struct Bookshelf has key {
id: UID,
books: VecMap<String, Book>,
book_names: VecSet<String>,
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
// 创建书架共享对象
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: vec_map::empty(),
book_names: vec_set::empty(),
});
}
// 销毁空书架
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, books, book_names:_} = bookshelf;
books.destroy_empty();
id.delete()
}
// 放置书本到书架
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>,
description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.books.insert(ascii::string(title), book);
bookshelf.book_names.insert(ascii::string(title));
}
// 拿取书本
public fun get_book(bookshelf: &Bookshelf, title: &String): &Book {
bookshelf.books.get(title)
}
// 取出最后一本放到书架上的书
public fun get_last_book(bookshelf: &mut Bookshelf): Book {
let (_, book) = bookshelf.books.pop();
book
}
// 设置书本的描述信息
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref: &mut Book = bookshelf.books.get_mut(&ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
// 判断书本是否存在
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
// bookshelf.book_names.contains(&ascii::string(title))
bookshelf.books.contains(&ascii::string(title))
}
// 判断书架是否为空
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
// bookshelf.book_names.is_empty()
bookshelf.books.is_empty()
}
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.book_names.remove(&ascii::string(title));
let (_, book) = bookshelf.books.remove(&ascii::string(title));
book
}
public fun get_book_count(bookshelf: &Bookshelf): u64{
// bookshelf.book_names.size()
bookshelf.books.size()
}
public fun get_book_title(book: &Book): String {
book.title
}
public fun get_book_desc(book: &Book): String {
book.description
}
}
- vec_map_and_vec_set_tests
#[test_only]
module cookbook::vec_map_and_vec_set_tests {
use std::ascii;
use sui::test_scenario as ts;
use cookbook::vec_map_and_vec_set::{Bookshelf, create_bookshelf, destroy_empty_bookshelf,
add_book, get_book, set_book_desc, is_book_existed, remove_book, is_bookshelf_empty,
get_book_count, get_book_title, get_book_desc};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_vec_map() {
let alice = @0xa;
let mut ts = ts::begin(alice);
// 创建书架
{
create_bookshelf(ts.ctx());
};
// 放置书本到书架
let expected_title = b"Mastering Bitcoin";
let expected_description= b"1st Edition";
let expected_new_description= b"3rd Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_description,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 1);
assert_eq(bookshelf.is_bookshelf_empty(), false);
ts::return_shared(bookshelf);
};
// 放置书本2到书架
let expected_title2 = b"Move Cookbook";
let expected_description2= b"1st Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title2,
expected_description2,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 2);
ts::return_shared(bookshelf);
};
// 拿取书本
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let book = get_book(
&bookshelf,
&ascii::string(expected_title),
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_description));
ts::return_shared(bookshelf);
};
// 设置书本的描述信息
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
set_book_desc(
&mut bookshelf,
expected_title,
expected_new_description,
);
let book = get_book(
&bookshelf,
&ascii::string(expected_title),
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
ts::return_shared(bookshelf);
};
// 判断书本是否存在
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let is_existed = is_book_existed(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
ts::return_shared(bookshelf);
};
// 从书架上借走书本
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
{
assert_eq(bookshelf.get_book_count(), 2);
let book = remove_book(
&mut bookshelf,
expected_title,
);
assert_eq(bookshelf.get_book_count(), 1);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
transfer::public_transfer(book, alice);
};
{
assert_eq(bookshelf.get_book_count(), 1);
let book = remove_book(
&mut bookshelf,
expected_title2,
);
assert_eq(bookshelf.get_book_count(), 0);
assert_eq(book.get_book_title(), ascii::string(expected_title2));
assert_eq(book.get_book_desc(), ascii::string(expected_description2));
transfer::public_transfer(book, alice);
};
ts::return_shared(bookshelf);
};
// 销毁书架
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
destroy_empty_bookshelf(bookshelf);
};
ts.end();
}
}
table
模块说明
Table
是一种类似于map
的集合- 与传统集合不同的是,它的键和值不是存储在
Table
值中,而是使用Sui
的对象系统(sui::dynamic_field
)进行存储 Table
结构仅作为一个句柄,用于在对象系统中检索这些键和值- 具有完全相同的键值映射的
Table
值不会被==
判断为相等 Table<K, V>
是一个同构映射,意味着所有其键和值都具有相同的类型
源码路径
方法图解
结构定义
public struct Table<phantom K: copy + drop + store, phantom V: store> has key, store {
/// the ID of this table
id: UID,
/// the number of key-value pairs in the table
size: u64,
}
方法说明
分类 | 方法 | 说明 |
---|---|---|
初始化 | new<...>(ctx: &mut TxContext): Table<K, V> | 创建空Table |
清理 | destroy_empty<...>(table: Table<K, V>) | 销毁空Table ,若不为空,将报错:ETableNotEmpty |
drop<...>(table: Table<K, V>) | 删除一个可能非空的表格。只有在值具有drop 能力时才可用 | |
增 | add<...>(table: &mut Table<K, V>, k: K, v: V) | 添加键值对到Table 中若 Key 已存在,将报错:EFieldAlreadyExists |
删 | remove<...>(table: &mut Table<K, V>, k: K): V | 从Table 中删除并返回指定Key 的键值对,若Key 不存在将报错:EFieldDoesNotExist |
改 | borrow_mut<...>(table: &mut Table<K, V>, k: K): &mut V | 从Table 中读取指定Key 的值的可变引用,以便进行对值进行修改,若Key 不存在将报错:EFieldDoesNotExist |
查 | borrow<...>(table: &Table<K, V>, k: K): &V | 从Table 中读取指定Key 的值,若Key 不存在将报错:EFieldDoesNotExist |
contains<...>(table: &Table<K, V>, k: K): bool | 若Table 中包含指定的Key 的值返回true ,否则返回false | |
length<...>(table: &Table<K, V>): u64 | 获取Table 的长度 | |
is_empty<...>(table: &Table<K, V>): bool | 当且仅当Table 为空时返回true ,否则返回false |
代码示例
同样采用书架和书本的示例,书本对象将添加到书架的Table
中。
结构定义
public struct Bookshelf has key {
id: UID,
books: Table<String, Book>
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
创建书架共享对象
调用
table::new
方法
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: table::new<String, Book>(ctx),
});
}
添加书本到书架
调用
table::add
方法
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.books.add(book.title, book);
}
获取书本
调用
table::borrow
方法。
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
bookshelf.books.borrow(ascii::string(title))
}
设置书本的描述信息
调用
table::borrow_mut
方法。
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref = bookshelf.books.borrow_mut(ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
判断书本是否存在
调用
table::contains
方法。
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
bookshelf.books.contains(ascii::string(title))
}
从书架上移除书本
调用
table::remove
方法。
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.books.remove(ascii::string(title))
}
判断书架是否为空
调用
table::is_empty
方法。
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
bookshelf.books.is_empty()
}
获取书本数量
调用
table::length
方法。
public fun get_book_count(bookshelf: &Bookshelf): u64{
bookshelf.books.length()
}
销毁空书架
调用
table::destroy_empty
方法。
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, books} = bookshelf;
books.destroy_empty();
id.delete()
}
完整代码
- table
module cookbook::table {
use sui::table::{Self, Table};
use std::ascii::{Self, String};
public struct Bookshelf has key {
id: UID,
books: Table<String, Book>
}
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
// 创建书架
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
books: table::new<String, Book>(ctx),
});
}
// 销毁空书架
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, books} = bookshelf;
books.destroy_empty();
id.delete()
}
// 添加书籍
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>,
description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.books.add(book.title, book);
}
// 拿取书本
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
bookshelf.books.borrow(ascii::string(title))
}
// 设置书本描述
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref = bookshelf.books.borrow_mut(ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.books.remove(ascii::string(title))
}
// 判断书本是否存在
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
bookshelf.books.contains(ascii::string(title))
}
// 判断书架是否为空
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
bookshelf.books.is_empty()
}
// 获取书本数量
public fun get_book_count(bookshelf: &Bookshelf): u64{
bookshelf.books.length()
}
public fun get_book_title(book: &Book): String {
book.title
}
public fun get_book_desc(book: &Book): String {
book.description
}
}
- table_tests
#[test_only]
module cookbook::table_tests {
use std::ascii;
use sui::test_scenario as ts;
use cookbook::table::{Bookshelf, create_bookshelf, destroy_empty_bookshelf,
add_book, get_book, set_book_desc, is_book_existed, remove_book, is_bookshelf_empty,
get_book_count, get_book_title, get_book_desc};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_table() {
let alice = @0xa;
let mut ts = ts::begin(alice);
// 创建书架
{
create_bookshelf(ts.ctx());
};
// 放置书本到书架
let expected_title = b"Mastering Bitcoin";
let expected_description= b"1st Edition";
let expected_new_description= b"3rd Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_description,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 1);
assert_eq(bookshelf.is_bookshelf_empty(), false);
ts::return_shared(bookshelf);
};
// 放置书本2到书架
let expected_title2 = b"Move Cookbook";
let expected_description2= b"1st Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title2,
expected_description2,
ts.ctx(),
);
assert_eq(bookshelf.get_book_count(), 2);
ts::return_shared(bookshelf);
};
// 拿取书本
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_description));
ts::return_shared(bookshelf);
};
// 设置书本的描述信息
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
set_book_desc(
&mut bookshelf,
expected_title,
expected_new_description,
);
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
ts::return_shared(bookshelf);
};
// 判断书本是否存在
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let is_existed = is_book_existed(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
ts::return_shared(bookshelf);
};
// 从书架上借走书本
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
{
assert_eq(bookshelf.get_book_count(), 2);
let book = remove_book(
&mut bookshelf,
expected_title,
);
assert_eq(bookshelf.get_book_count(), 1);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
transfer::public_transfer(book, alice);
};
{
assert_eq(bookshelf.get_book_count(), 1);
let book = remove_book(
&mut bookshelf,
expected_title2,
);
assert_eq(bookshelf.get_book_count(), 0);
assert_eq(book.get_book_title(), ascii::string(expected_title2));
assert_eq(book.get_book_desc(), ascii::string(expected_description2));
transfer::public_transfer(book, alice);
};
ts::return_shared(bookshelf);
};
// 销毁书架
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
destroy_empty_bookshelf(bookshelf);
};
ts.end();
}
}
bag
模块说明
Bag
同样是一种类似于map
的集合- 与
Table
的同构映射不同,Bag
是异构映射集合,可以容纳任意类型的键值对 - 与
Table
类似,它的键和值不是存储在Bag
值中,而是使用Sui
的对象系统(sui::dynamic_field
)进行存储 Bag
结构仅作为一个句柄,用于在对象系统中检索这些键和值- 具有完全相同的键值映射的
Bag
值不会被==
判断为相等 Bag
必须为空才能被销毁
源码路径
方法图解
结构定义
Bag
类型没有任何类型参数,可以使用任意类型作为键值。
public struct Bag has key, store {
/// the ID of this bag
id: UID,
/// the number of key-value pairs in the bag
size: u64,
}
方法说明
分类 | 方法 | 说明 |
---|---|---|
初始化 | new(ctx: &mut TxContext): Bag | 创建空Bag |
清理 | destroy_empty<...>(bag: Bag) | 销毁空Bag ,若不为空,将报错:ETableNotEmpty |
增 | add<...>(bag: &mut Bag, k: K, v: V) | 添加键值对到Bag 中若 Key 已存在,将报错:EFieldAlreadyExists |
删 | remove<...>(bag: &mut Bag, k: K): V | 从Bag 中删除并返回指定Key 的键值对若 Key 不存在将报错:EFieldDoesNotExist 若值的类型与指定类型不匹配,将报错: EFieldTypeMismatch |
改 | borrow_mut<...>(bag: &Bag, k: K): &mut V | 从Bag 中读取指定Key 的值的可变引用,以便进行对值进行修改若 Key 不存在,将报错:EFieldDoesNotExist 若值的类型与指定类型不匹配,将报错: EFieldTypeMismatch |
查 | borrow<...>(bag: &Bag, k: K): &V | 从Bag 中读取指定Key 的值若 Key 不存在,将报错:EFieldDoesNotExist 若值的类型与指定类型不匹配,将报错: EFieldTypeMismatch |
contains<K: ...>(bag: &Bag, k: K): bool | 若Bag 中包含指定的Key 的值返回true ,否则返回false | |
contains_with_type<K: ..., V: store>(bag: &Bag, k: K): bool | 若Bag 中包含指定的Key 的指定类型的值将返回true ,否则返回false | |
length<...>(bag: &Bag): u64 | 获取Bag 的长度 | |
is_empty<...>(bag: &Bag): bool | 当且仅当Bag 为空时返回true ,否则返回false |
代码示例
示例中定义了一个书架结构(Bookshelf
),其中包含一个Bag
类型的items
,可以放入任意类型的键值对。示例中以在书架上放置书籍(Book
)和玩具(Toy
)两种不同的对象为例。
结构定义
public struct Bookshelf has key {
id: UID,
items: Bag,
}
// 书籍
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
// 玩具
public struct Toy has key, store {
id: UID,
name: String,
category: String,
}
创建书架共享对象
调用
bag::new
方法
// 创建书架
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
items: bag::new(ctx),
});
}
放置书本到书架
调用
bag::add
方法
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.items.add(book.title, book);
}
放置玩具到书架
调用
bag::add
方法
public fun add_toy(bookshelf: &mut Bookshelf, name: vector<u8>, category: vector<u8>, ctx: &mut TxContext) {
let toy = Toy {
id: object::new(ctx),
name: ascii::string(name),
category: ascii::string(category)
};
bookshelf.items.add(toy.name, toy);
}
获取书本
调用
bag::borrow
方法。
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
bookshelf.items.borrow(ascii::string(title))
}
获取玩具
调用
bag::borrow
方法。
public fun get_toy(bookshelf: &Bookshelf, name: vector<u8>): &Toy{
bookshelf.items.borrow(ascii::string(name))
}
设置书本的描述信息
调用
bag::borrow_mut
方法。
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref = bookshelf.items.borrow_mut<_, Book>(ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
判断书本是否存在
调用
bag::contains
方法。
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
bookshelf.items.contains(ascii::string(title))
}
从书架上移除书本
调用
bag::remove
方法。
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.items.remove(ascii::string(title))
}
从书架上移除玩具
调用
bag::remove
方法。
public fun remove_toy(bookshelf: &mut Bookshelf, name: vector<u8>): Toy{
bookshelf.items.remove(ascii::string(name))
}
判断书架是否为空
调用
bag::is_empty
方法。
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
bookshelf.items.is_empty()
}
获取书架上物品数量
调用
bag::length
方法。
public fun get_count(bookshelf: &Bookshelf): u64{
bookshelf.items.length()
}
销毁空书架
调用
bag::destroy_empty
方法。
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, items} = bookshelf;
items.destroy_empty();
id.delete()
}
完整代码
- bag
module cookbook::bag {
use sui::bag::{Self, Bag};
use std::ascii::{Self, String};
public struct Bookshelf has key {
id: UID,
items: Bag,
}
// 书籍
public struct Book has key, store {
id: UID,
title: String,
description: String,
}
// 玩具
public struct Toy has key, store {
id: UID,
name: String,
category: String,
}
// 创建书架
public fun create_bookshelf(ctx: &mut TxContext) {
transfer::share_object(Bookshelf {
id: object::new(ctx),
items: bag::new(ctx),
});
}
// 销毁空书架
public fun destroy_empty_bookshelf(bookshelf: Bookshelf) {
let Bookshelf {id, items} = bookshelf;
items.destroy_empty();
id.delete()
}
// 放置书籍
public fun add_book(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>, ctx: &mut TxContext) {
let book = Book {
id: object::new(ctx),
title: ascii::string(title),
description: ascii::string(description)
};
bookshelf.items.add(book.title, book);
}
// 放置玩具
public fun add_toy(bookshelf: &mut Bookshelf, name: vector<u8>, category: vector<u8>, ctx: &mut TxContext) {
let toy = Toy {
id: object::new(ctx),
name: ascii::string(name),
category: ascii::string(category)
};
bookshelf.items.add(toy.name, toy);
}
// 拿取书本
public fun get_book(bookshelf: &Bookshelf, title: vector<u8>): &Book {
bookshelf.items.borrow(ascii::string(title))
}
// 拿取玩具
public fun get_toy(bookshelf: &Bookshelf, name: vector<u8>): &Toy{
bookshelf.items.borrow(ascii::string(name))
}
// 设置书本描述
public fun set_book_desc(bookshelf: &mut Bookshelf, title: vector<u8>, description: vector<u8>) {
let book_mut_ref = bookshelf.items.borrow_mut<_, Book>(ascii::string(title));
book_mut_ref.description = ascii::string(description);
}
// 从书架上移除书本
public fun remove_book(bookshelf: &mut Bookshelf, title: vector<u8>): Book {
bookshelf.items.remove(ascii::string(title))
}
// 从书架上移除玩具
public fun remove_toy(bookshelf: &mut Bookshelf, name: vector<u8>): Toy{
bookshelf.items.remove(ascii::string(name))
}
// 判断书本是否存在
public fun is_book_existed(bookshelf: &Bookshelf, title: vector<u8>): bool {
bookshelf.items.contains(ascii::string(title))
}
// 判断书架是否为空
public fun is_bookshelf_empty(bookshelf: &Bookshelf): bool {
bookshelf.items.is_empty()
}
public fun get_count(bookshelf: &Bookshelf): u64{
bookshelf.items.length()
}
public fun get_book_title(book: &Book): String {
book.title
}
public fun get_book_desc(book: &Book): String {
book.description
}
public fun get_toy_name(toy: &Toy): String {
toy.name
}
public fun get_toy_category(toy: &Toy): String {
toy.category
}
}
- bag_tests
#[test_only]
module cookbook::bag_tests {
use std::ascii;
use sui::test_scenario as ts;
use cookbook::bag::{Bookshelf, create_bookshelf, destroy_empty_bookshelf,
add_book, add_toy, get_book, get_toy, set_book_desc, is_book_existed,
remove_book, is_bookshelf_empty, get_count, get_book_title, get_book_desc,
remove_toy, get_toy_name, get_toy_category};
#[test_only]
use sui::test_utils::assert_eq;
#[test]
public fun test_bag() {
let alice = @0xa;
let mut ts = ts::begin(alice);
// 创建书架
{
create_bookshelf(ts.ctx());
};
// 放置书本到书架
let expected_title = b"Mastering Bitcoin";
let expected_description= b"1st Edition";
let expected_new_description= b"3rd Edition";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_book(
&mut bookshelf,
expected_title,
expected_description,
ts.ctx(),
);
assert_eq(bookshelf.get_count(), 1);
assert_eq(bookshelf.is_bookshelf_empty(), false);
ts::return_shared(bookshelf);
};
// 放置玩具到书架
let expected_name= b"Lego set";
let expected_category= b"Building Blocks";
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
add_toy(
&mut bookshelf,
expected_name,
expected_category,
ts.ctx(),
);
assert_eq(bookshelf.get_count(), 2);
ts::return_shared(bookshelf);
};
// 拿取书本和玩具
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
{
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_description));
};
{
let toy = get_toy(
&bookshelf,
expected_name,
);
assert_eq(toy.get_toy_name(), ascii::string(expected_name));
assert_eq(toy.get_toy_category(), ascii::string(expected_category));
};
ts::return_shared(bookshelf);
};
// 设置书本的描述信息
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
set_book_desc(
&mut bookshelf,
expected_title,
expected_new_description,
);
let book = get_book(
&bookshelf,
expected_title,
);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
ts::return_shared(bookshelf);
};
// 判断书本是否存在
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
let is_existed = is_book_existed(
&bookshelf,
expected_title,
);
assert_eq(is_existed, true);
ts::return_shared(bookshelf);
};
// 从书架上拿走书本和玩具
{
ts.next_tx(alice);
let mut bookshelf: Bookshelf = ts.take_shared();
{
assert_eq(bookshelf.get_count(), 2);
let book = remove_book(
&mut bookshelf,
expected_title,
);
assert_eq(bookshelf.get_count(), 1);
assert_eq(book.get_book_title(), ascii::string(expected_title));
assert_eq(book.get_book_desc(), ascii::string(expected_new_description));
transfer::public_transfer(book, alice);
};
{
assert_eq(bookshelf.get_count(), 1);
let toy = remove_toy(
&mut bookshelf,
expected_name,
);
assert_eq(bookshelf.get_count(), 0);
assert_eq(toy.get_toy_name(), ascii::string(expected_name));
assert_eq(toy.get_toy_category(), ascii::string(expected_category));
transfer::public_transfer(toy, alice);
};
ts::return_shared(bookshelf);
};
// 销毁书架
{
ts.next_tx(alice);
let bookshelf: Bookshelf = ts.take_shared();
destroy_empty_bookshelf(bookshelf);
};
ts.end();
}
}
钱包领水
本文将介绍Sui
生态常见的三款钱包在测试网和开发网的领水方式。
Sui Wallet
Surf Wallet
Suiet
discord领水
本文将介绍在Sui
的discord
领取测试网和开发网测试代币的方法。
加入Sui discord
https://discord.com/invite/sui
在水龙头频道领取测试代币
在Sui #devnet-faucet 或 #testnet-faucet Discord频道中请求测试代币,请求格式如下:
!faucet <YOUR SUI ADDRESS>
命令行领水
本文将介绍使用Sui CLI
命令和curl
命令领取测试代币的方法。
CLI领水
- 查看所在网络
$ sui client envs
- 切换网络
若不是测试网或开发网进行切换。
$ sui client switch --env xxx
- 执行领水命令
$ sui client faucet
Request successful. It can take up to 1 minute to get the coin. Run sui client gas to check your gas coins.
- 查看领水结果
$ sui client gas --json
[
{
"gasCoinId": "0xb12724820ac35687458d97f2bfabb56ae77dc89140dc24918cf3afc6d0c76c1a",
"mistBalance": 1000000000,
"suiBalance": "1.00"
}
]
curl命令领水
curl
命令仅在开发网可以领水,请求格式如下:
curl --location --request POST 'https://faucet.devnet.sui.io/gas' \
--header 'Content-Type: application/json' \
--data-raw '{
"FixedAmountRequest": {
"recipient": "<YOUR SUI ADDRESS>"
}
}'
IDE
VSCode插件
安装sui-move-analyzer language server
在安装VSCode插件前,需要先安装
sui-move-analyzer language server
,否则将报错:![]()
方式1:直接下载发行版本
-
下载最新版本
下载地址:https://github.com/movebit/sui-move-analyzer/releases
-
改名为:
sui-move-analyzer
或sui-move-analyzer.exe
-
添加到PATH环境变量中
-
重启
VSCode
方式2:使用cargo命令安装
- 执行安装命令
cargo install --git http://github.com/movebit/sui-move-analyzer --branch master sui-move-analyzer
- 添加到PATH环境变量中
export PATH=$HOME/.cargo/bin:$PATH
- 重启
VSCode
安装VSCode插件
在VSCode插件市场搜索并安装:sui-move-analyzer
。
效果展示
RustRover
Jetbrains全家桶产品使用方式类似,当前RustRover对个人非商业用途免费,推荐使用RR。
下载链接:https://www.jetbrains.com/rust/download
安装Sui CLI
如果环境中没有配置Sui CLI
,请根据提示进行下载。
插件安装
路径:Setting ➜ Plugins ➜ Sui Move Language
效果展示
可见已具备语法高亮、函数补全等能力。