以太坊教程-web3 + express開發以太坊錢包
在上一節中我們講到了瞭如何使用web3調來用以太坊API,學習瞭如何建立賬戶,使用私鑰簽名交易,查詢餘額,轉賬等操作。本節我們將建立一個視覺化的介面,來完成建立賬戶,轉賬、查詢餘額等功能。
目錄:
- 建立專案
- 安裝專案依賴
- 獲取web3例項物件
- 建立前端頁面
- 建立後端
- 功能實現
建立專案
開啟命令列工具,新建一個資料夾 EthWallet_web3
mkdir EthWallet_web3
進入到專案目錄中,使用NPM初始化
cd EthWallet_web3
npm init -y
使用引數 -y
可以直接生產package.json
package.json
。
安裝專案依賴
我們使用express
來作為專案後端,通過它來為前端頁面提供呼叫介面。
npm install --save express
--save
引數可以使專案依賴儲存在package.json
檔案中。
安裝body-parser
,這個庫是用來支援post請求的。
npm install --save body-parser
最後,最重要的,我們還要安裝web3
npm install --save web3
獲取web3例項物件
在專案根目錄中新建 getWeb3.js
程式碼:
var Web3 = require('web3');
var web3;
if (typeof web3 !== 'undefined') {
web3 = new Web3(web3.currentProvider);
} else {
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545"));
}
module.exports = web3;
最後通過module.exports = web3;
把web3例項物件匯出,方便其他檔案呼叫。
建立前端頁面
前端依賴
前端頁面我使用了Bootstrap框架,你可以去https://getbootstrap.com/(英文)或者 http://www.bootcss.com/(中文)下載 Bootstrap。
並且使用了jQuery(https://jquery.com/)來方便DOM操作和ajax。
前端目錄結構
新建public
目錄,把Bootstrap和jQuery(如果你使用CDN則不需要)放到public
目錄下,新建index.html
檔案。現在專案目錄結構如下:
注意:這裡面有一個 .gitignore的檔案,這是git倉庫的忽略配置檔案,用來在推送遠端倉庫的時候忽略倉庫裡的某些檔案。
如果你沒有使用git的話,可以忽略這個檔案,當它不存在好了。
接下來編輯前端頁面,程式碼如下
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
<script src="/jquery.min.js"></script>
<script src="/bootstrap/js/bootstrap.min.js"></script>
<style>
.row{
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container" style="width: 600px;">
<div class="row" style="text-align: center">
<h1> 以太坊錢包 </h1>
</div>
<div class="row">
<div class="col-md-12">
<input type="text" class="form-control" placeholder="請輸入賬戶密碼" id="password">
</div>
</div>
<div class="row">
<div class="col-md-12">
<input class="btn btn-primary btn-block" type="submit" value="提交" id="addaccount">
</div>
</div>
</div>
<div class="container" style="width: 600px;margin-top: 10px" id="account-list">
</div>
<div class="container" style="width: 600px;margin-top: 10px">
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="address_from">from</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="address_from" placeholder="轉賬方地址">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="address_to">to</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="address_to" placeholder="接收方地址">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="trans_value">金額</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="trans_value" placeholder="轉賬金額">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="trans_password">密碼</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="trans_password" placeholder="密碼">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<input class="btn btn-primary btn-block" type="submit" value="轉賬" id="trans_btn">
</div>
</div>
</div>
</body>
</html>
建立後端
在專案根目錄下新建index.js
,使用express建立一個伺服器
var express = require("express");
var app = express();
app.listen(8081,function(){
console.log('server [email protected]')
});
配置靜態檔案路徑
var path = require('path');
app.use(express.static(path.join(__dirname, 'public')));
這不操作的目的是吧 public
設定問靜態檔案路徑,這樣再瀏覽器訪問public目錄下的檔案的時候,可以不用新增 public,比如如果想訪問index.html,就可以直接在位址列輸入http://localhost:8081/index.html
,同樣的,如果你注意的話,會發現我們在index.html裡引用的bootstrap和jQuery也是如此
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
<script src="/jquery.min.js"></script>
<script src="/bootstrap/js/bootstrap.min.js"></script>
使用body-parser
中介軟體支援post請求
var bodyParser = require('body-parser');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
接下來,我們先新增一個介面,用來訪問index.html,在index.js新增如下程式碼:
app.get("/", function(req, res){
res.sendFile(__dirname + "/public/index.html");
})
__dirname
這是一個全域性變數,用來獲取當前檔案的路勁,這裡用它獲取到的是index.js
的路勁,我們通過拼接就得到了index.html
的檔案絕對路徑了。
__dirname + "/public/index.html"
接下來啟動伺服器,在命令列(注意要在當前專案目錄下)執行
node index.js
命令列顯示
然後開啟瀏覽器,輸入http://localhost:8081/
,回車
功能實現
獲取所有賬戶
接下來我們新增一個介面用來獲取所有賬戶,在index.js
中新增如下程式碼
//獲取所有使用者
app.get('/accounts',function(req,res){
web3.eth.getAccounts(function(error, result){
res.send(result)
})
})
然後前端頁面也要相應的呼叫這個介面,修改index.html
,新增一對<script></script>
標籤,在其中新增:
var accounts = {};
function gtAccounts(){
$.get('http://localhost:8081/accounts',function(accs){
console.log(accs)
for(var i = 0;i < accs.length;i++){
accounts[accs[i]] = 0;
}
for( k in accounts){
getBalance(k)
}
})
}
gtAccounts()
這裡使用get的方式請求http://localhost:8081/accounts
介面,獲取所有的賬戶,展示在頁面。
註冊新賬戶
後端介面:
//註冊使用者
app.post("/register", function(req, res){
var password = req.body.password;
console.log('password:');
console.log(password);
web3.eth.personal.newAccount(password)
.then(function(addr){
console.log('新增賬戶:',addr)
res.send({address:addr,balance:0})
});
})
前端頁面呼叫如下:
$("#addaccount").click(function(){
if($("#password").val() != ""){
$.post('http://localhost:8081/register/',
{password:$("#password").val()},
function(res){
console.log(res)
accounts[res.address] = res.balance;
showAccountList();
})
}
$("#password").val("")
})
轉賬
後端程式碼:
//傳送以太幣
app.post("/sendcoin", function(req, res){
var address_from = req.body.address_from;
var address_to = req.body.address_to;
var trans_value = req.body.trans_value;
var password = req.body.trans_password;
web3.eth.personal.unlockAccount(address_from,password,9999,function(){
console.log('unlock accounts ok')
web3.eth.sendTransaction({
from: address_from,
to: address_to,
value: web3.utils.toWei(trans_value,"ether"),
},function(err,transactionHash){
if(!err){
console.log('transactionHash:',transactionHash)
res.send({msg:"ok",hash:transactionHash});
}else{
console.log('-------------error-----------')
console.log(err)
console.log('-------------error-----------')
}
})
});
})
前端呼叫程式碼:
$("#trans_btn").click(function(){
var address_from = $("#address_from").val();
var address_to = $("#address_to").val();
var trans_value = $("#trans_value").val();
var trans_password = $("#trans_password").val();
if(address_from != '' && address_to != '' && trans_value != "" && trans_password != ""){
$.post("http://localhost:8081/sendcoin/",
{
address_from,
address_to,
trans_value,
trans_password
},function(res){
if(res.msg == 'ok'){
accounts[address_to] = trans_value;
showAccountList();
// getBalance(address_from)
// getBalance(address_to)
}
})
}
// $("#address_from").val("");
$("#address_to").val("");
$("#trans_value").val("");
$("#trans_password").val("");
})
完整程式碼
後端程式碼
var express = require("express");
var app = express();
var path = require('path');
var bodyParser = require('body-parser');
// var BigNumber = require('bignumber.js');
app.use(express.static(path.join(__dirname, 'public')));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
var web3 = require("./getWeb3");
app.get("/", function(req, res){
res.sendFile(__dirname + "/public/index.html");
})
//獲取所有使用者
app.get('/accounts',function(req,res){
web3.eth.getAccounts(function(error, result){
res.send(result)
})
})
//註冊使用者
app.post("/register", function(req, res){
var password = req.body.password;
console.log('password:');
console.log(password);
web3.eth.personal.newAccount(password)
.then(function(addr){
console.log('新增賬戶:',addr)
res.send({address:addr,balance:0})
});
})
//查詢餘額
app.get("/getBalance", function(req, res){
var address = req.query.address;
web3.eth.getBalance(address).then(function(balance){
var ether = web3.utils.fromWei(balance, 'ether');
res.send(ether);
})
})
//傳送以太幣
app.post("/sendcoin", function(req, res){
var address_from = req.body.address_from;
var address_to = req.body.address_to;
var trans_value = req.body.trans_value;
var password = req.body.trans_password;
web3.eth.personal.unlockAccount(address_from,password,9999,function(){
console.log('unlock accounts ok')
web3.eth.sendTransaction({
from: address_from,
to: address_to,
value: web3.utils.toWei(trans_value,"ether"),
},function(err,transactionHash){
if(!err){
console.log('transactionHash:',transactionHash)
res.send({msg:"ok",hash:transactionHash});
}else{
console.log('-------------error-----------')
console.log(err)
console.log('-------------error-----------')
}
})
});
})
app.listen(8081,function(){
console.log('server [email protected]')
});
前端程式碼
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<link rel="stylesheet" href="/bootstrap/css/bootstrap.min.css">
<script src="/jquery.min.js"></script>
<script src="/bootstrap/js/bootstrap.min.js"></script>
<style>
.row{
margin-top: 10px;
}
</style>
</head>
<body>
<div class="container" style="width: 600px;">
<div class="row" style="text-align: center">
<h1> 以太坊錢包 </h1>
</div>
<div class="row">
<div class="col-md-12">
<input type="text" class="form-control" placeholder="請輸入賬戶密碼" id="password">
</div>
</div>
<div class="row">
<div class="col-md-12">
<input class="btn btn-primary btn-block" type="submit" value="提交" id="addaccount">
</div>
</div>
</div>
<div class="container" style="width: 600px;margin-top: 10px" id="account-list">
</div>
<div class="container" style="width: 600px;margin-top: 10px">
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="address_from">from</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="address_from" placeholder="轉賬方地址">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="address_to">to</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="address_to" placeholder="接收方地址">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="trans_value">金額</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="trans_value" placeholder="轉賬金額">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="form-group form-group-sm">
<label class="col-sm-2 control-label" for="trans_password">密碼</label>
<div class="col-sm-10">
<input class="form-control" type="text" id="trans_password" placeholder="密碼">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<input class="btn btn-primary btn-block" type="submit" value="轉賬" id="trans_btn">
</div>
</div>
</div>
<script>
var accounts = {};
function gtAccounts(){
$.get('http://localhost:8081/accounts',function(accs){
console.log(accs)
for(var i = 0;i < accs.length;i++){
accounts[accs[i]] = 0;
}
for( k in accounts){
getBalance(k)
}
})
}
gtAccounts()
$("#addaccount").click(function(){
if($("#password").val() != ""){
$.post('http://localhost:8081/register/',
{password:$("#password").val()},
function(res){
console.log(res)
accounts[res.address] = res.balance;
showAccountList();
})
}
$("#password").val("")
})
$("#trans_btn").click(function(){
var address_from = $("#address_from").val();
var address_to = $("#address_to").val();
var trans_value = $("#trans_value").val();
var trans_password = $("#trans_password").val();
if(address_from != '' && address_to != '' && trans_value != "" && trans_password != ""){
$.post("http://localhost:8081/sendcoin/",
{
address_from,
address_to,
trans_value,
trans_password
},function(res){
if(res.msg == 'ok'){
accounts[address_to] = trans_value;
showAccountList();
// getBalance(address_from)
// getBalance(address_to)
}
})
}
// $("#address_from").val("");
$("#address_to").val("");
$("#trans_value").val("");
$("#trans_password").val("");
})
function getBalance(address){
console.log(address)
if(address){
$.get('http://localhost:8081/getBalance?address='+ address,function(bal){
accounts[address] = bal;
showAccountList();
})
}
}
function showAccountList(){
var str = "";
for(k in accounts){
str += `<div class="row">
<div class="col-md-9">
地址:${k}
</div>
<div class="col-md-3">
金額:${accounts[k]}
</div>
</div>`
}
$("#account-list").html(str)
}
</script>
</body>
</html>
完整專案案例可以從github上下載:https://github.com/cooleye/web3_wallet