Solidity(6)——应用前端和Web.js
第一章 介绍Web.js
以太坊网络是由节点组成的,每一个节点都包含了区块链的一份拷贝。当你想要调用一份智能合约的一个方法,你需要从其中一个节点中查找并告诉它:
- 智能合约的地址
- 你想调用的方法
- 你想传入那个方法的参数
以太坊节点只能识别一种叫做 JSON-RPC 的语言。这种语言直接读起来并不好懂。当你你想调用一个合约的方法的时候,需要发送的查询语句将会是这样的:
// 哈……祝你写所有这样的函数调用的时候都一次通过
// 往右边拉…… ==>
{"jsonrpc":"2.0","method":"eth_sendTransaction","params"[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}],"id":1}
幸运的是 Web3.js 把这些令人讨厌的查询语句都隐藏起来了, 所以你只需要与方便易懂的 JavaScript 界面进行交互即可。
你不需要构建上面的查询语句,在你的代码中调用一个函数看起来将是这样:
CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto 🤔")
.send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "3000000" })
Web.js的使用,取决于你的项目工作流程和你的爱好,你可以用一些常用工具把 Web3.js 添加进来:
// 用 NPM
npm install web3
// 用 Yarn
yarn add web3
// 用 Bower
bower install web3
// ...或者其他。
甚至,你可以从 github 直接下载压缩后的 .js 文件 然后包含到你的项目文件中:
<script language="javascript" type="text/javascript" src="web3.min.js"></script>
第二章 Web3提供者
以太坊是由共享同一份数据的相同拷贝的 节点 构成的。 在 Web3.js 里设置 Web3 的 Provider(提供者) 告诉我们的代码应该和 哪个节点 交互来处理我们的读写。这就好像在传统的 Web 应用程序中为你的 API 调用设置远程 Web 服务器的网址。
你可以运行你自己的以太坊节点来作为 Provider。 不过,有一个第三方的服务,可以让你的生活变得轻松点,让你不必为了给你的用户提供DApp而维护一个以太坊节点— Infura。
- Infura是一个服务,它维护了很多以太坊节点并提供了一个缓存层来实现高速读取。你可以用他们的 API 来免费访问这个服务。 用 Infura 作为节点提供者,你可以不用自己运营节点就能很可靠地向以太坊发送、接收信息。
你可以通过这样把 Infura 作为你的 Web3 节点提供者:
var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));
不过,因为我们的 DApp 将被很多人使用,这些用户不单会从区块链读取信息,还会向区块链 写 入信息,我们需要用一个方法让用户可以用他们的私钥给事务签名。
注意: 以太坊 (以及通常意义上的 blockchains )使用一个公钥/私钥对来对给事务做数字签名。把它想成一个数字签名的异常安全的密码。这样当我修改区块链上的数据的时候,我可以用我的公钥来 证明 我就是签名的那个。但是因为没人知道我的私钥,所以没人能伪造我的事务。
加密学非常复杂,所以除非你是个专家并且的确知道自己在做什么,你最好不要在你应用的前端中管理你用户的私钥。
不过幸运的是,你并不需要,已经有可以帮你处理这件事的服务了:Metamask。
-
Metamask是 Chrome 和 Firefox 的浏览器扩展,它能让用户安全地维护他们的以太坊账户和私钥,并用他们的账户和使用 Web3.js 的网站互动。作为开发者,如果你想让用户从他们的浏览器里通过网站和你的DApp交互,你肯定会想要兼容 Metamask 的。
注意: Metamask 默认使用 Infura 的服务器做为 web3 提供者。 就像我们上面做的那样。不过它还为用户提供了选择他们自己 Web3 提供者的选项。所以使用 Metamask 的 web3 提供者,你就给了用户选择权,而自己无需操心这一块。 -
使用 Metamask 的 web3 提供者
Metamask 把它的 web3 提供者注入到浏览器的全局 JavaScript对象web3中。所以你的应用可以检查 web3 是否存在。若存在就使用 web3.currentProvider 作为它的提供者。
这里是一些 Metamask 提供的示例代码,用来检查用户是否安装了MetaMask,如果没有安装就告诉用户需要安装MetaMask来使用我们的应用。
window.addEventListener('load', function() {
// 检查web3是否已经注入到(Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// 使用 Mist/MetaMask 的提供者
web3js = new Web3(web3.currentProvider);
} else {
// 处理用户没安装的情况, 比如显示一个消息
// 告诉他们要安装 MetaMask 来使用我们的应用
}
// 现在你可以启动你的应用并自由访问 Web3.js:
startApp()
})
你可以在你所有的应用中使用这段样板代码,好检查用户是否安装以及告诉用户安装 MetaMask。
注意: 除了MetaMask,你的用户也可能在使用其他他的私钥管理应用,比如 Mist 浏览器。不过,它们都实现了相同的模式来注入 web3 变量。所以我这里描述的方法对两者是通用的。
第三章 和合约对话
现在,我们已经用 MetaMask 的 Web3 提供者初始化了 Web3.js。接下来就让它和我们的智能合约对话吧。
Web3.js 需要两个东西来和你的合约对话: 它的 地址 和它的 ABI。
-
合约地址
在你写完了你的智能合约后,你需要编译它并把它部署到以太坊。在你部署智能合约以后,它将获得一个以太坊上的永久地址。你需要在部署后复制这个地址以来和你的智能合约对话。 -
合约ABI
另一个 Web3.js 为了要和你的智能合约对话而需要的东西是 ABI。ABI 意为应用二进制接口(Application Binary Interface)。 基本上,它是以 JSON 格式表示合约的方法,告诉 Web3.js 如何以合同理解的方式格式化函数调用。
当你编译你的合约向以太坊部署时, Solidity 编译器会给你 ABI,所以除了合约地址,你还需要把这个也复制下来。 -
实例化Web.js
一旦你有了合约的地址和 ABI,你可以像这样来实例化 Web3.js。
// 实例化 myContract
var myContract = new web3js.eth.Contract(myABI, myContractAddress);
第四章 调用和合约函数
Web3.js 有两个方法来调用我们合约的函数: call
and send
.
- Call
call 用来调用view
和pure
函数。它只运行在本地节点,不会在区块链上创建事务。
复习: view 和 pure 函数是只读的并不会改变区块链的状态。它们也不会消耗任何gas。用户也不会被要求用MetaMask对事务签名。
使用 Web3.js,你可以如下 call 一个名为myMethod
的方法并传入一个 123 作为参数:
myContract.methods.myMethod(123).call()
- Send
send 将创建一个事务并改变区块链上的数据。你需要用 send 来调用任何非view
或者pure
的函数。
注意: send 一个事务将要求用户支付gas,并会要求弹出对话框请求用户使用 Metamask 对事务签名。在我们使用 Metamask 作为我们的 web3 提供者的时候,所有这一切都会在我们调用 send() 的时候自动发生。而我们自己无需在代码中操心这一切。
使用 Web3.js, 你可以像这样 send 一个事务调用myMethod
并传入 123 作为参数:
myContract.methods.myMethod(123).send()
第五章 MetaMask和账户
MetaMask 允许用户在扩展中管理多个账户。
我们可以通过这样来获取 web3 变量中激活的当前账户:
var userAccount = web3.eth.accounts[0]
因为用户可以随时在 MetaMask 中切换账户,我们的应用需要监控这个变量,一旦改变就要相应更新界面。
我们可以通过 setInterval
方法来做:
var accountInterval = setInterval(function() {
// 检查账户是否切换
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// 调用一些方法来更新界面
updateInterface();
}
}, 100);
这段代码做的是,每100毫秒检查一次 userAccount
是否还等于 web3.eth.accounts[0]
(比如:用户是否还激活了那个账户)。若不等,则将 当前激活用户赋值给 userAccount
,然后调用一个函数来更新界面。
第七章 发送事务
现在我们来看看用 send 函数来修改我们智能合约里面的数据。
相对 call 函数,send 函数有如下主要区别:
-
send 一个事务需要一个 from 地址来表明谁在调用这个函数(也就是你 Solidity 代码里的 msg.sender )。 我们需要这是我们 DApp 的用户,这样一来 MetaMask 才会弹出提示让他们对事务签名。
-
send 一个事务将花费 gas。
-
在用户 send 一个事务到该事务对区块链产生实际影响之间有一个不可忽略的延迟。这是因为我们必须等待事务被包含进一个区块里,以太坊上一个区块的时间平均下来是15秒左右。如果当前在以太坊上有大量挂起事务或者用户发送了过低的 gas 价格,我们的事务可能需要等待数个区块才能被包含进去,往往可能花费数分钟。
所以在我们的代码中我们需要编写逻辑来处理这部分异步特性。
未完,待续……