Develop WebApps with Vue using Polkadot.js

Element

一般的,以下三个要素会出现在任何程序上的一次操作中

  • 发/收信方:Account钱包地址
  • 通道: WebSocket长连接
  • 信息: Message可被加密

Account

1

2
依赖:

  • @polkadot/util-crypto 用于生成随机的密钥
  • @polkadot/keyring 用于管理钱包账户,/testing可以同步开发测试账户
  • @vue-polkadot/vue-settings 同步链节点的基本信息(比如转账时要用到的货币单位)
  • @vue-polkadot/vue-identicon 用于显示账户头像
  • @polkadot/util 一些工具,例如数据格式化

安装keyring

//  store.ts
import { cryptoWaitReady } from '@polkadot/util-crypto'
import testKeyring from '@polkadot/keyring/testing'
const myKeyring = async (store: any) => {
  await cryptoWaitReady()
  const keyring = testKeyring({ type: 'sr25519' })
  Vue.prototype.$keyring = keyring
}

在组件中调用keyring生成钱包账户

//  Account.vue
import { Vue } from 'vue-property-decorator'
export default class Account extends Vue {
    public newAccount: any = {
        name: '',
        mnemonicSeed: '',
        address: '',
        password: ''
    }
    private getAddressFromSeed(mnemonicSeed: string): any {
        return this.$keyring.createFromUri(
            mnemonicSeed.trim(),
            this.getMeta(),
            'sr25519'
        )
    }
    private getMeta(): any {
        return {
            name: this.newAccount?.name || '',
            whereCreated: 'Earth',
            whenCreated: Date.now()
        }
    }
}

creat接口用于创建账户,add接口用于在本地内存里添加账户。

 const json = pair.toJson(password)
 this.$keyring.addFromJson(json)

交易转账时,金额数据需要额外处理

static sendBalance(
        sender: string | any,
        recipient: string,
        amount: number,
        tokenDecimals: number
    ): Promise<any> {
        let value = numberToHex(amount * 1e3 * 10 ** (tokenDecimals || 12))
        return exec(sender, 'balances', 'transfer', [recipient, value])
    }

Link to Chain

将web客户端与链侧应用建立websocket长连接,以保持数据通信,随后将polkdot-api实例化。
执行的时机一般只需一次,发生在页面加载后。

  • connect with websocket
  • make the api global

Code

//  引入依赖
import { ApiPromise, WsProvider } from '@polkadot/api'
import { EventEmitter } from 'events'
//  定义接口类型
export interface ApiService {
    connect(apiUrl: string, types: any): Promise<ApiPromise | Error>
    disconnect(): void
    // registerCustomTypes(userTypes: string, apiUrl?: string): Promise<ApiPromise | Error>;
}
/**
 * Singleton instance for @polkadot/api.
 */
export default class Api extends EventEmitter implements ApiService {
    private static _instance: Api = new Api()
    private _api!: ApiPromise
    private _apiUrl!: string
    /**
     * getInstance
     * @returns Api Instance
     */
    public static getInstance(): Api {
        return Api._instance
    }
    private constructor() {
        super()
    }
    /**
     * connect
     * @requires apiUrl: string
     * @returns instance of polkadot-js/api instance
     */
    public async connect(
        apiUrl: string,
        types: Record<any, any>
    ): Promise<ApiPromise | Error> {
        if (!apiUrl) {
            throw new TypeError(
                `[VUE API] ERR: Unable to init api with apiUrl ${apiUrl}`
            )
        }
        try {
            const provider = new WsProvider(apiUrl)
            const apiPromise = await ApiPromise.create({ provider, types })
            this.setApi(apiPromise)
            this._emit('connect', apiPromise)
            // const isReady = apiPromise.isReady.then(api => {
            //     this.setApi(apiPromise)
            //     this._emit('connect', apiPromise)
            // })
        } catch (err) {
            this._emit('error', err)
            throw err
        }
        this.setUrl(apiUrl)
        return this._api
    }
    /**
     * disconnect
     */
    public disconnect(): void {
        if (this._api) {
            // this._api.once('disconnected', () => this._emit('disconnect', this._apiUrl));
            this._api.disconnect()
            this.setUrl('')
        }
    }
    private setApi(api: ApiPromise) {
        this._api = api
    }
    private setUrl(apiUrl: string) {
        this._apiUrl = apiUrl
    }
    get api(): ApiPromise {
        return this._api
    }
    /**
     * tryEmit
     *
     */
    public _emit(message = 'event', payload?: ApiPromise | Error): void {
        this.emit(message, payload)
    }
}

Tips:

  • websocket 连接失败的异常无法捕获,暂不考虑解决。
  • event 依赖主要处理connect事件的成功/失败监听,若果在外部已有其他的异常捕获机制则可以去掉此处。
  • @vue-polkadot/vue-api 插件和此代码功能一致,前者有些针对vue的封装。
  • @vue-polkadot/vue-api在实例化时没有传入type参数,如果有自定义type需要传入的要另做处理。

Query & Transactions

Query

import Connector from '@/api/util/connector'
static async getBalance(address: string): Promise<any> {
        let { api } = Connector.getInstance()
        const { data: balance } = await api.query.system.account(address)
        return balance.free
    }

Transactions

import Connector from '@/api/util/connector'
let { api } = Connector.getInstance()
const transfer = await api.tx[section][method](...params)
/*unsub将执行持续的监听。 返回的回调函数可以在出块或确认块后停止监听。*/
const unsub = await transfer.signAndSend(
     sender,    // account: AddressOrPair, 
     {},    //options: Partial<SignerOptions>
     ()=>{   //statusCb
         // after isFinalized
         unsub()
     }
)

Transactions with Extension

import Connector from '@/lib/substrate/Api'
import {
    web3Accounts,
    web3Enable,
    web3FromAddress
} from '@polkadot/extension-dapp'
let { api } = Connector.getInstance()
const allInjected = await web3Enable('Dipole')
const allAccounts = await web3Accounts()
// finds an injector for an address
const sender = allAccounts[0].address
const injector = await web3FromAddress(sender)
api.setSigner(injector.signer)

Transactions with password

if (!sender.isLocked) {
    await unSubFoo(payload)
} else {
    try {
         //  unlock account success
         sender.decodePkcs8(password)
         await unSubFoo(payload)
    } catch (err){
         //  unlock password failed
         console.log(err)
    }
}

参考


Mulander
527 声望17 粉丝

人生不过一场空,我不灿烂谁灿烂