跟Dex学PlatON应用开发–JavaScript篇(八)

本章我们实现多账号,切换测试网和主网,以及委托LAT的功能
由于PlatON的开发测试网当前没有可以质押的节点,因此我们本章在主网实现委托功能.

增加网络管理类

之前我们连接测试网的连接地址是放在TransactionManager里面的,为了能够支持多网络切换,我们把节点的连接抽取成网络管理类NetworkManager. 在background.js文件里面添加NetworkManager的代码,部分代码如下:

    static webIns = null;
    static curNetwork = "PlatON开发测试网";

    static networkInfos = {
        PlatON开发测试网: {
            rpcUrl: "http://35.247.155.162:6789",
            chainId: "210309",
            browserUrl: "https://devnetscan.platon.network"
        },
        PlatON网络: {
            rpcUrl: "https://samurai.platon.network",
            chainId: "100",
            browserUrl: "https://scan.platon.network"
        }
    };
    static async InitNetwork()
    /**
     * 加载网络信息
     * @returns
     */
    static loadNetworkInfos() 
    /**
     * 获取所有网络信息
     * @returns
     */
    static GetNetworkNameList() 


    static GetCurNetworkName()
    /**
     * 获取区块链浏览器地址
     * @returns
     */
    static GetBrowserUrl() 
    /**
     * 切换网络
     * @param {}} networkName
     * @returns
     */
    static SwitchNetwork(networkName)
    /**
     * 加载网络信息
     * @returns
     */
    static loadCurNetwork()
    /**
     * 获取网页实例
     * @returns
     */
    static GetWebIns()
    /**
     * 
     * @returns 获取链id
     */
    static GetChainId()

这个类默认有PlatON的开发测试网和主网的信息. 接着在background.js增加NetworkManager的初始化代码.

... 省略代码
//  初始化钱包
async function InitWallet() {
    // 加载网络
    await NetworkManager.InitNetwork();
    // 加载交易记录
    TransactionManager.LoadPendingTxRecord();
}
// 调用初始化函数
InitWallet();

这样可以保证是打开插件页面之前,插件的网络已经完成加载.

增加Api的切换网络的功能

切换网络的同时也需要,切换钱包获取交易记录的地址,因此需要增加Api切换地址的函数.
打开src/api目录下的tx-record.js,代码修改如下:

import axios from "axios";
let txRecorcdService = null;
export default {
    SwitchNetwork(browserUrl) {
        txRecorcdService = axios.create({
            baseURL: `${browserUrl}/browser-server`,
            timeout: 30000,
            withCredentials: true
        });

        txRecorcdService.interceptors.response.use(
            response => {
                return response.data;
            },
            error => {
                return Promise.reject(error);
            }
        );
    },
    /**
     * 获取交易记录列表
     * @param {*} params
     * @returns
     */
    GetTransactionListByAddress(params) {
        return txRecorcdService.post("/transaction/transactionListByAddress", params);
    }
};

在每次切换网络的是否都调用SwitchNetwork函数,切换网络.

构建切换网络的页面

打开main-page, 布局页面修改如下:

... 省略代码
<el-dropdown trigger="click">
    <div class="network-component  horzontal-layout flex-center">
        <span style="background:#e91550" class="circle-dot"></span>
        <span class="network-name">{{ curNetworkName }}</span>
        <i class="el-icon-arrow-down"></i>
    </div>
    <el-dropdown-menu slot="dropdown">
        <el-dropdown-item
            @click.native="onSwitchNetwork(item)"
            v-for="(item, index) in networkList"
            :key="index"
            >{{ item }}</el-dropdown-item
        >
    </el-dropdown-menu>
</el-dropdown>
... 省略代码

界面效果如下图:

编写切换页面网络的逻辑

在main-page.vue添加一下代码

async onSwitchNetwork(item) {
    let res = await this.digging.NetworkManager.SwitchNetwork(item);
    if (res.errCode !== 0) {
        this.$message.error(res.errMsg);
        return;
    }
    this.curNetworkName = item;
    // 切换网络
    this.api.SwitchNetwork();
    // 加载表数据
    this.reloadTableData();
   },

 reloadTableData() {
   if (this.tabIndex === 0) {
       this.$refs.txTable.ReloadData();
   } else if (this.tabIndex === 1) {
       this.$refs.pendingTxTable.ReloadData();
   }
}

