1. 程式人生 > >以太坊教程-web3 + express開發以太坊錢包

以太坊教程-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檔案。現在專案目錄結構如下:
image.png

注意:這裡面有一個 .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

掃描下方二維碼,關注微信公眾號:H5開講啦,獲取更多學習資料。

qr.jpg