赞
踩
本篇需要大家事先准备好 T y p e S c r i p t \mathit {TypeScript} TypeScript 最基础的环境(能简单运行跑出来 H e l l o W o r l d \mathit {Hello\ World} Hello World 就够了),然后一步一步带着大家完成调用 S u i t e s t \mathit {Sui}\ \mathit {test} Sui test 链上的 a + b \mathit {a\ +\ b} a + b 合约(该合约在前面的篇章当中部署,感兴趣的可以自行翻阅,如果后续本篇中用到的 O b j e c t I D \mathit {ObjectID} ObjectID 失效了,那应该是测试链重置了吧,届时请按照前面的篇章自行部署相关的合约),所有用到的依赖本篇都会一一阐述(从安装到简单的用途),系统环境是 M a c O S \mathit {MacOS} MacOS, N o d e . j s \mathit {Node.js} Node.js 的替代包管理器使用的是 p n p m \mathit {pnpm} pnpm。
在
S
u
i
M
o
v
e
\mathit {Sui\ Move}
Sui Move 当中,不论是部署还是调用合约,都需要事先创建一个
a
d
d
r
e
s
s
\mathit {address}
address,它既是你的钱包,也是你身份对外的象征,而在
T
y
p
e
S
c
r
i
p
t
\mathit {TypeScript}
TypeScript 当中,想要调用链上的合约,自然也需要表明是谁在调用。
这一点可能在本篇当中体现不出其重要性,但是在今后尤其是涉及到资产方面的转赠与获取将至关重要,因为区块链一切都是匿名的,如果资产出了问题(被钓鱼被骗被盗),几乎没有找回的可能。
当然,每一次合约调用都将支付一笔费用,这要求你在
T
y
p
e
S
c
r
i
p
t
\mathit {TypeScript}
TypeScript 中表明的身份需要拥有一定的资产(由于是
t
e
s
t
\mathit {test}
test 链,可以领水,所以不存在这个问题,相关操作也可以自行翻阅前篇)。
本篇选择通过助记词来表明身份,通过
S
u
i
C
L
I
\mathit {Sui\ CLI}
Sui CLI 创建
a
d
d
r
e
s
s
\mathit {address}
address 后(本篇选择的加密方式是Ed25519
),会给你私钥和助记词(
12
\text {12}
12 个单词),大家应该精心保存好。
当然, 你大可以大大咧咧地将这个助记词写进代码里,但是一旦你将这份代码公开(例如
g
i
t
p
u
s
h
\mathit {git\ push}
git push 到
g
i
t
h
u
b
\mathit {github}
github 上,没有人能够
100%
\text {100\%}
100% 保证每一次都将助记词隐去),这就存在严重的安全隐患,因为所有人都可以通过这一份助记词来盗取你的资产。
因此,我们借助dotenv
来读取环境变量。
mkdir sui_sdk_call_a_add_b
cd sui_sdk_call_a_add_b
pnpm init
# 后续的代码写在 index.ts 文件当中
touch index.ts
pnpm install -g dotenv
pnpm add -D dotenv
此时在package.json
当中的devDependencies
字段里,就会出现"dotenv": "^16.4.5"
,后面的数字代表版本号,请以你自己的版本为准。
export MNEMONIC="if you want to try it then try it it is ok"
注意: 上面双引号内的内容请自行替换成你的助记词 不会真有人直接复制粘贴吧
import dotenv from "dotenv";
dotenv.config();
const MNEMONIC = process.env.MNEMONIC!;
console.log(MNEMONIC);
process.env.MNEMONIC
就是用来读取环境变量的值,它可能不存在,此时MNEMONIC
的类型就是string | undefined
(这一点可能会对后续使用该值造成困扰,具体困扰的原因后面再解释),而在最后添加!
的目的就是人为标注保证这个值一定存在,此时MNEMONIC
的类型就是固定的string
,方便后续使用。
这里顺便提一下如何运行
t
s
\mathit {ts}
ts 代码,本篇选择用ts-node
来跳过生成
j
s
\mathit {js}
js 文件再运行这一步骤,想要使用它只需要通过pnpm install -g ts-node
安装即可。
ts-node index.ts
得到的输出就是你之前设置的环境变量的值,如果你真是直接复制粘贴的上面,那么这里得到的就是if you want to try it then try it it is ok
如果你无法运行,可能是你缺少@types/node
这个依赖,请按照安装并添加dotenv
依赖的方式,将其添加到package.json
文件的依赖项字段里。
在
S
u
i
T
y
p
e
S
c
r
i
p
t
S
D
K
\mathit {Sui\ TypeScript\ SDK}
Sui TypeScript SDK 当中,提供了Keypair
类来处理与
S
u
i
a
d
d
r
e
s
s
\mathit {Sui\ address}
Sui address 相关的加密密钥对,提供了签名并验证的逻辑,当然,想要使用它,首先你得需要有这个
S
D
K
\mathit {SDK}
SDK。
pnpm add -D @mysten/sui.js
Keypair
// 这里根据你选择的加密方式进行选择,还有两个是
// @mysten/sui.js/keypairs/secp256k1 和
// @mysten/sui.js/keypairs/secp256r1
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
const keyPair = Ed25519Keypair.deriveKeypair(MNEMONIC);
如果在从环境变量读取助记词的时候不在末尾添加!
,那么这里就会报错,因为这个函数只能接受类型为string
的参数(这就是上面提到的可能产生的困扰)。
通过这个Keypair
类,我们可以轻松获得其对应的私钥、公钥及更多信息,但在本篇当中,我们只需要这个实例化的类即可,具体作用请继续向下阅读。
接触过 S u i M o v e \mathit {Sui\ Move} Sui Move 或多或少都应该了解,它分为主网 m a i n n e t \mathit {mainnet} mainnet,开发网 d e v n e t \mathit {devnet} devnet,测试网 t e s t n e t \mathit {testnet} testnet 以及本地网络(其实绝大多数开发者都不会用到本地网),本篇想要调用的合约在测试网上,那么我们如何确定我在哪条链上呢?
依赖@mysten/sui.js
上面已经添加,所以可以直接使用,在@mysten/sui.js/client
里有一个getFullnodeUrl
可以用来获取对应的
u
r
l
\mathit {url}
url,传入的参数也很简单 —— mainnet or devnet or testnet
,localnet
这里不做展开,就像上面说的,大多数人都不会用到本地模拟的链上环境。
我们调用的合约在测试网上,所以我们可以这么做:const rpcUrl = getFullnodeUrl('testnet');
光有
u
r
l
\mathit {url}
url 还不够,我们需要通过这个
u
r
l
\mathit {url}
url 来获取对应的
c
l
i
e
n
t
\mathit {client}
client,同样的,在@mysten/sui.js/client
里有一个SuiClient
可以做到这一点,就像是这样:const client = new SuiClient({ url: rpcUrl });
至此,我们已经明确了身份信息和所在的链,那么如何进行交易,也就是合约调用?
我们需要创建交易相关的信息,也就是调用合约时的必要参数,包括
P
a
c
k
a
g
e
I
D
,
M
o
d
u
l
e
,
F
u
n
c
t
i
o
n
,
A
r
g
u
m
e
n
t
s
\mathit {PackageID},\ \mathit {Module},\ \mathit {Function},\ \mathit {Arguments}
PackageID, Module, Function, Arguments 等,这些内容组合在一起,可以称之为交易块(
T
r
a
n
s
a
c
t
i
o
n
B
l
o
c
k
\mathit {TransactionBlock}
TransactionBlock),每一笔交易(
T
r
a
n
s
a
c
t
i
o
n
\mathit {Transaction}
Transaction)都接受一组输入并产生结果,在本篇当中,我们只需要一笔交易。
在@mysten/sui.js/transactions
当中提供了TransactionBlock
来创建交易块,代码也十分简单:const txb = new TransactionBlock();
接下去,我们来预处理一些信息:
PackageID::Module::Function
的形式组成的字符串,按照前面篇章当中的部署上链的信息,我们可以编写以下代码:const PACKAGE = "0xe2496799139225a06e7251857cdf46a32c20d773030c62b9bf24095cd60aac43";
const MODULE = "my_module";
const FUN = "add";
const target = `${PACKAGE}::${MODULE}::${FUN}`;
// console.log(target);
txb.pure
,例如:txb.pure.u64(a)
,其中
a
\mathit a
a 在
T
y
p
e
s
c
r
i
p
t
\mathit {Typescript}
Typescript 文件当中是一个number
类型的值。紧接着,我们就可以通过 m o v e C a l l \mathit {moveCall} moveCall 将这些信息导入:
txb.moveCall({
target,
arguments: [txb.pure.u64(a), txb.pure.u64(b)],
});
交易信息已经处理好了,就差签字画押提供费用进行交易了,在
S
D
K
\mathit {SDK}
SDK 当中提供了两个相关的接口:executeTransactionBlock
和signAndExecuteTransactionBlock
前者将签字画押和发送执行交易这两个过程分开了,本篇的内容我们选择使用后者,也就是将签名和交易二合一的方法。
在之前得到的client
当中,存在刚刚提到的signAndExecuteTransactionBlock
函数,需要向其提供如下参数:
txb
keyPair
WaitForEffectsCert
和WaitForLocalExecution
可选,默认是WaitForLocalExecution
False
):在这里,我们选择将
s
h
o
w
O
b
j
e
c
t
C
h
a
n
g
e
s
\mathit {showObjectChanges}
showObjectChanges 设置为 True
,根据得到的
O
b
j
e
c
t
I
D
\mathit {ObjectID}
ObjectID 来查询里面存储的值是否为
a
+
b
\mathit a\ +\ \mathit b
a + b 的和,由于各种数据都在前面经过处理,这段代码就非常简洁:
const result = await client.signAndExecuteTransactionBlock({
transactionBlock: txb,
signer: keyPair,
requestType: "WaitForLocalExecution",
options: {
showObjectChanges: true,
}
});
console.log(
`executeTransactionBlock result: ${JSON.stringify(result, null, 2)}`
);
最后console.log
的内容,将会是本次交易的
O
b
j
e
c
t
C
h
a
n
g
e
s
\mathit {ObjectChanges}
ObjectChanges 的相关信息。
每次 a + b \mathit a\ +\ \mathit b a + b 的参数,都只能通过代码修改?不!我要自己决定!
怎么决定?
从终端输入!
注意: 这里并非本篇文章的重点,所以简单带过。
利用readline
当中的createInterface
来创建标准输入输出:
const readline = createInterface({
input: process.stdin,
output: process.stdout,
});
定义一个用来接受输入的空的字符串,并将得到的值传递给它:
var strNumber = "";
readline.question("Please enter two numbers, separated by spaces: ", inputNumber => {
strNumber = inputNumber;
readline.close();
});
从逻辑上来讲,我们需要等待用户完成输入再进行后续操作(交易块数据处理以及发送交易等环节),所以我们判断strNumber
是否为空,再循环sleep
:
while (strNumber == "")
await new Promise(resolve => setTimeout(resolve, 100));
由于这里用到了await
(等待一个异步函数的结果),所以这段输入的代码需要放在async
声明的异步函数当中,如果有返回值也应该是Promise<Type>
:
async function read(): Promise<string[]> {
// TODO
// ......
return strNumber.split(' ');
}
import dotenv from "dotenv"; import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519'; import { getFullnodeUrl, SuiClient } from "@mysten/sui.js/client"; import { TransactionBlock } from "@mysten/sui.js/transactions"; import { createInterface } from "readline"; dotenv.config(); const MNEMONIC = process.env.MNEMONIC!; // console.log(MNEMONIC); const keyPair = Ed25519Keypair.deriveKeypair(MNEMONIC); const client = new SuiClient({url: getFullnodeUrl("testnet")}); const txb = new TransactionBlock(); const PACKAGE = "0xe2496799139225a06e7251857cdf46a32c20d773030c62b9bf24095cd60aac43"; const MODULE = "my_module"; const FUN = "add"; const target = `${PACKAGE}::${MODULE}::${FUN}`; // console.log(target); async function read(): Promise<string[]> { const readline = createInterface({ input: process.stdin, output: process.stdout, }); var strNumber = ""; readline.question("Please enter two numbers, separated by spaces: ", inputNumber => { strNumber = inputNumber; readline.close(); }); while (strNumber == "") await new Promise(resolve => setTimeout(resolve, 100)); return strNumber.split(' '); } async function callAdd(a: number, b: number) { txb.moveCall({ target, arguments: [txb.pure.u64(a), txb.pure.u64(b)], }); const result = await client.signAndExecuteTransactionBlock({ transactionBlock: txb, signer: keyPair, requestType: "WaitForLocalExecution", options: { showObjectChanges: true, } }); console.log( `executeTransactionBlock result: ${JSON.stringify(result, null, 2)}` ); } async function main() { const nums = await read(); // const a = Number(nums[0]); // const b = Number(nums[1]); // console.log(typeof a, typeof b, a, b); callAdd(Number(nums[0]), Number(nums[1])); } main();
ts-node index.ts
Please enter two numbers, separated by spaces:
Please enter two numbers, separated by spaces: 369 963
executeTransactionBlock result: { "digest": "BiFBgM2AdpAGSkpsYcgWUFUK7osETW3NFNhdyUkWxKG", "objectChanges": [ { "type": "mutated", "sender": "0x9e4092b6a894e6b168aa1c6c009f5c1c1fcb83fb95e5aa39144e1d2be4ee0d67", "owner": { "AddressOwner": "0x9e4092b6a894e6b168aa1c6c009f5c1c1fcb83fb95e5aa39144e1d2be4ee0d67" }, "objectType": "0x2::coin::Coin<0x2::sui::SUI>", "objectId": "0x03335f68ff3616af7e000b113c56a5ad53e8e8209784ca0a5623f70997c8d948", "version": "28131890", "previousVersion": "28131889", "digest": "C6kmRbL5LB5wvikk8bEij1YgZTN4gnyLCvSSu1aRPpv3" }, { "type": "created", "sender": "0x9e4092b6a894e6b168aa1c6c009f5c1c1fcb83fb95e5aa39144e1d2be4ee0d67", "owner": { "AddressOwner": "0x9e4092b6a894e6b168aa1c6c009f5c1c1fcb83fb95e5aa39144e1d2be4ee0d67" }, "objectType": "0xe2496799139225a06e7251857cdf46a32c20d773030c62b9bf24095cd60aac43::my_module::Result", "objectId": "0xafd346ba388f626f2bbc364ed507a33b3e8073bf0e610f538803ac0c986183f7", "version": "28131890", "digest": "CByybjYC9EKoGAGWvY2T3zuY8TLsmdkgJtyLh1rUd536" } ], "confirmedLocalExecution": true }
Result ObjectID
进行查询:sui client object 0xafd346ba388f626f2bbc364ed507a33b3e8073bf0e610f538803ac0c986183f7
╭───────────────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ objectId │ 0xafd346ba388f626f2bbc364ed507a33b3e8073bf0e610f538803ac0c986183f7 │ │ version │ 28131890 │ │ digest │ CByybjYC9EKoGAGWvY2T3zuY8TLsmdkgJtyLh1rUd536 │ │ objType │ 0xe2496799139225a06e7251857cdf46a32c20d773030c62b9bf24095cd60aac43::my_module::Result │ │ owner │ ╭──────────────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ │ AddressOwner │ 0x9e4092b6a894e6b168aa1c6c009f5c1c1fcb83fb95e5aa39144e1d2be4ee0d67 │ │ │ │ ╰──────────────┴──────────────────────────────────────────────────────────────────────╯ │ │ prevTx │ BiFBgM2AdpAGSkpsYcgWUFUK7osETW3NFNhdyUkWxKG │ │ storageRebate │ 1368000 │ │ content │ ╭───────────────────┬─────────────────────────────────────────────────────────────────────────────────────────╮ │ │ │ │ dataType │ moveObject │ │ │ │ │ type │ 0xe2496799139225a06e7251857cdf46a32c20d773030c62b9bf24095cd60aac43::my_module::Result │ │ │ │ │ hasPublicTransfer │ false │ │ │ │ │ fields │ ╭─────┬───────────────────────────────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ id │ ╭────┬──────────────────────────────────────────────────────────────────────╮ │ │ │ │ │ │ │ │ │ │ id │ 0xafd346ba388f626f2bbc364ed507a33b3e8073bf0e610f538803ac0c986183f7 │ │ │ │ │ │ │ │ │ │ ╰────┴──────────────────────────────────────────────────────────────────────╯ │ │ │ │ │ │ │ │ res │ 1332 │ │ │ │ │ │ │ ╰─────┴───────────────────────────────────────────────────────────────────────────────╯ │ │ │ │ ╰───────────────────┴─────────────────────────────────────────────────────────────────────────────────────────╯ │ ╰───────────────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
不难发现,其中存储的 r e s \mathit {res} res 值为 1332 \text {1332} 1332,大成功(๑•̀ㅂ•́)و✧
有了上述内容打底,你是否对安装、添加依赖,简单调用相关的 A P I \mathit {API} API 有了一定的了解?
现在就到了实践的时刻!
t
w
i
t
t
e
r
\mathit {twitter}
twitter 以及
l
e
t
′
s
m
o
v
e
t
a
s
k
6
\mathit {let's\ move\ task}\text 6
let′s move task6,完成相关任务就可以领
S
u
i
\mathit {Sui}
Sui(主网的,可在交易所变现的那种),与本篇的差异就是需要你添加navi-sdk
,通过助记词在NAVISDKClient
上表明身份,再调用depositToNavi
向
N
a
v
i
\mathit {Navi}
Navi 转
1
S
u
i
\text 1 \mathit {Sui}
1Sui,最后根据这笔交易的信息提交并等待
S
u
i
\mathit {Sui}
Sui 到账即可,甚至都不需要自己创建并处理交易块信息。
n a v i s d k \mathit {navi\ sdk} navi sdk 相关可以点击查看文档。
注意: 任务奖励随时可能截止,尤其是
t
w
i
t
t
e
r
\mathit {twitter}
twitter 上的 (可能就在这几天吧),先到先得,抓紧做起来(๑•̀ㅂ•́)و✧
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。