以上代码我们就可以实现在PlatON的测试网和主网切换的功能啦.

创建显示验证节点页面

验证节点页面我们以组件的方式构建,然后再main-page.vue页面中显示.在component目录下创建validator-table.vue文件,部分代码如下:

<template>
    <div class="validator-table">
        <el-table :data="datas" style="width: 100%" height="400">
            <el-table-column prop="ranking" label="排名" width="100" />

            <el-table-column prop="nodeName" label="节点名"  width="120"> </el-table-column>

            <el-table-column label="状态">
                <template slot-scope="scope">
                    <span>{{ getStatus(scope.row) }}</span>
                </template>
            </el-table-column>
            <el-table-column prop="totalValue" label="总质押">
                <template slot-scope="scope">
                    <span>{{ scope.row.totalValue }}LAT</span>
                </template>
            </el-table-column>

            <el-table-column prop="delegateValue" label="委托人数">
                <template slot-scope="scope">
                    <span>{{ scope.row.delegateValue }}LAT</span>
                </template>
            </el-table-column>

            <el-table-column prop="delegatedRewardRatio" label="委托奖励比例" />

            <el-table-column prop="delegateQty" label="委托者" />
            <el-table-column prop="blockQty" label="已产生区块数" />

            <el-table-column prop="expectedIncome" label="预计年收化率" />

            <el-table-column prop="deleAnnualizedRate" label="预计委托年化率" />
            <el-table-column prop="genBlocksRate" label="出块率" />
            <el-table-column prop="version" label="版本号" />
        </el-table>
        <div class="horzontal-layout flex-center validator-table-footer">
            <el-pagination
                class="table-pager"
                background
                layout="total,  prev, pager, next"
                :total="totals"
                :page-size="20"
                :current-page.sync="curPageIndex"
                @current-change="loadData"
            ></el-pagination>
        </div>
    </div>
</template>

然后再main-page.vue引入该组件. 部分代码如下:

...省略代码
import validatorTable from "@/component/validator-table.vue";
...省略代码

在主页面的增加验证节点的tab

...省略代码
           <div class="transaction-record-container" v-if="tabIndex === 2">
                    <validator-table />
                </div>
...省略代码

效果如下图:

编写获取验证节点接口

获取验证节点的信息,需要我们编写接口去浏览器获取.在api目录下新建validator.js文件,如下图:
1639491719(1)
代码如下:

import axios from "axios";

const txRecorcdService = axios.create({
    baseURL: "https://devnetscan.platon.network/browser-server",
    timeout: 30000,
    withCredentials: true
});

txRecorcdService.interceptors.response.use(
    response => {
        return response.data;
    },
    error => {
        Message.error(error.response.data.errMsg);
        return Promise.reject(error)
    }
);

export default {
    /**
     * 获取验证节点列表
     * @param {*} params
     * @returns
     */
    GetAliveStakingList(params) {
        return txRecorcdService.post("/staking/aliveStakingList", params);
    }
};

调用该接口返回如下字段:

  • ranking 排名
  • nodeName节点名
  • status 节点状态 1 候选中 2 活跃中 3 出块中 6 共识中
  • totalValue 总质押
  • delegateValue 委托人数
  • delegatedRewardRatio 委托奖励比例
  • delegateQty 委托者数量
  • blockQty 已产生区块数
  • expectedIncome 预计年收化率
  • deleAnnualizedRate 预计委托年化率
  • genBlocksRate 出块率
  • version 版本号
    在 validator-table.vue中调用该接口, 代码如下:
        async loadData() {
            let res = await this.api.validator
                .GetAliveStakingList({
                    pageNo: this.curPageIndex,
                    pageSize: 20,
                    queryStatus: "all"
                })
                .catch(e => {
                    console.log(e);
                });

            if (res.code !== 0) {
                this.$message.error(res.errMsg);
                return;
            }
            this.datas = res.data;
            this.totals = res.totalCount;
        }

测试网验证节点:


主网验证节点:

由于当前我们的测试账号没有LAT,在这里大家可以把主网的LAT转到测试账户上,进行测试.这里笔者增加通过秘钥导入主网钱包的功能.

增加账号菜单弹出框

在目录component下新建文件account-menu.vue, 部分代码如下:

