query string parameter前端怎么传参_Substrate 前端开发-1: 用 Polkadot-JS API 轻松搭建前端

896ef666cd9241e99e5e7a08dabfba45.png

Substrate 前端开发系列 - 1/2

前言

看了这专栏之前几篇文章后,相信各位对用 Substrate 作开发已经有了基本认识。可以把节点跑起来,也能写出能完成个别功能的 runtime 出来,甚至跑起几个节点形成一个网络出来。但终端用户始终不会直接与这区块链网络互动。现在我们需要搭建一个前端,借着它用户才能与这网络互动。

所以接下来我们会介绍如何利用 Substrate 生态中的 Polkadot-JS API (下面简称 JS API) 来使前端与Substrate 节点交互。这项目名称虽然写着 Polkadot,但其实它可以连接到所有基于 Substrate 开发的节点。

本篇文章是前端系列的第一篇,先深入探讨以 JS API 来连接到 Substrate 节点并与之交互。内容适合任何前端框架,甚至如果你要打造一个 Node.js 的中间件来订阅 Substrate 节点事件 (events) 也可以,JS API 也允许你这么做。如果你的前端打算是用 React 打造,请留意我们的下篇,讲述如何在 Substrate Front-end Template 的基础上打造你的前端,它把 Polkadot-JS API 封装在 React 的组件内来使用。

接下来,我们假设你在本机已能跑起 Substrate (还没做这步的小伙伴可参考这里)。并且 Substrate 的 web socket 端口设在默认的 localhost:9944

连接到开发节点

首先在你的 JS 项目中添加 JS API 的库

yarn add @polkadot/api

我们建议使用 yarn 作你的项目包管理工具。

然后在开始要与 Subtrate 网络互动前创建一个 api 对象如下:

// 引入
import { ApiPromise, WsProvider } from '@polkadot/api';

// 创建 api 对象
const wsProvider = new WsProvider('ws://localhost:9944');
const api = await ApiPromise.create({ provider: wsProvider });

// 简单测试-读取常量
console.log(api.consts.balances.transactionByteFee.toNumber());

在这里,注意我们是用 ES2015 的 JS 准则来写的,所以用 import 来引用外部的库及支持 async / await 这些功能。做了以上的操作后,你可从 api 这对象取得所有需要的与 Substrate 交互的函数及常量。

读取链上数据 (Queries)

接下来,下面是取得链上数据的例子。

// 初始化 `api` 对象
const api = ...;

// 取得链上的时间戳
const now = await api.query.timestamp.now();

// 一个模拟地址
const ADDR = '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE';

// 取得用户地址中的余额
const balance = await api.query.balances.freeBalance(ADDR);
const nonce = await api.query.system.accountNonce(ADDR);

console.log(`${now}: balance of ${balance} and a nonce of ${nonce}`);

读取链上数据数据的方法是用 api.queryquery 后的名字则是当连到 Substrate 节点时动态建成的,取决于连接的 Substrate 网络加载了什么模块 (pallets),这些模块里的存取项 (storage),及其对应的读取函数 (getter function)。这里可了解更多 Substrate 存取项的读取函数。基本原则就是:

api.query.<pallet 名字>.<getter function 名字>;

这里的函数只会对链上数据做出简单的读取操作。因为这是需要和 Substrate 节点实时交互,会是一个异步操作,返回一个 Promise,然后用 await 等待结果。

订阅链上数据的变化 (Subscription)

做前端开发时,你有时不但需要在载入网页那一刻取得链上的数据,随着这些链上数据在变更,可能你也需要动态变更页面上的内容。这也是为什么我们一开始连接时,不是用简单的 http API 请求,而是 WebSocket 连接。

在以上取得用户余额的例子中,你也可以传入一个回调函数。这样取得用户余额以外,每次当这余额数值变更时也会回调过来。

// 订阅着该数值
const unsub = await api.query.balances.freeBalance(ADDR, balance => {
  console.log(`balance of ${balance}`);
});

用这方法的话,返回的将会是他的取消订阅函数。当你不再需要监听这数值时,就呼叫这函数。

JS API 也有个便捷的方法,可一次过订阅多个链上数值。例子如下:

const unsub = await api.queryMulti([
  // 一个 getter function
  api.query.timestamp.now,
  // 另一个 getter function,及所需参数
  [api.query.balances.freeBalance, ADDR],
  [api.query.system.accountNonce, ADDR],
], ([now, balance, nonce]) => { // 回调函数
  console.log(`${now}: balance of ${balance} and a nonce of ${nonce}`);
});

