20.轻松入门Move: 对象的展示
SUI提供了一个模板引擎,可以使用它将对象的数据转换为模板对象定义的键值对,来实现对链上对象的链下展示管理。
下面我们通过一个例子来展示如何使用模板引擎,在这个例子中我们试图创建一个Phone对象的模板对象。
生成Publisher
#![allow(unused)] fn main() { module test6::test6 { use std::string::{Self, String}; use sui::package::{Self, Publisher}; use sui::display; //需要展示的对象 public struct Phone has key{ id: UID, model: String, image_url: String, create_time: u64 } //一次性见证者 public struct TEST6 has drop {} fun init(otw: TEST6, ctx: &mut TxContext) { //生成Publisher对象 let publisher = package::claim(otw, ctx); transfer::public_transfer(publisher, tx_context::sender(ctx)); } } }
因为Display对象的创建需要Publisher权限,所以要先创建一个Publisher对象。创建Publisher的函数在源代码中定义如下:
#![allow(unused)] fn main() { module sui::package { public fun claim<OTW: drop>(otw: OTW, ctx: &mut TxContext): Publisher { assert!(types::is_one_time_witness(&otw), ENotOneTimeWitness); let tyname = type_name::get_with_original_ids<OTW>(); Publisher { id: object::new(ctx), package: tyname.get_address(), module_name: tyname.get_module(), } } } }
claim函数主要作用是消费一次性见证者、生成Pulisher对象并返回。它的参数中包含一次性见证者,那就意味着这个函数只能在init函数中调用。init函数仅在包发布的执行一次,所以可以保证一个模块只会有一个Publisher对象,但是一个包可能包含多个Publisher对象。
除了claim函数外,还可以使用claim_and_keep函数创建并转交Publisher对象给当前上下文环境的账户(即包的拥有者)。
创建Display对象
#![allow(unused)] fn main() { //创建模板 public entry fun new_display(p: &Publisher, ctx: &mut TxContext) { //定义模板的字段名 let keys = vector[ string::utf8(b"name"), string::utf8(b"link"), string::utf8(b"image_url"), string::utf8(b"model"), string::utf8(b"crete_time"), string::utf8(b"creator"), ]; //定义模板的字段值 let values = vector[ string::utf8(b"{name}"), string::utf8(b"https://www.phone.com/phone/{id}"), string::utf8(b"ipfs://{image_url}"), string::utf8(b"{model}"), string::utf8(b"{create_time}"), string::utf8(b"some studio") ]; //创建模板对象 let mut display = display::new_with_fields<Phone>( p, keys, values, ctx ); transfer::public_transfer(display, tx_context::sender(ctx)); } }
在生成Display对象之前,我们要定义展示的字段名和字段值。字段名数组和字段值数组都是字符串数组,两个数组个数必须一致否则会导致报错。字段值与其他模板语言类似,可以通过大括号“{}”来嵌入对象的字段值。我们使用display::new_with_fields函数来创建Display对象并指定它的键值对。也可以使用display::new函数创建空的Display对象。或者使用display::create_and_keep来创建Display空对象并将其转交给调用这个函数的账户。
一个包可能有多个Publisher, 其他模块的Publisher肯定没有权限创建Phone对象的模板对象,所以在创建Display对象的时候还需要验证创建Publisher的模块是否是Phone对象的模块,源码如下:
#![allow(unused)] fn main() { module sui::display { //判断传入的Publisher对象是否有权限创建类型T的Display对象 public fun is_authorized<T: key>(pub: &Publisher): bool { pub.from_package<T>() } } module sui::package { /// 判断泛型所属的包,是否与Publisher对象的package字段吻合 public fun from_package<T>(self: &Publisher): bool { type_name::get_with_original_ids<T>().get_address() == self.package } } }
修改和删除模板内容
display模块还提供了新增键/值对,修改键/值对和删除键/值对的方法,需要注意的是,修改和删除键值对之前要确保键值对已经存在于Display对象中,否则会产生报错。
在必要时候可以调用display::update_version升级Display的版本,并且它会发布一个版本升级的事件,监听这个事件的代码可以接到通知做相应处理。
使用Display对象展示Phone对象
只需要在查询Phone对象的时候,使用 { showDisplay: true }
选项就可以返回模板变量定义的内容:
const rpcObject = await toolbox.client.getObject({
id,
options: {
showBcs: true,
showContent: true,
showDisplay: true,//指定返回模板对象定义的内容
showOwner: true,
showPreviousTransaction: true,
showStorageRebate: true,
showType: true,
},
});
参考资料:
https://docs.sui.io/standards/display
了解更多Move内容:
- telegram: t.me/move_cn
- QQ群: 79489587