<template>
    <div class="account-menu vertical-layout">
        <div class="account-list">
            <div
                class="horzontal-layout account-item flex-horzontal-center"
                v-for="(item, index) in accountList"
                :key="index"
            >
                <img src="@/assets/little_dog.jpg" />
                <div class="vertical-layout">
                    <div class="account-name">{{ item.account }}</div>
                    <div class="account-lat">
                        <span>{{ item.lat }}</span> LAT
                    </div>
                </div>
            </div>
        </div>

        <div class="account-item-2" @click="onJumpToImportAccount">
            <i class="el-icon-download"></i>
            <span class="item-name">导入账户</span>
        </div>
    </div>
</template>
<script>

获取账号列表逻辑如下:

        async loadAccountList() {
            let res = await this.digging.PrivateKeyManager.GetAccountList();

            this.accountList = res.accountList;

            this.accountMap = {};
            this.accountList.forEach(async ele => {
                ele.lat = 0;
                // 获取钱包余额
                ele.lat = await this.digging.TransactionManager.GetBalanceOf(ele.address);
                this.$forceUpdate();
            });

            this.curAccount = res.curAccount;
        },

这里使用到私钥管理类PrivateKeyManager的获取账号列表的接口, 在main-page.vue页面使用该组件的代码如下:

...省略代码
<el-popover placement="bottom" width="320" trigger="click">
    <account-menu />
    <div class="account-select-btn vertical-layout flex-center" slot="reference">
    <img src="@/assets/little_dog.jpg" />
    </div>
</el-popover>
...省略代码

效果如下图:
2W$%%TN@4){J9P(41_324A

增加导入私钥的页面

在page目录下新建import-account-page.vue页面,部分代码如下:

<template>
    <div class="import-account-page vertical-only-layout">
        <header-bar />
        <div class="go-back" @click="onGoBack">< Back</div>
        <page-title>导入账户</page-title>
        <div class="desc-text">
            导入的账户将不会与最初创建的 Digging 账户助记词相关联。
        </div>
        <div class="horzontal-layout " style="margin-bottom:6px;">
            <span style="color: black;">请输入账号名:</span>
        </div>
        <div class="horzontal-layout flex-center" style="margin-bottom: 10px;">
            <el-input class="private-key-password-display-textarea" v-model="accountName" />
        </div>

        <div class="horzontal-layout " style="margin-bottom:6px;">
            <span style="color: black;">请粘贴您的私钥:</span>
        </div>
        <div class="horzontal-layout flex-center">
            <el-input
                class="private-key-password-display-textarea"
                type="textarea"
                v-model="privateKey"
                rows="3"
                resize="none"
            />
        </div>

        <div class="horzontal-layout flex-center" style="margin-top:20px;">
            <el-button round class="import-btn" @click="onGoBack">取消</el-button>
            <span class="flex-1"></span>
            <el-button round class="import-btn " type="primary" @click="onImport">创建</el-button>
        </div>
    </div>
</template>
...省略代码
async onImport() {
    if (!this.accountName) {
        this.$message.error("请输入账号名");
        return;
    }

    if (!this.privateKey) {
        this.$message.error("请输入秘钥");
        return;
    }

    let res = await this.digging.PrivateKeyManager.StorePrivateKey(
        this.accountName,
        this.privateKey,
        false
    );

    if (res.errCode !== 0) {
        this.$message.error(res.errMsg);
        return;
    }
    this.$message.success("导入账户成功!");
}
}

这里导入私钥使用私钥管理类PrivateKeyManager的存储私钥StorePrivateKey接口,注意这里第三个参数一定为false否则情况之前的账号的. 效果如下图:
1639576849(1)

笔者导入一个主网的测试账,效果如下图:

1639576885(1)

编写切换账号的逻辑

在main-page.vue添加onSwitchAccount函数,代码如下:

async onSwitchAccount(accountName) {
    let res = await this.digging.PrivateKeyManager.SwitchAccount(accountName);
    if (res.errCode !== 0) {
        this.$message.error(res.errMsg);
        return;
    }
    this.accountName = accountName;
    this.address = res.data;
    this.makeShowAddress();
    this.getBalanceOf();
},

切换账号效果如下:

编写委托页面

在page目录下新建delegate-lat-page.vue文件, 部分代码如下:

<template>
    <div class="delegate-lat-page vertical-only-layout">
        <header-bar />
        <div class="go-back" @click="onGoBack">< Back</div>
        <page-title>委托LAT</page-title>
        <el-form ref="delegateForm" :model="delegateInfo">
            <el-form-item prop="nodeName" label="节点名">
                <el-input disabled class="send-input" v-model="delegateInfo.nodeName" />
            </el-form-item>

            <el-form-item prop="nodeId" label="节点id">
                <el-input disabled class="send-input" v-model="delegateInfo.nodeId" />
            </el-form-item>

            <el-form-item
                prop="lat"
                label="委托数量"
                :rules="[
                    {
                        required: true,
                        message: '请输入要委托的LAT数量',
                        validator: ValidateLAT
                    }
                ]"
            >
                <el-input
                    class="send-input"
                    type="number"
                    v-model="delegateInfo.lat"
                    placeholder="请输入要委托的LAT数量(不少于10LAT)"
                />
            </el-form-item>

            <el-form-item label="所需手续费">
                <el-input disabled class="send-input" v-model="delegateInfo.gasfee" />
            </el-form-item>

            <el-form-item>
                <div class="horzontal-layout">
                    <span class="flex-1"></span>
                    <el-button class="create-btn" @click="onGoBack">取消</el-button>
                    <el-button class="create-btn" type="primary" @click="onDelegate"
                        >委托</el-button
                    >
                </div>
            </el-form-item>
        </el-form>
    </div>
