1. 阅读本文需要了解区块链基本原理及术语,掌握solidity的基本用法。
2. 文本会介绍合约如何应用在前端及web3.js的常用api
你肯定很好奇什么是web3.js?第一反应听起来就是个js库(好吧,确实是这样...),我的任务就是告诉你为什么Dapp需要用到这个库,以及这个库是怎么和合约联合工作的。
以太坊网络是由节点组成,每一个节点都包含了一份区块链的拷贝。当你想要调用一份智能合约的某个方法时,你需要去从其中一个节点中查找并告诉这个节点:
1.想要调用的这个智能合约的地址
2.你想要调用的方法以及传入的参数
以太坊节点只能识别一种叫做JSON-RPC的语言,不要紧张,我们不需要了解这门语言怎么写。web3.js是以太坊提供的一个js库,它封装了以太坊的JSON RPC API,提供了一系列和区块链交互的js对象与函数,包括了查看网络状态、查看本地账户、查看交易、发送交易、编译、部署智能合约等。
下面我们开始正文部分。建议配合:https://cryptozombies.io/zh/lesson/6/chapter/1 食用更加。(本文实际也是在读了这篇教程后的一个总结)
在web3.js里设置web3的provider,告诉代码该和哪个节点交互来处理我们的读写。你可以运行自己的以太坊节点来做privider,但显然,可以更简单一点
1.1 Infura
一个服务,它维护了很多以太节点并提供了一个缓存层实现告诉读取。用infura作为节点提供者,你可以不用自己运营节点就能向以太坊发送、接受信息。引用方法如下:
var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
1.2 Metamask
一个chrome和firefox的插件。metamask默认使用infura的服务器作为web3提供者,在此基础上,能让用户安全的维护他们的以太坊账户和私钥。
使用:该插件把它的web3提供者注入到浏览器的全局js对象web3中,所以可以通过检查web3是否存在,存在就使用当前provider作为提供者。
示例代码:
window.addEventListener('load', function() {
// 检查web3是否已经注入到(Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// 使用 Mist/MetaMask 的提供者
web3js = new Web3(web3.currentProvider);
} else {
// 处理用户没安装的情况, 比如显示一个消息
// 告诉他们要安装 MetaMask 来使用我们的应用
}
// 现在你可以启动你的应用并自由访问 Web3.js:
startApp()
})
1.3 获取metamask用户中的账户
metamask允许用户在扩展中管理多个账户,用户可以随时切换账户,一旦切换账户,就需要相应更新界面。示例:
//通过这句可以获取当前web3的账户
var userAccount = web3.eth.accounts[0]
//如果切换账户,可以用setInterval去刷新
var accountInterval = setInterval(function() {
// 检查账户是否切换
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// 调用一些方法来更新界面
updateInterface();
}
}, 100);
//显然教程里给的这个方法会消耗一个计时器资源,其实可以监听切换账号的事件,如果事件发生,就刷新一次页面。
web3.js需要两个参数调用智能合约:address、ABI
2.1 两个参数
address:即你部署智能合约在以太坊后,获得的一个在以太坊上的永久地址。(合约部署不在本文介绍范围)
ABI:一个二进制接口。 以json格式表示合约的方法。当你在以太坊部署合约时,编译器也会给你ABI
2.2 连接合约
// 实例化目标合约
//将myABI,myContractAddress替换成你想要连接的合约abi和地址即可
var myContract = new web3js.eth.Contract(myABI, myContractAddress);
2.3 合约函数调用
2.3.1 call()
call用来调用view和pure。只运行在本地节点,不耗gas。调用示例:
//myContract是前面定义的目标合约
//yourWantMethod 是你想要调用的方法的名称,123为传入该方法的参数
myContract.methods.yourWantMethod(123).call()
2.3.2 send()
send将创建一个事务并改变区块链上的数据,会消耗gas,并会要求弹出对话框请求用户使用metamask对事务签名。所以当调用的函数非pure或view时,需要用send,但尽量避免,因为要花钱啊!调用用例:
myContract.methods.yourWantMethod(123).send()
相比call函数,send一个事务需要一个from来表明谁在调用这个函数(对呀solidity的msg.sender),这样metamask才会弹出提示让他们对事务签名。因为send是要写到区块链中的,所以在用户send到事务被包含进区块之前有一个不可忽略的延迟,所以这里需要异步处理。示例:
function createRandomZombie(name) {
// 这将需要一段时间,所以在界面中告诉用户这一点
// 事务被发送出去了
$("#txStatus").text("正在区块链上创建僵尸,这将需要一会儿...");
// 把事务发送到我们的合约:
return cryptoZombies.methods.createRandomZombie(name)
.send({ from: userAccount })
.on("receipt", function(receipt) {
$("#txStatus").text("成功生成了 " + name + "!");
// 事务被区块链接受了,重新渲染界面
getZombiesByOwner(userAccount).then(displayZombies);
})
.on("error", function(error) {
$("#txStatus").text(error);
});
}
一些关于send函数的补充 :
//receipt在合约被包含进区块后触发
//error在事务未被包含进以太坊上触发
//可以在调用send的时候指定gas和gasprice
.send({from: userAccount, gas: 300000});
// 同时send中发送的应该是以wei为单位发送,1ether等于10^18wei,转换方法如下
web3js.utils.toWei("1","ether");
//在send函数中
.send({ from: userAccount, value: web3js.utils.toWei("0.001","ether") })
直接看代码就明白了:
//假设你的目标合约中定义了一个事件监听叫做NewZombie;
cryptoZombies.events.NewZombie()
.on("data", function(event) {
let zombie = event.returnValues;
console.log("一个新僵尸诞生了!", zombie.zombieId, zombie.name, zombie.dna);
}).on('error', console.error);
3.1 indexed关键字的用法
可以看成是一个过滤字段,用于筛选仅和当前用户有关的字段。用例:
//合约里有如下transfer事件
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
//监听该事件,过滤掉只要 userAccount
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
let data = event.returnValues;
// 当前用户更新!更新界面来显示
}).on('error', console.error);
3.2 查询过去的事件 getPastEvents(fromBlock,toBlock)。block指以太坊区块编号
cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: 'latest' })
.then(function(events) {
// events 是可以用来遍历的 `event` 对象
});
以上,你知道了web3.js怎么去调合约。
参考文献:
https://share.cryptozombies.io/zh/lesson/6/chapter/1 一个非常不错的入门教程
http://web3.tryblockchain.org/Web3.js-api-refrence.html Web3.js API中文文档