就是用 api.queryMulti(queries 数组, 回调函数)

查询节点常量 (Constants)

方法跟读取链上数据差不多。在取得 api 接口后,可以用 api.const.<pallet 名称>.<pallet 常量>.toNumber(); 来获取。以下是一些例子。

// babe 组件内的常量
console.log(api.consts.babe.epochDuration.toNumber());

// balances 组件内的常量
console.log(api.consts.balances.creationFee.toNumber());
console.log(api.consts.balances.transferFee.toNumber());

值得注意两点,第一,常量在 api 连接到节点时已取得,所以它们是直接返回,不需要以 Promise 方式异步返回。第二,尽管这常量是一个数字, 但返回时 JS API 帮我们封装到一个对象中,要用 toNumber() 从这对象取出在 JS 中能识别的值。这点我们会在自定义结构里进一步讲解。

提交外部交易 (Extrinsics)

这部份的操作会直接变更链上的数据,而且都需要有个用户/主体对呼叫的函数作出签署,所以我们称之为外部交易。以下例子是用户 Alice 打款 12345 个单位货币到另一帐户:

// ...

// 一个模拟地址
const recipient = '5DTestUPts3kjeXSTMyerHihn1uwMfLj8vU8sqF7qYrFabHE';

// Sign and send a transfer from Alice to Bob
const txHash = await api.tx.balances
  .transfer(recipient, 12345)
  .signAndSend(alice);

// 显示交易 hash 码
console.log(`Submitted with hash ${txHash}`);

其使用方法是:

api.tx.<pallet 名字>.<extrinsics 名字>(参数1, ...);

返回的是一个 hash 值,代表这个交易被记录到区块链上。但这并不意味交易已经顺利执行。所以接下来我们可监听事件 (events) 来确定交易已完成(或报错)。另外你可能也留意我们还没有提 alice 究竟是个怎么样的值。这部份留到 帐号管理/签署部份来谈。

用以下的方法,我们就可监听自己提交的交易。方法和上面订阅链上数据类似:

// 从 Alice 提交一个交易给另一用户
const unsub = await api.tx.balances
  .transfer(recipient, 12345)
  .signAndSend(alice, ({ eventRecords = [], status }) => {
    console.log(`Current status is ${status.type}`);

    if (status.isInBlock) {
      console.log(`Transaction included at blockHash ${status.asInBlock}`);
    } else if (status.isFinalized) {
      console.log(`Transaction finalized at blockHash ${status.asFinalized}`);
      unsub();
    }
  });

我们把回调函数放在 signAndSend 的最后参数内。这回调函数会返回以下对象包含两个属性。

  • status: 是一个 enum,可以是 InBlock。用 isInBlock 来查询 (返回 truefalse),代表交易已写在区块内,但未被最后确认。另外也可以是 Finalized。用 isFinalized 来查询。代表交易已被最后确认。
  • eventRecords: 放着 Event Record 对象的数组。每个 Event Record 有着以下属性:
  • phase: 这事件在哪个阶段触发
  • event: 对象,有着以下属性
    • section: 触发此事件的 pallet 名字。
    • method: 此事件在 pallet 中的名字。
    • data: 此事件传出的参数,是一个数组。

帐号管理/签署 (Keyring)

接下来,我们详细说明上文 alice 这用户对象是如何取得的。这当然不是一条字符串地址。若然如此,那么任何人都可以冒充另一个人发交易给其他人。

要创建用户对象,首先要加入 @polkadot/keyring 库到项目中:

yarn add @polkadot/keyring

然后在 JS 代码里:

// 引入
import { Keyring } from '@polkadot/api';

// 初始化 api
// const api = await ...;

// api 完成初始化后,再创建 keyring 对象。
const keyring = new Keyring({ type: 'sr25519' });

跟着你可以用以下几种方法创建出有公钥及私钥的用户帐号:

// 从 mnemonic 来生成,建议方法:
const PHRASE = 'entire material egg meadow latin bargain dutch coral blood melt acoustic thought';
const newPair = keyring.addFromUri(PHRASE);

// 只限开发时使用,即运行 Substrate 节点时加了 `--dev` 参数:
const alice = keyring.addFromUri('//Alice', { name: 'Alice default' });

// 用 32 位的 16 进制数字生成
const hexPair = keyring.addFromUri('0x1234567890123456789012345678901234567890123456789012345678901234');

