前言
Web3 與 Web2 的不同之處在於 "去中心化",由分散且不同人運營的 Server 來進行交易的驗證與維護帳本。而在乙太坊上因為借力於智能合約,可以讓我們執行更多樣化的邏輯與交易,而為了讓用戶可以輕易地與這些智能合約互動,我們需要一個前端頁面,這時就是 Web3.js 發揮功能的時候。
很多人會問: Web3 是什麼?不也是跟之前一樣用網頁來瀏覽網站嗎,筆者本人當時接觸到 Web3 這個詞是從一個 js 框架叫做 Web3.js 開始,Web3 可以說是一個概念也可以說是一個架構,雖然去中心化以及區塊鏈這些概念在多年前已經有類似的實現,但使用不同的名詞,Web3 改變的底層協議或許不大,但也許是人們對於另一個階段的集體共識的體現,以自己所理解的來認知即可。
我們這邊使用 React.js 前端框架來建構 UI 並使用 web3.js (也可選擇使用 ether.js) 來與剛才建立的 RPC server 溝通,達到一個網頁控制區塊鏈的 DApp。
用戶在與 Dapp 網頁互動的時候會於瀏覽器安裝 Metamask 擴充工具,其為一個區塊鏈錢包,用來存放代幣,並且於執行網頁互動時支付交易手續費。
2024 更新
web3 v4.6.0
初始化方式改為
const { Web3 } = require("web3");
const web3 = new Web3();
2021 更新: Metamask connect to web3
web3 v1.3.4
const Web3 = require("web3");
const ethEnabled = () => {
if (window.ethereum) {
window.web3 = new Web3(window.ethereum);
window.ethereum.enable();
return true;
}
return false;
}
ethEnabled();
其他連線 web3 方式
Wallet Connect: https://github.com/WalletConnect/web-examples
Ether.js: https://docs.ethers.io/v4/api-providers.html
Web3Modal: https://github.com/Web3Modal/web3modal
Dapp 範例
安裝 React
使用 create-react-app 建構前端環境,這邊我們選擇連線到自行架設的乙太坊測試節點 Geth。
npm install -g create-react-app
npm install -g yarn
create-react-app my-dapp
cd my-dapp
yarn add web3@0.18.0
yarn start
這裡記得因為我們要用瀏覽器存取RPC server,所以geth啟動時RPC server要加上CORS--rpccorsdomain="*"
geth --ipcdisable --rpc --rpcport 8114 --datadir "./privatechain/03" --networkid 123 --rpcapi="db,eth,net,web3,personal" --nodiscover --port=30319 --rpccorsdomain="*" console
web3 之API文件
https://github.com/ethereum/wiki/wiki/JavaScript-API
把src/App.js改為如下
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
var Web3 = require('web3');
var web3 = new Web3();
class App extends Component {
constructor() {
super();
this.state = {
accounts: ''
}
}
componentWillMount() {
console.log(web3);
web3.setProvider(new web3.providers.HttpProvider('http://localhost:8104')); //指定為RPC server的位置
this.setState({ accounts: web3.eth.accounts });
console.log(web3.eth.accounts)
}
render() {
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to My Dapp</h2>
</div>
<p className="App-intro">
{this.state.accounts.map((i, idx) => (
<p>帳號{idx}: {i} </p>
))}
</p>
</div>
);
}
}
export default App;
web3版本如果大於1.0.0 要改為如下
主要是大部份web3 function 回傳值都改為要從callback第二個參數取出
web3.eth.getAccounts(function(err, result){
context.setState({accounts: result});
});
即可看到列出我們的帳號
接著新增一個資料夾contract
裡面放入contract01.js
把我們剛才的contract的地址以及內容放入(記得要先去掉at)
module.exports = {
ABI: [{constant: false,
inputs: [{
name: "receiver",
type: "address"
}, {
name: "amount",
type: "uint256"
}],
name: "sendCoin",
outputs: [{
name: "sufficient",
type: "bool"
}],
payable: false,
type: "function"
}, {
constant: true,
inputs: [{
name: "",
type: "address"
}],
name: "coinBalanceOf",
outputs: [{
name: "",
type: "uint256"
}],
payable: false,
type: "function"
}, {
inputs: [{
name: "supply",
type: "uint256"
}],
payable: false,
type: "constructor"
}, {
anonymous: false,
inputs: [{
indexed: false,
name: "sender",
type: "address"
}, {
indexed: false,
name: "receiver",
type: "address"
}, {
indexed: false,
name: "amount",
type: "uint256"
}],
name: "CoinTransfer",
type: "event"
}],
address: "0x14067c5707025c4dabba49949c0c166070b4f5c9"
}
上面的Address 可在geth console輸入以下查看
之後App.js改為如下
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import contract01 from './contract/contract01.js' //引入contract
var Web3 = require('web3');
var web3 = new Web3();
class App extends Component {
constructor() {
super();
this.state = {
accounts : ''
}
}
componentWillMount() {
console.log(web3);
window.tokenContract = web3.eth.contract(contract01.ABI).at(contract01.address);
window.web3 = web3;
web3.setProvider(new web3.providers.HttpProvider('http://localhost:8104')); //指定為RPC server的位置
this.setState({ accounts: web3.eth.accounts });
}
render() {
console.log(window.tokenContract.coinBalanceOf("0x66a2e289b35147188876c2007f9a810dd20e480d"))
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to My Dapp</h2>
</div>
<p className="App-intro">
{this.state.accounts.map((i,idx) => (
<p key={idx}>帳號{idx}: {i} ,餘額: {window.tokenContract.coinBalanceOf(i).c[0]}</p>
))}
</p>
</div>
);
}
}
export default App;
即可看到
記得把程式碼 改為剛才有餘額的地址
加入簡單轉帳功能
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import contract01 from './contract/contract01.js' //引入contract
var Web3 = require('web3');
var web3 = new Web3();
class App extends Component {
constructor() {
super();
this.state = {
accounts : ''
}
}
componentWillMount() {
window.tokenContract = web3.eth.contract(contract01.ABI).at(contract01.address); //初始化合約
window.web3 = web3;
web3.setProvider(new web3.providers.HttpProvider('http://localhost:8104')); //指定為RPC server的位置
this.setState({ accounts: web3.eth.accounts });
web3.personal.unlockAccount(web3.eth.accounts[0], "12345") //解鎖帳號,這裡記得要填入密碼,每次重啟geth都須重新解鎖
}
send() {
window.tokenContract.sendCoin.sendTransaction(web3.eth.accounts[1], this.state.coin, {from: web3.eth.accounts[0]})
}
render() {
console.log(window.tokenContract);
return (
<div className="App">
<div className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h2>Welcome to My Dapp</h2>
<button onClick={() => this.send()}>轉帳</button>
<input onChange={(e) => this.setState({coin: e.target.value})} />
</div>
<p className="App-intro">
{this.state.accounts.map((i,idx) => (
<p key={idx}>帳號{idx}: {i} ,餘額: {window.tokenContract.coinBalanceOf(i).c[0]}</p>
))}
</p>
</div>
);
}
}
export default App;
Last updated