使用 web3 应该是开发 DApp 必须的,不管要查询 Ethereum 区块链状态、发送交易、呼叫智能合约都可以透过 web3。使用 web3 必须连结到 Ethereum 节点,之前我写了一篇文章介绍使用 Infura 提供的节点。其实常用的 Ethereum 浏览器钱包 — MetaMask 也有提供 web3 provider,用他提供的 provider 初始化 web3,就可以连上 MetaMask 提供的节点。参考以下两篇官方文件,我实作了简单的范例,并记录几个可能碰到的问题。
MetaMask 官方文件:
另外 MetaMask/mascara 提供在不安装 MetaMask 的环境下,使用 MetaMask 提供的 web3。
使用范例
写一个简单的 JavaScript 程式,使用 MetaMask 提供的 web3 provider 来初始化 web3:
var Web3 = require('web3');
// set the provider of web3
if (typeof web3 !== 'undefined') {
console.debug(web3.currentProvider);
web3 = new Web3(web3.currentProvider);
} else {
alert("No currentProvider for web3");
}
用 browserify 打包:
browserify web3_init.js -o web3_bundle.js
在 HTML file 中执行:
<script src="js/web3_bundle.js"></script>
再写个 HTML file 测试看看。希望透过 MetaMask 提供的 web3 取得:
- web3 的 API version
- 我的 MetaMask account
<html>
<body>
<h2>Web3 API version</h2>
<p id="p1"></p>
<h2>My Account</h2>
<p id="account"></p>
<script src="js/web3_bundle.js"></script>
<script>
// Get API version
var p1 = document.getElementById("p1");
p1.innerHTML = web3.version.api;
// Get my MetaMask account
var account = document.getElementById("account");
account.innerHTML = web3.eth.accounts;
</body>
</html>
执行结果:
可能碰到的问题
1. 找不到 web3.currentProvider
- 必须使用 http server,根据 MetaMask 官方文件:
Due to browser security restrictions, we can’t communicate with dapps running on file://. Please use a local server for development.
- 必须确认启用 MetaMask extension。
2. 无法取得 web3.eth.accounts
必须用密码解锁 MetaMask,不然会回传 undefined 。
3. 没有使用 callback
MetaMask 官方文件表示所有提供的 web3 API 都是非同步,必须要传入 callback function,除了以下例外:
- eth_accounts (web3.eth.accounts)
- eth_coinbase (web3.eth.coinbase)
- eth_uninstallFilter (web3.eth.uninstallFilter)
- web3.eth.reset (uninstalls all filters)
- net_version (web3.version.network)
除了以上 API,我在使用时也有其他 API 不需要 callback。但确实碰到 API 是必需要用 callback,不然 MetaMask 会跳出 error。
4) Web3 API 版本
以上範例是使用 官方 wiki 的 API,版本是 0.2x.x。如果直接用 npm install web3
,根據我的經驗會安裝的版本為 1.0.0,API 使用的方法會有些不同,使用方式請看 web3.js Doc。
使用 EthJS 呼叫合约
EthJS 是另一个 Ethereum 的 JavaScript API,也是 MetaMask 开发者推荐的 JavaScript API。根据 EthJS 官方文件的描述:
EthJS is a highly optimised, light-weight JS utility for Ethereum based on web3.js, but lighter, async only and using BN.js.
EthJS 分为多个 module,如果要使用合约要安装这两个:
npm install ethjs-query ethjs-contract --save
同样使用 MetaMask 提供的 web3 provider 来初始化 EthJS:
var Eth = require('ethjs-query');
var EthContract = require('ethjs-contract');
if (typeof web3 !== 'undefined') {
eth = new Eth(web3.currentProvider);
contract = new EthContract(eth);
startApp();
} else {
alert("No currentProvider for web3");
}
使用 contract
初始化合约,一样需要合约的 ABI 和 address:
function startApp() {
const abi = [ { "constant": true, "inputs": [], "name": "data", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "_from", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Set", "type": "event" }, { "constant": false, "inputs": [ { "name": "x", "type": "uint256" } ], "name": "set", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" } ];
const addresss = '0x06e1c13546e04514a0cf8d842216a84745ac317a';
const SimpleStorage = contract(abi);
const simpleStorage = SimpleStorage.at(addresss);
// Listen to clicks from a <button> that trigger a function call of contract
listenForClicks(simpleStorage);
}
写一个简单的 HTML file,有一个 input 栏位可以输入任意数值,和一个 button。
<input id="data-value" type="text" placeholder="Enter a number">
<button class="set">Set Data!</button>
在写一个 JavaScript function 监听这个 button,按下 button 后会透过 EthJS 呼叫合约的 set(uint256)
,把合约中的状态 data
设为对应数值。要呼叫合约不需要知道 function signature,也不用自己建 transaction。就像使用 JavaScript 物件中的 function,像是: simpleStorage.set(param, {from: myAddr}, callback() {...})
。
function listenForClicks(simpleStorage) {
var button = document.querySelector('button.set');
button.addEventListener('click', function() {
var value = document.getElementById('data-value').value;
simpleStorage.set(value, { from: "0x123abc000..." }, function(error, result) {
if (error) {
console.debug(error);
return;
}
// will return txHash as result
console.debug(result);
})
})
}
用看看
-
任意输入一个数字后,按 Set Data!
-
跳出 MetaMask 的提醒视窗。MetaMask 提供介面让使用者授权交易的发送,点击 confirm 就可以发送这笔交易。
-
发送成功,取得 Transaction Hash。
等 transaction confirm 后,再去呼叫合约的 data(),就会得到更新后的值。一样可以透过 EthJS,像是这段简单的 code:
simpleStorage.data(function(error, result) {
// result[0] is a object of bn.js
console.debug(result[0].toNumber());
})
// Return value
20