从0到1开发一款基于Vite公有链的DApp-开发指南
序
本文将介绍Vite的DApp开发知识,已假定读者有一定的技术基础,所以本文重点讲解Vite智能合约及web程序如何与Vite智能合约进行交互,以及一些要注意的点。其它相关技术可自行在网上查询。
从0到1开发DApp所需技术栈:HTML/CSS/JS、VUE框架、Linux、对智能合约有一定理解、对Vite链有一定理解。
目前Vite的开发者文档也在逐渐完善过程中,很多部分都已经比较详细了。一些比较详细且经过验证的部分,在文中我会直接引用对应的官方开发者文档链接,如有特别注意的地方我会注明。
本文目标
读完本文后可以理解从头开发搭建基于Vite公有链的一款DApp有哪些步骤,再结合本文引用的相关官方技术文档,相信读者可以开发一款自己的基于Vite公有链的DApp程序。
本文目录
- 前期准备
- 开发流程
- Vite智能合约运行环境
- 编写Vite智能合约
- 创建Vite智能合约(链上部署)
- 前端调用Vite智能合约
- DApp调试
- DApp申请上线至官方钱包APP
- 其它注意事项
- 推荐几篇重要的官方文档
前期准备
- 购买一台服务器,建议配置:2核4G。
- 服务器上安装部署Vite全节点(或超级节点)。
- 全节点配置(node_config.json),启用WS配置,如下:
"WSEnabled": true,
"WSHost": "0.0.0.0",
"WSPort": 41420,
- 全节点配置(node_config.json),记录指定合约地址的vmLog,如下:
// 表示保存所有合约的vmlog
"VmLogAll":true
或
// 表示只保存指定合约的vmlog,注意:此合约地址需在自己的智能合约部署在主网之后进行替换。
"VmLogWhiteList":["vite_d8f83ebc2e0c1312bf16b8ef172663688143bbd0f96d5b8f66"]
- 如使用阿里云或AWS等服务器的网络安全规则也需将对应的端口放行(如:WS端口41420和RPC端口48132)。
- 服务器上安装Nginx,用于部署Html程序。
- 注册1个域名,并做好域名解析(已上线的DApp中有个别的也没有使用域名,此项也许不是必须的)。
- 准备一些VITE,主网每次部署合约消耗10VITE,合约执行需要消耗配额,需要抵押一定量的VITE为合约获取配额。俱体需要抵押多少VITE获取配额取决于该合约的复杂程度及调用并发量。
开发流程
- DApp功能设计/原型设计/合约设计
- 前端Vue项目搭建,界面开发
- 智能合约编写调试
- 将智能合约部署到测试环境节点
- 前端程序与智能合约交互部分编写调试
- 在本地浏览器中以导入账户的方式与合约交互进行调试
- 下载官方测试钱包,新建或导入一个账号用于测试
- 在官方测试钱包中进行调试
- 将智能合约部署到主网节点
- 申请上线至官方钱包APP
以上为个人建议开发流程,也可按自己实际情况调整。
Vite智能合约运行环境
官方文档中共准备了两个合约的测试环境包(其实就是单机版的Vite节点+常用功能的脚本),一个是开发环境(
contractdev开头的名字),一个是测试环境(contracttest开头的名字),个人建议下载一个测试环境的包即可。在合约开发阶段可直接使用vscode soliditypp插件进行开发,插件目前已支持windows/linux/mac系统,该插件内置了一个Vite开发环境节点,在调试合约代码时会在本地自动启动该节点并且自动分配vite地址。
参考:http://t.cn/Ai0Ic9hv
合约在vs code中调试基本完成后可以选择部署一个Vite测试环境,Vite测试环境比较接近Vite主网的环境,所以相关操作步骤基本与主网相关。在开发前端程序与智能合约交互的阶段基本都需要在测试环境上完成(虽然前端也可以连接vscode插件中的开发节点,但是开发环境节点比较不稳定,有时调用合约很长时间都不响应,与此相比测试环境的节点比较稳定,也最接近真实的主网环境)。
为方便开发者在测试环境部署,Vite官方在测试环境包中准备了一系列的执行脚本。
脚本使用参考:http://t.cn/Ai0IcBWE
主网环境即Vite全节点或者超级节点,搭建也比较简单。
参考链接:
https://pre-mainnet.vite.wiki/zh/tutorial/node/install.html
待DApp在测试环境整体测试无问题后,再考虑在主网进行部署,进行最终测试及上线。
编写Vite智能合约
开发工具
- 使用VS Code开发工具,安装soliditypp插件
参考链接:https://pre-mainnet.vite.wiki/zh/tutorial/contract/debug.html
开发语言
- Vite智能合约所采用的开发语言是solidity++,即从以太坊的solidity语言升级扩展而来,其中重点是将同步改为异步。
- 关于solidity++中的数据类型、运算、调用、关键字等
如果是第一次编写智能合约,建议先从以太坊的智能合约开发学起(因为这方面的资料非常的多,同时Vite智能合约的编写还没有真正入门的文章,官方文档中也只是重点介绍了和solidity的区别),有编程基础的同学学起来会很容易,了解solidity基本语法后,再回来看Vite智能合约和以太坊智能合约的区别即可。
solidity++中常用代码
onMessage BetAndRoll(uint16 rollTargets) payable {
// msg.sender // 调用者地址
// msg.amount // 转账金额
// msg.tokenid // 转账的Tokenid
// 合约中随机数的获取,7月3日硬分叉后还会有其它的获取随机数方式,可以关注一下。
// uint64 randomNumber = random64();
// TODO 其它逻辑代码....
// emit触发事件
// emit win(betAddr, rollTargets, betAmount, rollNum, winAmount);
}
- 定义供外部数据查询的合约中状态数据方法:getter
getter getTokenList() returns(tokenId[] memory) {
return tokens;
}
event win(address indexed addr, uint256 rollTarget, uint256 betAmount, uint256 rollNum, uint256 winAmount);
参考链接:https://pre-mainnet.vite.wiki/zh/tutorial/contract/soliditypp.html
一些建议
目前上线到官方钱包的DApp官方都会要求其智能合约代码开源,目前在官方论坛中都可以找到已上线的DApp智能合约源代码,将来也可以在Vite区块浏览器中直接查看认证合约的源代码,可供开发参考。
小技巧:有一些算法或是可能是常用的代码片段,可以到github上去搜索基于以太坊的一些DApp智能合约的源码,若是不涉及Vite特性的语法,基本拿过来在Vite合约也是可以正常运行的。所以基于以太坊链的智能合约,对于开发Vite链的智能合约也会有很大的参考价值。
建议在合约编译无错误,功能代码编写完毕可正常在插件工具中调用以后,再考虑部署到测试环境。
在此阶段就要注意每次调用合约所消耗的配额是否合理,若是消耗过多就要及时对代码进行优化,甚至要考虑自己的需求是否适合在合约上实现或换一种更简便的实现方式(当然最好在功能设计之初就考虑清楚),总之节省配额消耗的有效办法只有尽量降低合约的复杂度。
创建Vite智能合约(链上部署)
开发环境&测试环境上创建智能合约
直接使用官方编写好的脚本即可,脚本中是采用RPC的方式进行调用创建。
参考官方文档:http://t.cn/Ai0IVq0K
另外,执行脚本时可能会报个错误,大概是无效命令'jq',一些版本的服务器默认无此命令,需要在服务器上安装一下jq命令。比如Ubuntu下安装jq命令:apt-get install jq
主网环境上创建智能合约
编译智能合约需执行如下命令(solppc文件在开发&测试环境包中都有,功能是一样的):
./solppc --abi --bin SimpleBet.solpp
若无编译错误会输出三段内容,分别是:
Binary:...内容略; // 此为完整智能合约的编译代码。
OffChain Binary:...内容略; // 此为智能合约中getter函数部分的编译代码。
Contract JSON ABI:...内容略; // 此为智能合约的ABI(即:该合约的接口)。
目前官方文档提供两种创建合约的方式:
1、采用RPC接口在终端进行创建(步骤比较繁琐,不建议,RPC接口文档中有详细说明)。
2、直接调用vitejs中的createContract方法进行创建,推荐。
在主网创建智能合约需做提前准备:
1、向创建合约的账户转账至少10VITE(创建智能合约消耗10个VITE)
2、为该账户抵押一定的配额,建议1000VITE左右(如果使用pow计算获取配额的话需要提前确认节点配置中PublicModules
部分中是否配置了pow模块)
3、创建合约times参数可省略,或者填写10-100之间的数字。
关键代码如下:
let Vite_TokenId = "tti_5649544520544f4b454e6e40";
// 此处填写编译智能合约后产生的"Contract JSON ABI"完整内容,是一个数组(不用引号)
let newabi = abicode;
// 此处填写编译智能合约后产生的"Binary"完整内容
let newhexcode = binary;
// 用导入的账户创建合约
myAccount.createContract({
abi:newabi,
hexCode: newhexcode,
confirmTimes: 2, // 确认数 0 ~ 75
// times: 0, // 翻倍数 Default 0
params: [],
tokenId: Vite_TokenId, // Default Vite_TokenId
amount: '0', // Default '0'
fee: '10000000000000000000', // Default '10000000000000000000'
}).then((accountBlock) => {
// 创建的合约地址
let contractAddress = accountBlock.toAddress;
console.log("contractAddress is " + contractAddress) ;
}).catch(err => {
console.warn(err);
});
前端与Vite智能合约交互
一个普通的DApp与合约的交互过程为:由前端发起调用合约方法->合约执行逻辑根据结果触发不同的事件->前端获取事件结果反馈给用户。
调用合约方法
此种方式主要还是在前端与智能合约联调时使用,比较的方便。
import WS_RPC from '@vite/vitejs-ws';
import { client, account, hdAccount, constant } from '@vite/vitejs';
let { Vite_TokenId } = constant;
let provider = new WS_RPC("ws://example.com");
let myClient = new client(provider);
// 导入一个账户,私钥方式导入
let myAccount = new account({
client: myClient,
privateKey: 'your privateKey'
});
// Or 助记词方式导入
// let myHdAccount = new hdAccount({
// client: myClient,
// mnemonic: 'your mnemonic'
// });
// let myAccount = myHdAccount.getAccount()
let abi = []; // 编译合约时完整的ABI内容
let contractAddress =""; // 合约地址
let callContractBlock = await myAccount.callContract({
accountAddress: myAccountParam.address,
toAddress: contractAddress, // 合约地址
tokenId: "tti_5649544520544f4b454e6e40", // 指定向合约发送的TokenId
amount: "0", // 向合约发送多少币
abi: abi[1], // 指定调用的方法的ABI
methodName:"BetAndRoll", // 合约的方法名称
params: [num], // 参数列表
});
适用于DApp在钱包App中的测试,直接调用钱包APP的支付功能
@vite/bridge文档参考链接:https://www.npmjs.com/package/@vite/bridge
import { abi as abiutils,utils } from '@vite/vitejs';
import Bridge from "@vite/bridge";
// 通过bridge中的wallet.currentAddress 方法获取,在页面加载时获取一次即可。
let address = "";
// 合约地址
let contractAddress = "";
let abi = []; // 编译时产生的完整abi内容
let num = 0; //合约方法参数
this.money = 0; // 向合约转账数量,不指定TokenId时 默认表示转账VITE
let bridge = new Bridge();
// 指定要调用该当的abi、参数列表、方法名称
// 转换数据
const hexData=abiutils.encodeFunctionCall(abi[1], [num], 'BetAndRoll');
const base64Data=utils._Buffer.from(hexData,'hex').toString('base64');
// 按照规范拼接uri
let uri3 = `vite:${contractAddress}/BetAndRoll?amount=${this.money}&data=`+base64Data;
// 唤出钱包APP的支付窗口
bridge["wallet.sendTxByURI"]({uri:uri3, address:address}).then(accountBlock => {
// 支付成功
console.log(accountBlock);
}).catch((err) => {
// 取消支付
console.log(err);
});
该方式提供任意场景下远程签名方案。编写此文时,官方还正在开发中。估计大概是一个使用钱包APP扫码授权的一种方案。
调用合约查询数据
即:调用合约中的getter方法
// 合约地址
let contractAddress = "vite_8a50b500773c7abe0ceeb7e6e25a97871868585159d8333cc0";
let offchaincode=""; // 此为编译合约时产生的OffChainBinary内容,字符串类型
let abi = []; // 编译合约时产生的ABI内容
let params = []; // 传入参数数据,无参数就传空数组
// 传入该方法对应的abi以及参数列表
let data = abiutils.encodeFunctionCall(abi[2],params);
let dataBase64 = utils._Buffer.from(data, 'hex').toString('base64');
let result = await myClient.request('contract_callOffChainMethod',{
'selfAddr':contractAddress,
'offChainCode':offchaincode,
'data':dataBase64
});
if (result) {
let resultBytes = Buffer.from(result, 'base64').toString('hex');
let outputs = [];
for (let i = 0; i < abi.outputs.length; i++) {
outputs.push(abi.outputs[i].type);
}
let offchainDecodeResult = abiutils.decodeParameters(outputs, resultBytes);
let resultList=[];
for (let i = 0; i < abi.outputs.length; i++) {
if (abi.outputs[i].name) {
resultList.push({'name':abi.outputs[i].name, 'value':offchainDecodeResult[i]});
} else {
resultList.push({'name':'', 'value':offchainDecodeResult[i]});
}
}
// resultList即返回的结果数据
console.log(resultList);
}
常用的链上数据查询代码
import WS_RPC from '@vite/vitejs-ws';
import { client, account, hdAccount, constant } from '@vite/vitejs';
let { Vite_TokenId } = constant;
let provider = new WS_RPC("ws://example.com");
let myClient = new client(provider);
myClient.pledge.getPledgeQuota("vite_8a50b500773c7abe0ceeb7e6e25a97871868585159d8333cc0").then((result) => {
// result是返回的配额数据
});
myClient.ledger.getAccountByAccAddr(currentAddr).then((result) => {
// result是返回的账户数据,其中包含余额等信息
});
myClient.ledger.getSnapshotChainHeight().then((result) => {
// result是返回的最新区块高度
});-
这里注意自己的节点配置是否记录了该合约的vmlog数据。
// 获取指定地址的最近N条账户块数据
let blockList = await myClient.ledger.getBlocksByAccAddr(contactAddr, offset, limit);
// 遍历账户块,获取每个块对应的vmlog
for (var i = 0; i < blockList.length; i++) {
let contractTx = blockList[i];
let contractBlockHash = contractTx.hash;
let abi = []; // 编译合约时产生的ABI内容
let vmLogs = [];
let vmLogList = await myClient.request('ledger_getVmLogList', contractBlockHash);
// 不是每个块都有vmlog数据(比如普通转账的块)
if (vmLogList) {
vmLogList.forEach(vmLog => {
let topics = vmLog.topics;
for (let j = 0; j < abi.length; j++) {
let abiItem = abi[j];
if (abiutils.encodeLogSignature(abiItem) === topics[0]) {
let dataBytes = '';
if (vmLog.data) {
dataBytes = utils._Buffer.from(vmLog.data, 'base64');
}
let log ={topic: topics[0],args: abiutils.decodeLog(abiItem.inputs, dataBytes.toString('hex'), topics.slice(1)),event: abiItem.name};
vmLogs.push(log);
break;
}
}
});
}
// vmLogs为事件日志,其中包含事件的名称及该事件通知的所有参数
console.log(vmLogs);
}
我在开发这部分功能时没注意到这篇文档,所以用上边的方法自己写了轮询来查询记录,看到这篇文档后觉得这种方式获取vmlog数据代码更简洁一些,推荐使用这种方式。文档中已有相关说明和示例。
参考链接:
https://vite.wiki/zh/api/rpc/subscribe.html
https://vite.wiki/zh/api/vitejs/client/subscribe.html
DApp调试
本地调试
- 建议直接使用PC浏览器即可,如:Google Chrome,可以调成手机端的显示模式调试兼容问题。
- 调用合约只能使用在js中导入本地账户的方式,使用vitejs的callContract方法调用(前面已给出关键代码)
- 使用测试环境节点
Vite Wallet APP联调
参考链接:https://vite.wiki/zh/tutorial/contract/testdapp.html
- 调用合约使用bridge["wallet.sendTxByURI"]的方式
- 使用测试环境节点,待合约在测试环境测试充分之后再将合约部署到主网
DApp申请上线官方Vite Wallet APP
上线流程
- 此流程官方也在不断的完善当中,之后对于DApp上线官方钱包APP的审核只会越来越严格,最新流程请及时关注官言公告或联系官方询问。
现有上线流程参考链接:http://t.cn/Ai0b2CVA
其它注意事项
合约的配额获取,抵押VITE多少合适?
- 这个主要取决于你的合约预计要支持多少TPS,目前的配额模型约抵押10000VITE,可支撑1TPS的不带备注普通转账(即每秒消耗1utps),但智能合约中方法的执行所消耗的配额一定会高于普通转账,也就是说每一次智能合约方法的调用可能约等于2-3个utps(甚至更高),这主要取决于你要调用的方法的实现逻辑的复杂程度(简单理解为执行的代码行数,在以太坊EVM中也是以类似的方法来计算gas的,只不过Vite公有链中的“gas”并不是消耗的,抵押VITE即可获得),所以在你的DApp上线之前,如果觉得你的合约每次调用消耗的配额过多,你就应该考虑优化一下你的合约代码了。
- 这里假设你的智能合约方法每次调用消耗3utps的配额,预计要支撑平均每秒1次的调用量,至少要抵押30000VITE来获取配额。
- 另外建议合约在测试环境充分测试之后再部署至主网,也避免在主网上反复部署合约,节略成本。
利用好github
- 目前翻阅Vite技术资料最好的两个途径一个是官方wiki,另一个就是github的官方代码库,遇到一些问题可能在官方wiki中找不到,可以用关键字去github中vitelabs的库中搜索一下,有些文档可能官方团队正在编写,还在分支中没有发布。还有一些比如接口,在文档中有描述,但没有实际的代码示例,也可以去github官方代码库中去搜索,很多功能代码库中都有对应的测试用例,只是文档中并没有把所有的示例代码列出来(记得顺便给官方代码库加个star)。
推荐几篇重要的官方文档
如果有时间的话建议官方文档整体都过一遍,关键信息仔细读一读,对Vite技术理念理解的越深,开发过程中遇到问题很容易想到是哪里的问题,开发起来会顺手很多。
以下列出几篇个人觉得比较关键的官方文档:
1、dapp开发指南:https://pre-mainnet.vite.wiki/zh/tutorial/contract/dapp.html
2、配额:https://pre-mainnet.vite.wiki/zh/tutorial/rule/quota.html
3、如何运用配额玩转Vite网络:http://t.cn/Ai0If7gC
4、智能合约:https://pre-mainnet.vite.wiki/zh/tutorial/contract/contract.html
5、VEP 6: Vite URI 格式:https://pre-mainnet.vite.wiki/zh/vep/vep-6.html
6、VEP 12: VITE 中随机数的实现:https://pre-mainnet.vite.wiki/zh/vep/vep-12.html
7、RPC接口文档:https://pre-mainnet.vite.wiki/zh/api/rpc/
8、Vite JS文档:https://pre-mainnet.vite.wiki/zh/api/vitejs/
最后
- Vite APP中新上线的DApp【VITE翻倍乐】(英文名:VITE To The Moon!),一个猜倍数赢奖励的小游戏,欢迎大家体验。
- 本文中的所有关键代码片段都是在这个DApp的中实际使用的。
本文作者:闫冬,N4Q.org超级节点负责人。欢迎转载。