调用合约
调用合约时,最常用的功能是 sui client, 可以先简单浏览源代码。这一小节会过一遍常用的功能。
我们先把上一节课的示例代码发布到测试网上,为了方便学习,这里也直接发布了一个版本,方便大家调用。testnet package ID 是 0xf88ed6fdffa373a09a1a54fbad1ac4730219142f7fa798bdcf632d5f159e4a18
.
签名并执行交易 signAndExecuteTransaction
先跳转到example_project文件夹,然后执行execute.ts
程序。
bun run execute.ts
可以看到执行结果返回一串哈希码,把该哈希码复制到区块链浏览器可以看到执行记录。
在这个最简单的execute.ts
示例程序中。
const client = new SuiClient({
url: getFullnodeUrl("testnet"),
});
是在程序内初始化一个Sui客户端,并且指定网络是在testnet.
const tx = new Transaction();
tx.moveCall({
package: PACKAGE,
module: "profile_clock",
function: "mint",
arguments: [
tx.pure(bcs.string().serialize("Example").toBytes()),
],
});
初始化一个交易,并且声明交易内容,是调用PACKAGE
的profile_clock
模块的mint
函数。
对比原始合约代码,输入参数有两个 handle: String, ctx: &mut TxContext
, 其中ctx
可以免输入。
String
变量的输入需要用bcs
编码方式编码,记住常见的输入格式即可。
const result = await client.signAndExecuteTransaction({ signer: keypair, transaction: tx });
console.log(result);
这一步骤就是关键,会使用私钥签署交易并执行。最后打印输出执行结果。
查询地址持有的Object getOwnedObjects
在调用合约的click
函数时,需要输入用户持有的Profile
Object 的地址和时间Clock
作为参数。
getOwnedObjects
可以查询某个地址持有的所有Object.
const object_list = await client.getOwnedObjects({owner: address});
查询输入的参数应该如何填写,可以查看源代码。
这里只需要查找目前地址下的Profile
Object 的信息,可以额外输入筛选信息。并且返回所有检索到符合条件的第一个Object的ID, 保存为profile_id
.
const struct_type = "0xf88ed6fdffa373a09a1a54fbad1ac4730219142f7fa798bdcf632d5f159e4a18::profile_clock::Profile";
const object_list = await client.getOwnedObjects({ owner: address, filter: { StructType: struct_type } });
const profile_id = object_list.data[0].data!.objectId;
这里的StructType
是由package_id::module::struct
格式组成的字符串。
有了这些信息之后,就可以调用click
函数。
在click
函数中,需要输入的第二个参数是Clock
, 是系统定义的ID为0x6
的共享Object。
const PACKAGE: string = '0xf88ed6fdffa373a09a1a54fbad1ac4730219142f7fa798bdcf632d5f159e4a18';
const tx = new Transaction();
tx.moveCall({
package: PACKAGE,
module: "profile_clock",
function: "click",
arguments: [
tx.object(profile_id),
tx.object("0x6"),
],
});
可编程交易块 PTB(Programmable Transaction Blocks)
Sui 的一个很棒的特性是,可以使用可编程交易块把很多不同的交易请求组合起来,甚至不同package的不同函数。这让构建应用变得更加灵活自由。
ptb示例代码中,就在可编程交易块里组装了三个交易。分别是创建Profile
Object, 使用该Profile
Object 执行click
函数一次,然后销毁该Profile
Object.
const tx = new Transaction();
const [profile] = tx.moveCall({
package: PACKAGE,
module: "profile_clock",
function: "new",
arguments: [
tx.pure(bcs.string().serialize("Example").toBytes()),
],
});
tx.moveCall({
package: PACKAGE,
module: "profile_clock",
function: "click",
arguments: [
profile,
tx.object("0x6"),
],
});
tx.moveCall({
package: PACKAGE,
module: "profile_clock",
function: "burn",
arguments: [
profile,
],
});
模拟交易 dryRunTransactionBlock
在使用钱包签署交易之前,可以预览到交易的执行结果。这是用到了 Sui 的模拟交易功能。
dryrun示例程序示范了如何使用模拟交易功能。
tx.setSender(address);
const dataSentToFullnode = await tx.build({ client: client });
const result = await client.dryRunTransactionBlock({
transactionBlock: dataSentToFullnode,
});
这段模拟交易的执行会报错。因为这段代码中执行的 points
函数,需要输入&Profile
, 但这个Profile
Object的所有权并不在当前地址。
查看函数 devInspectTransactionBlock
devInspect
函数同样可以查看函数的执行效果,有点像EVM
的view
函数,而且不需要在意Object所有权的问题。
devinspect示例程序示范了如何使用devInspectTransactionBlock
在不需要消耗gas的情况下,执行智能合约里写好的读函数。
const res = await client.devInspectTransactionBlock({
sender: normalizeSuiAddress(address),
transactionBlock: tx,
});
console.log(res?.results?.[0]?.returnValues?.[0]?.[0]);
查询Object信息 getObject
devInspect
的使用需要提前在合约里写好读函数,如果没有,其实也可以直接使用getObject
在链上查询Object的数据。
getobject示例程序中给出了直接根据Object id查询属性的方法。
作业
重新发布合约,然后使用本小节提及的client函数写调用函数,读取数据的TypeScript脚本。