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 functionapi.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日。

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

  1. 通过URL在前端页面传参的方法

    通过URL在前端页面传参的方法 整个过程实例 在HTML页面a.html中:参数写入URL function detail(goods_id){window.location.href="h ...

  2. 前端Url 传参编码

    背景 我遇到的问题是这样的 例如: 我的用户中心的项目是这样的URL:https://my.csdn.net/我的一个活动页的项目的URL是:https://my.csdn.net/?id=1166这 ...

  3. query分页共享,可传参

    query分页,可带参数. 封装一个分页类: public class NewPage<T> { //需要获取参数有: 每页条数,当前页,总条数 private int pageSize; ...

  4. 前端上传预览文件以及下载,node后端存储文件以及返回前端文件流下载

    上传文件<div class="inputBox" style="background-color: rgb(63, 137, 212);">上传文 ...

  5. vuejs 传参 向 子组件 父组件_VUe.js 父组件向子组件中传值及方法

    父组件向子组件中传值 1.  Vue实例可以看做是大的组件,那么在其内部定义的私有组件与这个实例之间就出现了父子组件的对应关系. 2. 父子组件在默认的情况下,子组件是无妨访问到父组件中的数据的,所以 ...

  6. vue路由传参的三种方式区别(params,query)

    最近在做一个项目涉及到列表到详情页的参数的传递,网上搜索一下路由传参,结合自己的写法找到一种适合自己的,不过也对三种写法都有了了解,在此记录一下 1 <ul class="table_ ...

  7. Vue路由传参详解(params 与 query)

    Vue路由传参详解(params 与 query) 前言: 路由传参分为 params 传参与 query 传参 params 传参类似于网络请求中的 post 请求,params 传过去的参数不会显 ...

  8. query和params传参区别

    query和params传参区别 传参可以使用params和query两种方式 params传参只能用name来引入路由 query传递显示参数(url那里),params传递不显示参数,参数在请求体 ...

  9. python前端图表框架_python-django与前端echarts图表传参

    一.利用django往前端的web图表传参 因为echarts其核心是利用js的底层技术,所以python-django往前端的echarts传递参数又可以转化成python-django像前端的js ...

最新文章

  1. 这应该是你见过的最全前端下载总结
  2. JavaScript 同源策略
  3. 【数据结构与算法】之深入解析“二叉搜索树中的插入操作”的求解思路与算法示例
  4. 爸爸的素质决定孩子飞多高,爸爸们请反复看!!!
  5. 用Java写数据到POST请求
  6. (26)Verilog HDL循环语句:repeat
  7. 数据结构题及c语言版答案周桂红版,数据结构习题与答案.pdf
  8. MS SQL Server:分区表、分区索引详解
  9. 用jframe给MySQL输入数据_如何从JTextField输入Info到sql数据库?
  10. Nginx安装出现错误解决记录
  11. vmware 14 pro许可证
  12. 经度,纬度的正则表达式
  13. libcurl - curl_easy_getinfo - 从 curl 句柄中提取信息 - 可用信息
  14. hadoop+Spark+hbase集群动态增加节点
  15. c语言结构体平面向量加法公式,高中平面向量学不好怎么办?这些公式帮你秒杀向量题目...
  16. 图数据库入门教程-深入学习Gremlin(1):图基本概念与操作
  17. 关于直播的iOS开发
  18. 移动IM开源框架对比
  19. SaaSBase:什么是企域数科?
  20. 机构数据总打架,微博终于把手机市场的事儿说清楚了

热门文章

  1. 社群经济:如何利用社群做营销?
  2. jQuery Mobile开发的新闻阅读器,适应iphone和android手机
  3. 第8章防范式编程上(代码大全3)
  4. Windows Phone本地数据库(SQLCE):13、更新数据(翻译)
  5. 【转】XP下OpenProcess( PROCESS_ALL_ACCESS...失败
  6. 递归算法在生成树型结构中,几乎完全属于无稽的算法
  7. android高仿微信视频编辑页-视频多张图片提取
  8. 魔改部署自己专属的合成大西瓜(三:上线篇<踩坑篇>)
  9. blender国内下载
  10. Gstreamer调用pulseaudio播放流程(十三)