// 最后你也可用不多于 32位的字符串生成。少于 32位值的前面会加上空格。
const strPair = keyring.addFromUri('Peter');

跟着你可以这样签署信息和核实信息:

// 引入一些帮助函数
import { stringToU8a, u8aToHex } from '@polkadot/util';

// 创建信息
const message = stringToU8a('a testing message');
// 签署信息
const signature = alice.sign(message);
// 核实信息
const isValid = alice.verify(message, signature);

当你使用 signAndSend() 提交外部交易时,內里已自动对交易信息作 sign 这操作。

读取及订阅网络内的事件 (Events)

你也可用类似链上数据查询的方法,来订阅链上发出的所有事件。方法如下:

// 创建 api
// const api = await ...;

api.query.system.events(events => {
  events.forEach(record => {
    // 遍历所有事件记录
    const { event, phase } = record;
    const types = event.typeDef;

    // 过滤掉我们不关注的事件
    const eventName = `${event.section}:${
      event.method
    }:: (phase=${phase.toString()})`;

    if (filter.includes(eventName)) return;

    // 从这里开始,对我们关注的事件作进一步的处理
    // ...
  });
});

上面是查询 System 模块内读取所有 events。回调函数中,过滤掉我们不关注的事件 (第 15 行),然后对我们所关注的事件作处理。

收听自定义类型 (Custom Types)

当 Substrate 把数据返回至 JS 时,并不会返回元数据 (meta-data)的。所以当 Substrate 网络内有自定义类型时,我们相应也要在前端把这些类型的定义作为参数输入,以至 JS API 在收到这些数据时,可重构回这些对象。另外要注意一点是 Substrate 节点是用 Rust 编写的。Rust 的数据类型和 JS 也没有一对一对应。所以也有一个库 @polkadot/types 把 Rust 的基本数据类型封装成不同的 JS 对象。

当我们要处理自定类型时,首先在项目里载入这个库,

yarn add @polkadot/types

然后,在初始化 api 对象时,可传入一个 types 的参数,说明前端会触及到的所有自定义数据类型。例子如下:

const api = await ApiPromise.create({
  ...,
  types: {
    Price: {
      dollars: 'u32',
      cents: 'u32',
      currency: 'Vec<u8>',
    }
  }
});

在以上例子里,我们定义了一个 Price 的类型。里面有三个属性 dollars, cents, currency. 分别是 u32, u32, Vec<u8>. 这些数据类型都是 Substrate Node 那边定义的。所以这方面的信息事先要 Substrate Runtime 开发那边告诉前端开发的。而 @polkadot/types 就对 u32, u8, Vec<u8> 等 Rust 类型在 JS 进行了封装,创建成一个独立对象。里面有函数可以进一步对数据进行处理。如 toJSON()toString()isEmpty(), toNumber() 等。

小结

今天这篇文章描述了如何在 JS 建立一个前端与 Substrate 网络交互。主要透过 Polkadot-JS API 来进行。你可以透过 JS API 读取及订阅链上数据及常量,提交外部交易,生成用户帐户并用它来签署交易等。

其实,这里也只是概括性地描述了 Polkadot JS 的功能。更详细的内容可在它的官方文档里看到。另外,你也可现在试一下实际用 JS API 建成的前端是怎样的。下一步你可以:

  • 尝试 Substrate Front-end Template。这个前端是用 React 和 Polkadot-JS API 打造的,连接到 Parity 运营的 Substrate 开发节点上,注意的是后台(链上)数据每若干小时就会重置。我们这系列的第二篇文章也会重点谈一下这个 Front-end Template。
  • 尝试 Polkadot-JS App。这是官方开发的 Polkadot / Substrate 的前端,功能非常强大。可对 Polkadot 及Substrate 网络作出不同类型的查询,及实时看到现在的链上状态及更新。 因为功能强大,刚开始可能要花点时间了解 App 里不同的界面,也需要对 Substrate 内部结构有些认识才行。

本篇读后有什么意见,欢迎在下方留言。对了,如果这篇文章你已读到这里,可能你会有兴趣再知道两个消息:

  • Parity 在亚洲正招聘开发推广及工程师,详情看这里,欢迎报名。
  • 如果你和小伙伴们有个主意,或已经开始在 Substrate/Polkadot 生态中打造一个产品/平台出来,也可报名我们的 Substrate Bootcamp,截止报名日期为 3月15日。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值