</template>

效果如下图:

增加委托管理类

在background.js中添加如下代码:

/**
 * 委托管理类
 */
class DelegateManager {
    /**
     * 委托LAT
     * @param {}} nodeId 
     * @param {*} lat 
     * @returns 
     */
    static async Deletgate(nodeId, lat) {
        let web3 = NetworkManager.GetWebIns();

        let curAccount = await PrivateKeyManager.GetCurrentAccount();
        let privateketRes = await PrivateKeyManager.ExportPrivateKey(
            curAccount,
            PasswordManager.GetPassPassword()
        );
        if (privateketRes.errCode !== 0) {
            return privateketRes;
        }

        let walletInfo = privateketRes.data;

        let ppos = new web3.PPOS({ provider: NetworkManager.GetCurNetworkRPCUrl() });

        ppos.updateSetting({
            privateKey: walletInfo.privateKey,
            chainId: 100
        });

        let params = {
            funcType: 1004, // 委托LAT的函数
            typ: 0,
            nodeId: ppos.hexStrBuf(nodeId),
            amount: ppos.bigNumBuf(web3.utils.toVon(lat, "lat"))
        };

        let reply = await ppos.send(params);
        return reply;
    }
}

本章我们只实现委托LAT的功能. 在这里我们需要修复sdk的bug,打开public/js目录下的web3.js文件,找,PPOS.prototype.bigNumBuf, 将代码修改如下

PPOS.prototype.bigNumBuf = function (intStr, radix, byteLen) {
  radix = radix || 10;
  BN.Buffer = Buffer; // 添加这行代码,否则报错
  var num = new BN(intStr, radix);
  byteLen = byteLen || Math.ceil(num.byteLength() / 8) * 8; // 好像宽度没用...
  return num.toTwos(byteLen).toBuffer();
};

接着找到函数 BN.prototype.toBuffer,将代码修改如下:

  BN.prototype.toBuffer = function toBuffer(endian, length) {
    assert(typeof BN.Buffer !== 'undefined');
    return this.toArrayLike(BN.Buffer, endian, length);
  };

笔者演示将10个LAT委托到节点名为:Infinity-PlatON, 节点ID为:0x9ca01d3332c2e4a7e16fa50c77a30bf7905aee1c0d7c456769b33c2939b3ee410a79a6fb204d826a5997b903964ff0fff62cf8f1c2a0b73b3bbc5f96c265ad49的节点.


最后在区块链浏览器中可以看到如下交易记录:

好啦,以上就是本章内容啦.下一章我们将完善委托LAT以及多网络对接的功能.

仓库地址: https://github.com/DQTechnology/Platon_DevGuideProject

3 Likes

辛苦!每期必转!

1 Like

:smile: :smile: :smile:感谢支持

教程非常详细,赞