以太坊Dapp项目-网页钱包开发手册

以太坊Dapp项目-网页钱包开发手册

修订日期 姓名 邮箱
2018-10-10 brucefeng [email protected]

前言

在之前的一篇文章以太坊智能合约项目-Token合约开发与部署中,我们提到了钱包了钱包的概念以及主流的几种钱包,如Mist,MyEtherWallet,MetaMask等,之前我们主要将钱包作为一个开发工具使用,用于智能合约的开发与调试工作,使用较多的是浏览器插件钱包MetaMask

在本文中,我们主要介绍MyEtherWallet以及如何实现一个简易版的MyEtherWallet网页版应用,在本文中,我们能够学习到如下内容

  • node.js开发基础
  • web3.js API使用
  • 以太币转账实现
  • Token转账实现

目前主流的钱包平台已经有太多了,而且有很多已经做得比较完善了,所以我们本文的开发工作只是为了学习以太坊开发技术,并非去设计一个新的钱包软件,重复造轮子几乎没有任何价值。

一. MyEtherWallet介绍

MyEtherWallet 是一个轻钱包,无需下载,所有操作在直接在网页上就可以完成

主要功能如下

  • Net Wallet : 新建钱包
  • Send Ether && Tokens :以太币或者Token转账
  • Contract: 部署智能合约
  • ENS:以太坊域名平台
  • Check TX Status: 查看交易状态
  • View Wallet Info: 查看钱包信息

由于操作比较简单,这里不做详细讲解,在下文中我们对其主要功能,如新建钱包,以太币或者Token转账,查看交易状态进行参照开发。

二.node.js与web.js

1.Node.js

Node.js是一个JS运行时环境,可以解析,执行JavaScript代码,在这个执行环境中,为JS提供了一些服务器级别的操作API,如文件读写,网络通信,Http服务器等,其使用事件驱动,非阻塞IO模型(异步),轻量高效,大多数与JS相关的包都放在npm上,通过命令就可以下载不同的库跟框架,无需在去各个模块的官网上面单独下载,如安装koa直接通过npm install koa即可完成。

2. Web3.js

web3.js是一个库集合,允许使用HTTP或者RPC连接与本地或者远程以太坊节点进行交互,包含以太坊生态系统的特定功能,开发者利用web3模块主要连接以太坊的RPC层,从而与区块链进行交互

  • web3-eth : 与以太坊区块链和智能合约之间的交互
  • web3-ssh: 用于进行通信的p2p和广播
  • web-bzz: 用于群协议,分散的文件存储
  • web3-utils: 主要用于Dapp开发的辅助函数

官方文档: https://web3js.readthedocs.io/en/1.0/

web3.eth.accounts

  • 创建钱包账户

web3.eth.accounts.create();

  • 生成钱包配置

web3.eth.accounts.encrypt(privateKey, password);

  • 通过私钥生成账户对象

web3.eth.accounts.privateKeyToAccount(privateKey);

  • 查询余额

web3.eth.getBalance(address [, defaultBlock] [, callback])

  • 通过私钥跟密码生成配置文件

web3.eth.accounts.encrypt(privateKey, password);

  • 通过配置文件和密码解锁账户

web3.eth.accounts.decrypt(keystoreJsonV3, password);

  • 发送签名交易

web3.eth.sendSignedTransaction(signedTransactionData [, callback])

Example

var Tx = require(‘ethereumjs-tx‘);
var privateKey = new Buffer(‘e331b6d69882b4cb4ea581d88e0b604039a3de5967688d3dcffdd2270c0fd109‘, ‘hex‘)

var rawTx = {
  nonce: ‘0x00‘,
  gasPrice: ‘0x09184e72a000‘,
  gasLimit: ‘0x2710‘,
  to: ‘0x0000000000000000000000000000000000000000‘,
  value: ‘0x00‘,
  data: ‘0x7f7465737432000000000000000000000000000000000000000000000000000000600057‘
}

var tx = new Tx(rawTx);
tx.sign(privateKey);

var serializedTx = tx.serialize();

// console.log(serializedTx.toString(‘hex‘));
// 0xf889808609184e72a00082271094000000000000000000000000000000000000000080a47f74657374320000000000000000000000000000000000000000000000000000006000571ca08a8bbf888cfa37bbf0bb965423625641fc956967b81d12e23709cead01446075a01ce999b56a8a88504be365442ea61239198e23d1fce7d00fcfc5cd3b44b7215f

web3.eth.sendSignedTransaction(‘0x‘ + serializedTx.toString(‘hex‘))
.on(‘receipt‘, console.log);

> // see eth.getTransactionReceipt() for details

(1) nonce

web3.eth.getTransactionCount(address [, defaultBlock] [, callback])

(2) gasPrice

web3.eth.getGasPrice([callback])

(3) gasLimit

  • 预估gas值

3. Koa中间件

Koa号称是基于Node.js平台的下一代web开发框架,Koa 是一个新的 web 框架,由 Express 幕后的原班人马打造, 致力于成为 web 应用和 API 开发领域中的一个更小、更富有表现力、更健壮的基石。 通过利用 async 函数,Koa 帮你丢弃回调函数,并有力地增强错误处理。 Koa 并没有捆绑任何中间件, 而是提供了一套优雅的方法,帮助您快速而愉快地编写服务端应用程序。

其使用方法可以参考https://koa.bootcss.com/

Koa的最大特色就是中间件,Koa中间件是简单的函数,调用app.user()传入,MiddlewareFunction函数带有两个参数(ctx,next),中间件作为HTTP Request和HTTP Reponse的桥梁,用来实现连接功能,如

app.use(async (ctx, next) =>{
    console.log(`Process ${ctx.request.method} ${ctx.request.url} ...`);
    await next();
});

ctx是一个请求的上下文,封装了传入的http消息,并对该消息进行响应,koa提供了一个Request/Response对象提供了Context的request/response属性与用于处理Http请求的方法。

next是被调用来执行上下游中间件的函数,必须手动调用next()运行下游中间件,否则下游中间件不会被正常执行。可以采用两种不同的方法来实现中间件

  • async function
  • common function

(1) 安装启动Koa服务

  • 安装koa
$ mkdir koaTest ; cd koaTest
$ npm init -y
$ npm install koa
  • 创建启动文件
$ vim index.js
var Koa = require("koa") //导入Koa库
var app = new Koa();     //创建Koa应用对象,带有node http服务的koa接口
app.listen(3003); //启动服务的快捷方法
  • 启动服务
$ node index.js

此时会监听3003端口,通过浏览器访问

(2) Context

context是一个请求的上下文,该对象封装了一个传入的http消息,context有request和response属性,我们可以通过涉案值两个属性来处理和相应不同的请求。

$ vim index.js
var Koa = require("koa");
var app = new Koa();
//
app.use(function(ctx,next){
    console.log(ctx.request.path);
    //通过设置ctx的response的body值可以返回数据到客户端
    ctx.response.body = "my koa app";
    console.log(ctx.response.type);
});
app.listen("3003");
console.log("koa server is listening on port 3003");
  • app.use(function):将给定的function作为中间件加载到应用中。
  • ctx: 每一个请求都会创建一段上下文,在控制业务逻辑的中间件中,上下文被寄存在ctx对象中,许多上线文属性和方法都被存在ctx.request和ctx.response中,比如访问ctx.type和ctx.length都被代理到reponse对象,ctx.path和ctx.method都被代理到request对象中。

(3) 网页模板

在实际开发中,返回给用户的网页都是通过模板文件进行返回的,可以让Koa先读取模板文件,然后将这个模板返回给用户,需要指定response的type为text/html类型

var Koa = require("koa");
var app = new Koa();
var fs = require("fs");
app.use(ctx=>{
    console.log(ctx.path);
    //必须指定type
    ctx.type = "text/html";
    ctx.body = fs.createReadStream("./views/teest.html");
    console.log(ctx.type);
});
app.listen(3003);
console.log("koa server is listening on port 3003");

(4) 中间件

Koa所有的功能都是通过中间件实现的,中间件处于HTTP Request和HTTP Response之间。

Koa的中间件之间按照编码书序在栈内以此执行,允许执行操作并向下传递请求,之后过滤并必须返回响应,响应的测试代码与步骤如下

var Koa = require("koa");
var app = new Koa();
//es6新语法:
//函数名 =>(参数) =>{}

var one = (ctx,next) =>{
    console.log("1.1");
    next();
    console.log("1.2");
};

var two = (ctx,next) =>{
    console.log("2.1");
    next();
    console.log("2.2");
};

var three = (ctx, next) =>{
    console.log("3.1");
    next();
    console.log("3.2");
};

app.use(one);
app.use(two);
app.use(three);

app.listen(3003);
console.log("服务启动完毕");

返回结果

2.1
3.1
3.2
2.2
1.2

(5) 异步中间件

由async标记的函数被称为异步函数,在异步函数中,可以通过await调用另外一个异步函数,使用await时,其所在的方法必须使用关键字async

var Koa = require("koa");
var app = new Koa();
app.use(async(ctx,next) =>{
    var start = Date.now();
    await next();
    console.log(`${ctx.url} ${Date.now - start}`);
});
app.use(async (ctx,next)=>{
    ctx.response.body = "async test"
});
app.listen(3003);
console.log("服务启动完毕");

(6) 原生路由

var Koa = require("koa");
var app = new Koa();
//es6新语法:
//函数名 =>(参数) =>{}
app.use((ctx,next)=> {
    console.log("%s %s", ctx.method, ctx.url);
    next();
});

app.use((ctx,next) =>{
    if (ctx.request.path == ‘/‘){
        ctx.response.body = ‘index page‘;
    }else {
        next();
    }
});

app.use((ctx,next) =>{
    if (ctx.request.path == ‘/error‘){
        ctx.response.body = ‘error page‘;
    }
});

app.listen(3003);
console.log("服务启动完毕");

(7) koa-router路由

由于原生路由使用比较繁琐,所以可以通过封装好的koa-router模块,使用router.routers()绑定到中间件

安装koa-router

$ npm install koa-router
var Koa = require("koa");
var app = new Koa();
//导入koa-router,注意要加上()才能生效
var router = require("koa-router")()
router.get("/hello",function(ctx,next){
    ctx.response.body = "hello,brucefeng";
});

router.get("/bye",function (ctx,next){
    ctx.response.body = "good bye brucefeng";
});

//将router路由注册到中间件
app.use(router.routes());

app.listen(3003);
console.log("服务启动完毕");

(8) 请求重定向

一般在如下情况下需要使用到重定向

  • 后台系统升级,对之前的页面不在支持,此时需要使用重定向到新的API上满足用户的访问准确性
  • 完成某个操作后自动跳转至其他页面,如注册成功,登录成功等等
var Koa = require("koa");
var app = new Koa();
//导入koa-router,注意要加上()才能生效
var router = require("koa-router")()
router.get("/hello",function(ctx,next){
    ctx.response.body = "hello,brucefeng";
});

router.get("/hi",function (ctx,next){
   ctx.response.redirect("/hello")
});

//将router路由注册到中间件
app.use(router.routes());

app.listen(3003);
console.log("服务启动完毕");

通过 ctx.response.redirect("/hello")将"/hi"请求重定向到/hello对应的页面

在node.js中访问的url中有中文时,需要通过全局encodeURIComponent(string)进行编码

(9) 获取get请求参数

客户端在请求获取服务的数据时,获取的URL中通常会携带各种参数,服务端如何获取到get请求的参数呢?

  • 格式1:http://127.0.0.1:3003/hello/brucefeng

获取方式: ctx.params.name

  • 格式2:http://127.0.0.1:3003/bye?name=brucefeng

获取方式: ctx.query.name

调用params获取参数的时候,params不是request的属性,需要通过ctx直接调用获取。

(10) 获取post请求参数

Get请求的参数附带在了url上,Post请求的参数在请求体body里面,所以要获取body的数据,需要使用到插件koa-body,通过ctx.request.body.name获取参数.

$ npm install koa-router
var Koa = require("koa");
var app = new Koa();

//导入koa-router,注意要加上()才能生效
var router = require("koa-router")()
//引入koa-body
var koaBody = require("koa-body")

router.post("/hello",function(ctx,next){
    var body = ctx.request.body;
    ctx.response.body = "hello,bruce";
    console.log(body);
    console.log(body.username);
});

//设置multipart : true,支持多个参数
app.use(koaBody({
    multipart:true
}))

//将router路由注册到中间件
app.use(router.routes());

app.listen(3003);
console.log("服务启动完毕");

//通过命令使用curl插件模拟调用一个Post请求
//curl -H "Content-Type:application/json" -X POST --data ‘{"username":"brucefeng"}‘ http://localhost:3003/hello

brucefengdeMBP:ETHWalletDemo brucefeng$ node index.js
服务启动完毕
{ username: ‘brucefeng‘ }
brucefeng

(11) 加载静态资源

加载静态资源,如图片,字体,样式表,脚本等,编码指定静态资源的路径是相对于./static的路径。

$ npm install koa-static
var Koa = require("koa");
var app = new Koa();

//导入koa-router,注意要加上()才能生效
var router = require("koa-router")();
var static = require("koa-static");
var path = require("path")

router.get("/hello",function (ctx,next){
    ctx.response.body = "<html> <a href=‘/0.png‘>看我</html>"
})
//静态资源的路径是相对于./static的路径
app.use(static(path.join(__dirname,"./static")))

//将router路由注册到中间件
app.use(router.routes());

app.listen(3003);
console.log("服务启动完毕");

启动服务,通过浏览器访问测试

(12) 模板引擎

模板引擎ejs需要配上模板渲染中间件koa-views使用,如果需要支持其他后缀的文件,需要将文件扩展名映射到引擎中。

$ npm install ejs koa-views

index.js

var Koa = require("koa");
var app = new Koa();

//导入koa-router,注意要加上()才能生效
var router = require("koa-router")();
var static = require("koa-static");
var path = require("path")
var views = require("koa-views")

router.get("/hello",async (ctx,next) =>{
    //将json里面的值替换为文件里面的变量
    var name = "brucefeng";
    await ctx.render("test.ejs",{
        name,
        "sex":"帅哥"
    })
})

router.get("/bye",async (ctx,next)=>{
    await ctx.render("home.html",{
        "name": "fengyingcong"
    })
})

app.use(views(
    //默认是views下面获取ejs后缀的文件,如果是其他类型的文件需要指定文件类型
    path.join(__dirname,"./static/views"),
    {extension:"ejs", map:{html: "ejs"}}
))

//静态资源的路径是相对于./static的路径
app.use(static(path.join(__dirname,"./static")))

//将router路由注册到中间件
app.use(router.routes());

app.listen(3003);
console.log("服务启动完毕");

static/views/test.ejs

<!DOCTYPE <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
    <script src="main.js"></script>
</head>
<body>
    <div>姓名: <%= name %> 性别: <%= sex %></div>
</body>
</html>

static/views/home.html

<!DOCTYPE <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Page Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" type="text/css" media="screen" href="main.css" />
    <script src="main.js"></script>
</head>
<body>
    <div>姓名: <%= name %> </div>
</body>
</html>

三.初始化项目环境

1.导入项目依赖

$ mkdir ETHWalletDemo ;cd ETHWalletDemo
$ npm init -y
$ npm i web3 koa koa-body koa-static koa-views koa-router ejs

2.创建路由文件

$ mkdir  router ; cd router
$ vim router.js
var router = require("koa-router")()
//定义路由newaccount
router.get("/newaccount",(ctx,next)=>{
    ctx.response.body = "创建钱包"
})

module.exports = router

3.创建入口文件

ETHWalletDemo/index.js

//导入./router/router包
var router = require("./router/router.js")
//引入Koa库
var Koa = require("koa")
//通过koa创建一个应用程序
var app = new Koa()

app.use(router.routes())
app.listen(3003)
console.log("钱包启动成功,请访问http://127.0.0.1:3003/newaccount进行测试")

4.创建MVC框架

ETHWalletDemo下创建

$ mkdir models views controllers   #创建MVC框架目录
$ mkdir utils #创建帮助文件目录
$ mkdir -p static/{css,js,html}  #创建静态文件目录

5.完善项目框架

ETHWalletDemo/index.js

//引入Koa库
var Koa = require("koa")
//通过koa创建一个应用程序
var app = new Koa()

//导入./router/router包
var router = require("./router/router.js")
var static = require("koa-static")
var path = require("path")
var views = require("koa-views")
var koaBody = require("koa-body")

//拦截获取网络请求,自定义的function需要使用next()
app.use(async (ctx,next)=>{
    console.log(`${ctx.url} ${ctx.method}...`)
    await next();
})

//注册中间件
//注册静态文件的库到中间件
app.use(static(path.join(__dirname,"static")))
//注册模板引擎的库到中间件
app.use(views(path.join(__dirname,"views"),{extension:"ejs", map:{html: "ejs"}}))
//针对于文件上传时,可以解析多个字段
app.use(koaBody({multipart:true}))
app.use(router.routes())
app.listen(3003)
console.log("钱包启动成功,请访问http://127.0.0.1:3003/...进行测试")

四.创建钱包账户

1.创建钱包账户

(1) 封装web3库调用

utils/myUtils.js

var web3 = require("../utils/myUtils").getWeb3()
module.exports = {
    getWeb3: ()=>{
        var Web3 = require("web3");
        var web3 = new Web3(Web3.givenProvider || ‘http://127.0.0.1:8545‘);
        return web3;
    }
}

(2) 创建控制器

controllers/newAccount.js

var web3 = require("../utils/myUtils").getWeb3()

module.exports = {
    //获取创建账号的页面
    newAccountHtml: async (ctx) =>{
        await ctx.render("newaccount.html")
    },
    //表单提交被触发的方法
    newAccount: (ctx) =>{
        console.log("newAccount");
        var password = ctx.request.body.password;
        //通过密码创建钱包账户
        var account = web3.eth.accounts.create(password);
        console.log(account.address);
        ctx.response.body = "钱包账户: "+account.address +" 创建成功";
    }
}

(3) 创建前端页面

views/newaccount.html

<html>
<head>
    <title>创建钱包</title>
</head>
<body>
    <div id="main">
        <h1>创建一个新的钱包</h1>
        <form method="POST" action="/newaccount">
            <input type="text" placeholder="请输入密码" name="password">
            <button type="submit">创建钱包</button>
        </form>
    </div>
</body>
</html>

(4) 配置路由

router/router.js

var router = require("koa-router")()
var newAccount = require("../controllers/newAccount")
//创建账号的页面
router.get("/newaccount",newAccount.newAccountHtml)
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount)

module.exports = router

2.下载配置文件

(1) 创建目录保存配置文件

$ mkdir static/keystore

(2) 实现配置文件的保存

controllers/newAccount.js

var web3 = require("../utils/myUtils").getWeb3()
var fs = require("fs")
var path = require("path")
module.exports = {
    //获取创建账号的页面
    newAccountHtml: async (ctx) =>{
        await ctx.render("newaccount.html")
    },
    //表单提交被触发的方法
    newAccount: async (ctx) =>{
        console.log("newAccount");
        var password = ctx.request.body.password;
        //通过密码创建钱包账户
        var account = web3.eth.accounts.create(password);
        console.log(account.address);
        //根据账号私钥跟密码生成keystore文件
        var keystore = web3.eth.accounts.encrypt(account.privateKey, password);
        //keystore文件保存到文件中,

        var keystoreString = JSON.stringify(keystore);
         //格式如下:UTC--Date--Adress
         //UTC--2018-09-26T05-07-57.260Z--937d091780693ab7f51331bb52797a9267bb9ed2
        var fileTime = new Date().toDateString()
        var fileName = ‘UTC--‘ + fileTime + ‘--‘ + account.address.slice(2) ;
        var filePath = path.join(__dirname,"../static/keystore",fileName)
        fs.writeFileSync(filePath,keystoreString)
        await ctx.render("downloadkeystore.html",{
            "downloadurl":path.join("keystore",fileName),
            "privatekey":account.privateKey
        })
    }
}

此时生成的Keystore文件将会被保存至static/keystore目录中

(3) 创建前端页面

view/downloadkeystore.html

<html>

<head>
    <title>保存KeyStore文件</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>

    <div id="main">

        <div id="save-keystore">
            <h1>保存KeyStore文件</h1>
            <a href="<%= downloadurl %>">保存KeyStore文件</a>
            <br>
            <button onclick="saveKeystoreNext()">Next Step</button>
        </div>
        <div id="save-privatekey" style="display:none">
            <h1>保存钱包私钥</h1>
            <div>
                <%= privatekey %>
                <span>请务必妥善保管!</span>
            </div>
        </div>
    </div>

</body>

</html>

由于涉及到了onclick,所以,我们现在需要创建js代码实现相关方法

实现saveKeystoreNext方法

  • 导入jquery文件
$ mkdir js/lib

jquery.url.jsjquery-3.3.1.min.js拷贝进lib目录中

  • 实现方法

static/js/wallet.js

function saveKeystoreNext(){
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

3.导航页设计

此处的导航页前端用的是蓝鲸智云的MagicBox组件,作为蓝鲸智云的忠实粉丝,推荐大家使用

http://magicbox.bk.tencent.com/

(1) 创建导航页

static/html/nav.html

<link href="https://magicbox.bk.tencent.com/static_api/v3/assets/bootstrap-3.3.4/css/bootstrap.min.css" rel="stylesheet">
<link href="https://magicbox.bk.tencent.com/static_api/v3/bk/css/bk.css" rel="stylesheet">

<div class="king-horizontal-nav4">
    <div class="logo_wrap">
        <a class="logo" title="" href="javascript:;">
            <img src="https://brucefeng-1251273438.cos.ap-shanghai.myqcloud.com/8.%E4%BB%A5%E5%A4%AA%E5%9D%8A%E7%BD%91%E9%A1%B5%E9%92%B1%E5%8C%85%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C/logo.png"
                 class="logo mt10 ml20">
        </a>
    </div>
    <div class="nav_wrap tmp">
        <ul>
            <li>
                <a href="/"><span>首页</span></a>
            </li>
            <li>
                <a href="/newaccount.html"><span>创建钱包</span></a>
            </li>
            <li>
                <a href="/transaction.html"><span>转账</span></a>
            </li>
            <li>
                <a href="/queryTransaction.html"><span>查询交易</span></a>
            </li>
            <li>
                <a href="https://github.com/DiaboFong/ETHWalletDemo" target="_blank"><span>项目代码</span></a>
            </li>
            <li>
                <a href="http://blog.51cto.com/clovemfong" target="_blank"><span>我的博客</span></a>
            </li>
        </ul>
    </div>
</div>

(2) 集成导航页

将nav.html集成到所有相关网页中即可,如newaccount.html

<html>

<head>
    <title>创建钱包</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h1>创建一个新的钱包</h1>
        <form method="POST" action="/newaccount">
            <input type="text" placeholder="请输入密码" name="password">
            <button type="submit">创建钱包</button>
        </form>
    </div>
</body>

</html>

五.解锁钱包账户

在通过钱包转账之前,我们都需要先对钱包账户进行解锁后才能进行正常交易,目前主要的解锁方式为

  • 私钥解锁
  • 配置文件+密码解锁
  • 助记词解锁

本文我们主要讲解如何开发通过私钥解锁跟配置文件解锁来进行账户解锁。

我们模仿MyEtherWallet的方式,将解锁钱包的功能放在转账交易模块下面,便于整合。

1. 搭建代码框架

(1) 创建控制器

controllers/transaction.js

module.exports = {
    transactionHtml: async (ctx) =>{
        await ctx.render("transaction.html")
    },
}

(2) 创建前端页面

views/transaction.html

<html>

<head>
    <title>转账</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h3>发送以太币/Token</h3>
        <input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0">
        <label for="unlockAccountType0">Keystore File</label>
        <br>
        <input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1">
        <label for="unlockAccountType1">Private Key</label>
        <!-- unlockAccount0 表示通过Keystore解锁账户 unlockAccount1 表示通过私钥解锁账户-->
        <div id="unlockAccount0" style="display:none">

        </div>

        <div id="unlockAccount1" style="display:none">

        </div>

    </div>
</body>

</html>

(3) 配置路由

router/router.js

var router = require("koa-router")();
var newAccount = require("../controllers/newAccount");
var transactionController = require("../controllers/transaction");

router.get("/",newAccount.homeHtml);
//创建账号的页面
router.get("/newaccount.html",newAccount.newAccountHtml);
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount);

//获取转账页面
router.get("/transaction.html",transactionController.transactionHtml);

module.exports = router

2. 通过私钥解锁账户

(1) 修改前端页面

views/transaction.html

<html>

<head>
    <title>转账</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
    <link rel="stylesheet" href="css/wallet.css">
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h3>发送以太币/Token</h3>
        <input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0">
        <label for="unlockAccountType0">Keystore File</label>
        <br>
        <input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1">
        <label for="unlockAccountType1">Private Key</label>
        <!-- unlockAccount0 表示通过Keystore解锁账户 unlockAccount1 表示通过私钥解锁账户-->
        <div id="unlockAccount0" style="display:none">

        </div>

        <div id="unlockAccount1" style="display:none">
            <h3>请输入钱包私钥[请认准官网,防止钓鱼]</h3>
            <textarea id="inputAccountType1" cols="" rows="3"></textarea>
            <button onclick="unlockAccountWithPK()">解锁</button>

        </div>

    </div>
</body>

</html>

修改js/wallet.js代码

function saveKeystoreNext(){
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

function unlockAccountWithPK(){
    var privateKey = $("#inputAccountType1").val()
    console.log(privateKey)
    //将私钥传至服务端
    $post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){
        console.log(status + JSON.stringify(res))
    })
}

// 对元素的操作需要等文档加载完毕后才能调用成功
$(document).ready(function (){
    $("input[name=unlockAccountType]").change(function(){
        if (this.value == 0) {
            //如果点击keystore,则显示keystore操作
            $("#unlockAccount0").show()
            $("#unlockAccount1").hide()
        }else {
            $("#unlockAccount0").hide()
            $("#unlockAccount1").show()

        }

    })
})

(2) 创建控制器

controllers/account.js

var web3 = require("../utils/myUtils").getWeb3()

module.exports = {
    unlockWithPK: (ctx) => {
        var privateKey = ctx.request.body.privatekey
        console.log(privateKey)
        var account = web3.eth.accounts.privateKeyToAccount(privateKey);
        console.log(account.address);
        ctx.response.body = "解锁成功";
    }
}

(3) 修改路由

router/router.js

var router = require("koa-router")();
var newAccount = require("../controllers/newAccount");
var transactionController = require("../controllers/transaction");
var accountController = require("../controllers/account")

router.get("/",newAccount.homeHtml);
//创建账号的页面
router.get("/newaccount.html",newAccount.newAccountHtml);
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount);

//获取转账页面
router.get("/transaction.html",transactionController.transactionHtml);
router.post("/unlockWithPK",accountController.unlockWithPK);

module.exports = router

(4) 阶段性测试

  • 启动服务
$ cd ETHWalletDemo
$ $ node index.js
钱包启动成功,请访问http://127.0.0.1:3003/...进行测试
  • 浏览器访问

http://127.0.0.1:3003/

[1] 首页

[2] 创建钱包

输入密码,此处先不隐藏,后期设计即可

)

点击保存Keystore文件,并点击Next Step

注意:此处的账号私钥不做隐藏,仅仅是为了测试需要,大家自己实际使用的私钥请务必保存妥当!!!

[3] 解锁账户

后端返回

!

前端返回

前后端调试成功

3. 显示账户余额

账户刚刚创建完毕,余额为0,我们可以通过私链转账的方式给新账户转入10以太币,具体操作可以参考博客:以太坊联盟链-多节点私链搭建手册中转账交易章节。

(1) 修改控制器

controllers/account.js

var web3 = require("../utils/myUtils").getWeb3()

async function getAccountBalance(address) {
    var balance = await web3.eth.getBalance(address);
    var balanceEther = web3.utils.fromWei(balance, ‘ether‘)
    return balanceEther;
}

module.exports = {
    unlockWithPK: async (ctx) => {
        //1.获取私钥
        var privateKey = ctx.request.body.privatekey
        console.log(privateKey)
        //2.通过私钥解锁账户
        var account = web3.eth.accounts.privateKeyToAccount(privateKey);
        console.log(account)
        //3.获取账户余额
        var balance = await getAccountBalance(account.address)
        console.log(balance)
        responseData = {
            code: 0,
            status: "success",
            data: {
                balance: balance,
                address: account.address
            }
        }
        ctx.response.body = responseData
    }

}

(2) 修改前端页面

views/transaction.html

<html>

<head>
    <title>转账</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
    <link rel="stylesheet" href="css/wallet.css">
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h3>发送以太币/Token</h3>
        <div id="transaction0">
            <input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0">
            <label for="unlockAccountType0">Keystore File</label>
            <br>
            <input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1">
            <label for="unlockAccountType1">Private Key</label>
            <!-- unlockAccount0 表示通过Keystore解锁账户 unlockAccount1 表示通过私钥解锁账户-->
            <div id="unlockAccount0" style="display:none">

            </div>

            <div id="unlockAccount1" style="display:none">
                <h3>请输入钱包私钥[请认准官网,防止钓鱼]</h3>
                <textarea id="inputAccountType1" cols="" rows="3"></textarea>
                <button onclick="unlockAccountWithPK()">解锁</button>
            </div>
        </div>

        <div id="transaction1" style="display: none">
            <div id="sendTransaction">

            </div>

            <div id="accountInfo"></div>
            <div>
                <span>账户地址:</span> <span id="accountAddress"></span>
            </div>

            <div>
                <span>账户余额:</span> <span id="accountBalance"></span>
            </div>

        </div>

    </div>
</body>

</html>
  • 将解锁界面跟账户显示页面分离
  • 根据响应是否成功显示不同页面

修改wallet.js文件

function saveKeystoreNext(){
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

function unlockAccountWithPK(){
    var privateKey = $("#inputAccountType1").val()
    console.log(privateKey)
    //将私钥传至服务端
    $.post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){
        console.log(status + JSON.stringify(res))
        //将服务端返回的账户信息显示到页面上
        if (res.code == 0){
            console.log("success yes")
            $("#accountAddress").text(res.data.address)
            $("#accountBalance").text(res.data.balance + " ETH")
            // 隐藏
            $("#transaction0").hide()
            $("#transaction1").show()

        }
    })
}

// 对元素的操作需要等文档加载完毕后才能调用成功
$(document).ready(function (){
    $("input[name=unlockAccountType]").change(function(){
        if (this.value == 0) {
            //如果点击keystore,则显示keystore操作
            $("#unlockAccount0").show()
            $("#unlockAccount1").hide()
        }else {
            $("#unlockAccount0").hide()
            $("#unlockAccount1").show()

        }

    })
})

3. 通过配置文件解锁账户

(1) 封装响应消息

utils/myUtils.js

module.exports = {
    getWeb3: () => {
        var Web3 = require("web3");
        var web3 = new Web3(Web3.givenProvider || ‘http://127.0.0.1:8545‘);
        return web3;
    },
    success: (data) => {
        responseData = {
            code: 0,
            status: "success",
            data: data
        }
        return responseData
    },
    fail :(msg) => {
        responseData = {
            code: 1,
            status: "fail",
            msg: msg
        }
        return responseData
    }
}

(2) 修改控制器

controller/account.js

var web3 = require("../utils/myUtils").getWeb3()
var {success,fail} = require("../utils/myUtils")
var fs = require("fs")

async function getAccountBalance(address) {
    var balance = await web3.eth.getBalance(address);
    var balanceEther = web3.utils.fromWei(balance, ‘ether‘)
    return balanceEther;
}

module.exports = {
    unlockWithPK: async (ctx) => {
        //1.获取私钥
        var privateKey = ctx.request.body.privatekey
        console.log(privateKey)
        //2.通过私钥解锁账户
        var account = web3.eth.accounts.privateKeyToAccount(privateKey);
        console.log(account)
        //3.获取账户余额
        var balance = await getAccountBalance(account.address)
        console.log(balance)
        ctx.response.body = success({
            balance:balance,
            address:account.address
        })
    },

    unlockWithKS: async (ctx) => {
        //获取前端传递的数据,password跟keystore
        var password = ctx.request.body.password
        console.log(password)
        var keystore = ctx.request.files.file
        console.log(keystore)
        //读取缓存文件中keystore的数据
        var keystoreData = fs.readFileSync(keystore.path, "utf8")
        console.log(keystoreData)
        // 通过keystore和密码解锁账户
        var account = web3.eth.accounts.decrypt(JSON.parse(keystoreData), password)
        console.log(account)
        //获取账户余额
        var balance = await getAccountBalance(account.address)
        console.log(balance)
        ctx.response.body = success({
            balance:balance,
            address:account.address
        })

    }
}

(2) 修改前端页面

views/transaction.html

<html>

<head>
    <title>转账</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
    <link rel="stylesheet" href="css/wallet.css">
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h3>发送以太币/Token</h3>
        <div id="transaction0">
            <input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0">
            <label for="unlockAccountType0">Keystore File</label>
            <br>
            <input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1">
            <label for="unlockAccountType1">Private Key</label>
            <!-- unlockAccount0 表示通过Keystore解锁账户 unlockAccount1 表示通过私钥解锁账户-->
            <div id="unlockAccount0" style="display:none">
                <h3>请选择KeyStore文件</h3>
                <input type="file">
                <input type="password" id="inputAccountType0">
                <button onclick="unlockAccountWithKS()">解锁</button>

            </div>

            <div id="unlockAccount1" style="display:none">
                <h3>请输入钱包私钥[请认准官网,防止钓鱼]</h3>
                <textarea id="inputAccountType1" cols="" rows="3"></textarea>
                <button onclick="unlockAccountWithPK()">解锁</button>
            </div>
        </div>

        <div id="transaction1" style="display: none">
            <div id="sendTransaction">

            </div>

            <div id="accountInfo"></div>
            <div>
                <span>账户地址:</span> <span id="accountAddress"></span>
            </div>

            <div>
                <span>账户余额:</span> <span id="accountBalance"></span>
            </div>

        </div>

    </div>
</body>

</html>

修改wallet.js文件

function saveKeystoreNext(){
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

function configAccountInfo(data) {
    $("#accountAddress").text(data.address)
    $("#accountBalance").text(data.balance + " ETH")
    // 隐藏
    $("#transaction0").hide()
    $("#transaction1").show()
}

function unlockAccountWithPK(){
    var privateKey = $("#inputAccountType1").val()
    console.log(privateKey)
    //将私钥传至服务端
    $.post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){
        console.log(status + JSON.stringify(res))
        //将服务端返回的账户信息显示到页面上
        if (res.code == 0){
            configAccountInfo(res.data)
        }
    })
}

function unlockAccountWithKS(){
    var filedata = $("#inputAccountType0").val()
    if (filedata.length <=0 ){
        alert("未选择文件,请选择文件上传!")
        return
    }
    //文件上传通过Formdata去存储文件的数据
    var data = new FormData()
    data.append("file", $("#inputAccountType0")[0].files[0])
    data.append("password",$("#inputAccountTypePassword").val())
    //提交到后端的路径
    var urlStr = "/unlockWithKS"
    $.ajax({
        url: urlStr,
        type: "post",
        dataType: "json",
        contentType : false,
        data :data,
        processData: false,
        success : function(res, status) {
            alert("解锁成功,可以使用该账户进行转账操作")
            if (res.code == 0) {
                configAccountInfo(res.data)
            }

        },
        error: function(res, status){
            alert("KeyStore文件与密码不匹配")

        }
    })

}

// 对元素的操作需要等文档加载完毕后才能调用成功
$(document).ready(function (){
    $("input[name=unlockAccountType]").change(function(){
        if (this.value == 0) {
            //如果点击keystore,则显示keystore操作
            $("#unlockAccount0").show()
            $("#unlockAccount1").hide()
        }else {
            $("#unlockAccount0").hide()
            $("#unlockAccount1").show()

        }

    })
})

(3) 配置路由

var router = require("koa-router")();
var newAccount = require("../controllers/newAccount");
var transactionController = require("../controllers/transaction");
var accountController = require("../controllers/account")

router.get("/",newAccount.homeHtml);
//创建账号的页面
router.get("/newaccount.html",newAccount.newAccountHtml);
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount);

//获取转账页面
router.get("/transaction.html",transactionController.transactionHtml);
//通过私钥解锁账户
router.post("/unlockWithPK",accountController.unlockWithPK);
//通过配置文件解锁账户
router.post("/unlockWithKS",accountController.unlockWithKS);

module.exports = router

(4) 阶段性测试

!

选择配置文件,输入密码解锁

验证成功

显示账户信息

至此,我们现在完成了私钥解锁账户以及配置文件解锁账户的功能了。

六.实现交易转账

1. 以太币转账

$ npm install ethereumjs-tx

(1) 修改控制器

controllers/transaction.js

var {success, fail } = require("../utils/myUtils")
var web3 = require("../utils/myUtils").getWeb3()
module.exports = {
    transactionHtml: async (ctx) => {
        await ctx.render("transaction.html")
    },
    sendTransaction: async (ctx) => {
        var { fromAddress, toAddress, amount, privateKey } = ctx.request.body
        var Tx = require(‘ethereumjs-tx‘);
        var privateKey = new Buffer(privateKey.slice(2), ‘hex‘)
        var nonce = await web3.eth.getTransactionCount(fromAddress)
        var gasPrice = await web3.eth.getGasPrice()
        var amountToWei = web3.utils.toWei(amount,‘ether‘)

        var rawTx = {
            nonce: nonce ,
            gasPrice: gasPrice,
            gasLimit: ‘0x2710‘,
            to: toAddress,
            value: amountToWei,
            data: ‘0x00‘
        }
        //对交易的数据进行gas计算,然后将gas值设置到参数中
        var gas = await web3.eth.estimateGas(rawTx)
        rawTx.gas = gas

        var tx = new Tx(rawTx);
        tx.sign(privateKey);

        var serializedTx = tx.serialize();

        var responseData 

       await web3.eth.sendSignedTransaction(‘0x‘ + serializedTx.toString(‘hex‘),function(err,data){
           console.log(err)
           console.log(data)
           if (err) {
               responseData = fail(err)
           }
       }).then(function (data){
           console.log(data)
           if (data) {
               responseData = success({
                   "blockHash": data.blockHash,
                   "transactionHash": data.transactionHash
               })
           }else {
               responseData  = fail("交易失败")
           }
       })

    }
}

(2) 修改前端页面

views/transaction.html

<html>

<head>
    <title>转账</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
    <link rel="stylesheet" href="css/wallet.css">
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h3>发送以太币/Token</h3>
        <div id="transaction0">
            <input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0">
            <label for="unlockAccountType0">Keystore File</label>
            <br>
            <input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1">
            <label for="unlockAccountType1">Private Key</label>
            <!-- unlockAccount0 表示通过Keystore解锁账户 unlockAccount1 表示通过私钥解锁账户-->
            <div id="unlockAccount0" style="display:none">
                <h3>请选择KeyStore文件</h3>
                <input type="file" id="inputAccountType0">
                <span>输入密码:</span>
                <input type="password" id="inputAccountTypePassword">
                <br>
                <button onclick="unlockAccountWithKS()">解锁</button>

            </div>

            <div id="unlockAccount1" style="display:none">
                <h3>请输入钱包私钥[请认准官网,防止钓鱼]</h3>
                <textarea id="inputAccountType1" cols="" rows="3"></textarea>
                <button onclick="unlockAccountWithPK()">解锁</button>
            </div>
        </div>

        <div id="transaction1" style="display: none">
            <div id="sendTransaction">
                <form id="sendTransactionForm">
                    <div>
                        <span>对方地址</span>
                        <input type="text" name="toAddress">
                    </div>
                    <div>
                        <span>发送金额</span>
                        <input type="text" name="amount">
                    </div>
                    <input name="fromAddress" hidden="hidden">
                    <input name="privateKey" hidden="hidden">
                    <button type="submit">发送交易</button>
                </form>

            </div>

            <div id="accountInfo"></div>
            <div>
                <span>账户地址:</span> <span id="accountAddress"></span>
            </div>

            <div>
                <span>账户余额:</span> <span id="accountBalance"></span>
            </div>

            <div id="transactionComplete" style="display:none">
                <div>
                        <span>交易Hash:</span>
                        <span id="transactionCompleteHash0"></span>
                </div>
                <div>
                        <span>Block Hash:</span>
                        <span id="transactionCompleteHash1"></span>
                </div>

            </div>

        </div>

    </div>
</body>

</html>

修改wallet.js文件

function saveKeystoreNext(){
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

function configAccountInfo(data) {
    $("#accountAddress").text(data.address)
    $("#accountBalance").text(data.balance + " ETH")
    // 隐藏
    $("#transaction0").hide()
    $("#transaction1").show()

    $("input[name=fromAddress]").val(data.address)
    $("input[name=privateKey]").val(data.privatekey)
}

function unlockAccountWithPK(){
    var privateKey = $("#inputAccountType1").val()
    console.log(privateKey)
    //将私钥传至服务端
    $.post("/unlockWithPK",`privatekey=${privateKey}`,function(res,status){
        console.log(status + JSON.stringify(res))
        //将服务端返回的账户信息显示到页面上
        if (res.code == 0){
            configAccountInfo(res.data)
        }
    })
}

function unlockAccountWithKS(){
    var filedata = $("#inputAccountType0").val()
    if (filedata.length <=0 ){
        alert("未选择文件,请选择文件上传!")
        return
    }
    //文件上传通过Formdata去存储文件的数据
    var data = new FormData()
    data.append("file", $("#inputAccountType0")[0].files[0])
    data.append("password",$("#inputAccountTypePassword").val())
    //提交到后端的路径
    var urlStr = "/unlockWithKS"
    $.ajax({
        url: urlStr,
        type: "post",
        dataType: "json",
        contentType : false,
        data :data,
        processData: false,
        success : function(res, status) {
            alert("解锁成功,可以使用该账户进行转账操作")
            if (res.code == 0) {
                configAccountInfo(res.data)
            }

        },
        error: function(res, status){
            alert("KeyStore文件与密码不匹配")

        }
    })

}

//转账 对元素的操作需要等文档加载完毕后才能调用成功
$(document).ready(function (){
    $("input[name=unlockAccountType]").change(function(){
        if (this.value == 0) {
            //如果点击keystore,则显示keystore操作
            $("#unlockAccount0").show()
            $("#unlockAccount1").hide()
        }else {
            $("#unlockAccount0").hide()
            $("#unlockAccount1").show()
        }
    })

    $("#sendTransactionForm").validate({
        rules: {
            toAddress:{
                required:true,
            },
            amount:{
                required:true,
            },
        },
        messages: {
            toAddress:{
                required:"请输入对方钱包地址",
            },
            amount:{
                required:"请输入转账金额",
            },
        },
        submitHandler: function(form)
        {
            var urlStr = "/sendtransaction"
            alert("urlStr:" +urlStr)
            $(form).ajaxSubmit({
                url:urlStr,
                type:"post",
                dataType:"json",
                success:function(res,status){
                    console.log(status + JSON.stringify(res))
                    if (res.code  == 0){
                        $("#transactionCompleteHash0").text(res.data.transactionHash)
                        $("#transactionCompleteHash1").text(res.data.blockHash)
                        $("#transactionComplete").show()
                    }
                },
                error:function(res,status){
                    console.log(status + JSON.stringify(res))

                }
            })
        }
    })

})

(3) 配置路由

router/router.js

var router = require("koa-router")();
var newAccount = require("../controllers/newAccount");
var transactionController = require("../controllers/transaction");
var accountController = require("../controllers/account")

router.get("/",newAccount.homeHtml);
//创建账号的页面
router.get("/newaccount.html",newAccount.newAccountHtml);
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount);

//获取转账页面
router.get("/transaction.html",transactionController.transactionHtml);
router.post("/sendtransaction",transactionController.sendTransaction)
//通过私钥解锁账户
router.post("/unlockWithPK",accountController.unlockWithPK);
//通过配置文件解锁账户
router.post("/unlockWithKS",accountController.unlockWithKS);

module.exports = router

2. 交易信息查询

(1) 修改控制器

controller/transaction.js

var { success, fail } = require("../utils/myUtils")
var web3 = require("../utils/myUtils").getWeb3()
module.exports = {
    transactionHtml: async (ctx) => {
        await ctx.render("transaction.html")
    },
    sendTransaction: async (ctx) => {
        var { fromAddress, toAddress, amount, privateKey } = ctx.request.body
        var Tx = require(‘ethereumjs-tx‘);
        var privateKey = new Buffer(privateKey.slice(2), ‘hex‘)
        var nonce = await web3.eth.getTransactionCount(fromAddress)
        var gasPrice = await web3.eth.getGasPrice()
        var amountToWei = web3.utils.toWei(amount, ‘ether‘)

        var rawTx = {
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: ‘0x2710‘,
            to: toAddress,
            value: amountToWei,
            data: ‘0x00‘
        }
        //对交易的数据进行gas计算,然后将gas值设置到参数中
        var gas = await web3.eth.estimateGas(rawTx)
        rawTx.gas = gas

        var tx = new Tx(rawTx);
        tx.sign(privateKey);

        var serializedTx = tx.serialize();

        var responseData;

        await web3.eth.sendSignedTransaction(‘0x‘ + serializedTx.toString(‘hex‘), function (err, data) {
            console.log(err)
            console.log(data)
            if (err) {
                responseData = fail(err)
            }
        }).then(function (data) {
            console.log(data)
            if (data) {
                responseData = success({
                    "blockHash": data.blockHash,
                    "transactionHash": data.transactionHash
                })
            } else {
                responseData = fail("交易失败")
            }
        })
        ctx.response.body = responseData

    },
    queryTransactionHtml: async (ctx) => {
        await ctx.render("queryTransaction.html")
    },
    queryTransaction: async (ctx) => {
        var txHash = ctx.request.body.txHash
        await web3.eth.getTransaction(txHash, function (err, res) {
            if (err) {
                responseData = fail(err)
            }
        }).then(function(res){
            if (res) {
                responseData = success(res)
            }else {
                responseData = fail("查询失败")
            }
        })
        ctx.response.body = responseData

    }
}

(2) 创建前端页面

views/queryTransaction.html

<html>

<head>
    <title>查询交易详情</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
    <link rel="stylesheet" href="css/wallet.css">
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h1>查询交易详情</h1>

        <input type="text" id="txHash">
        <button onclick="queryTransaction()">查询</button>

        <!-- 用于显示代码块 -->
        <pre id="transactionInfo"></pre>

    </div>
</body>

</html>

修改wallet.js文件

function saveKeystoreNext() {
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

function configAccountInfo(data) {
    $("#accountAddress").text(data.address)
    $("#accountBalance").text(data.balance + " ETH")
    // 隐藏
    $("#transaction0").hide()
    $("#transaction1").show()

    $("input[name=fromAddress]").val(data.address)
    $("input[name=privateKey]").val(data.privatekey)
}

function unlockAccountWithPK() {
    var privateKey = $("#inputAccountType1").val()
    console.log(privateKey)
    //将私钥传至服务端
    $.post("/unlockWithPK", `privatekey=${privateKey}`, function (res, status) {
        console.log(status + JSON.stringify(res))
        //将服务端返回的账户信息显示到页面上
        if (res.code == 0) {
            configAccountInfo(res.data)
        }
    })
}

function unlockAccountWithKS() {
    var filedata = $("#inputAccountType0").val()
    if (filedata.length <= 0) {
        alert("未选择文件,请选择文件上传!")
        return
    }
    //文件上传通过Formdata去存储文件的数据
    var data = new FormData()
    data.append("file", $("#inputAccountType0")[0].files[0])
    data.append("password", $("#inputAccountTypePassword").val())
    //提交到后端的路径
    var urlStr = "/unlockWithKS"
    $.ajax({
        url: urlStr,
        type: "post",
        dataType: "json",
        contentType: false,
        data: data,
        processData: false,
        success: function (res, status) {
            alert("解锁成功,可以使用该账户进行转账操作")
            if (res.code == 0) {
                configAccountInfo(res.data)
            }

        },
        error: function (res, status) {
            alert("KeyStore文件与密码不匹配")

        }
    })

}

//转账 对元素的操作需要等文档加载完毕后才能调用成功
$(document).ready(function () {
    $("input[name=unlockAccountType]").change(function () {
        if (this.value == 0) {
            //如果点击keystore,则显示keystore操作
            $("#unlockAccount0").show()
            $("#unlockAccount1").hide()
        } else {
            $("#unlockAccount0").hide()
            $("#unlockAccount1").show()
        }
    })

    $("#sendTransactionForm").validate({
        rules: {
            toAddress: {
                required: true,
            },
            amount: {
                required: true,
            },
        },
        messages: {
            toAddress: {
                required: "请输入对方钱包地址",
            },
            amount: {
                required: "请输入转账金额",
            },
        },
        submitHandler: function (form) {
            var urlStr = "/sendtransaction"
            alert("urlStr:" + urlStr)
            $(form).ajaxSubmit({
                url: urlStr,
                type: "post",
                dataType: "json",
                success: function (res, status) {
                    console.log(status + JSON.stringify(res))
                    if (res.code == 0) {
                        $("#transactionCompleteHash0").text(res.data.transactionHash)
                        $("#transactionCompleteHash1").text(res.data.blockHash)
                        $("#transactionComplete").show()
                    }
                },
                error: function (res, status) {
                    console.log(status + JSON.stringify(res))

                }
            })
        }
    })

})

//查询交易详情
function queryTransaction() {
    var txHash = $("#txHash").val()
    $.post("/queryTransaction", "txHash=" + txHash, function (res, status) {
        console.log(status + JSON.stringify(res))
        if (res.code == 0) {
            alert("查询成功")
            $("#transactionInfo").text(JSON.stringify(res.data, null, 4))
        } else {
            alert("查询失败")
        }
    })
}

(3) 配置路由

router/router.js

var router = require("koa-router")();
var newAccount = require("../controllers/newAccount");
var transactionController = require("../controllers/transaction");
var accountController = require("../controllers/account")

router.get("/",newAccount.homeHtml);
//创建账号的页面
router.get("/newaccount.html",newAccount.newAccountHtml);
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount);

//获取转账页面
router.get("/transaction.html",transactionController.transactionHtml);
//发送交易
router.post("/sendtransaction",transactionController.sendTransaction)
//查询交易详情
router.get("/queryTransaction.html",transactionController.queryTransactionHtml)
router.post("/queryTransaction",transactionController.queryTransaction)
//通过私钥解锁账户
router.post("/unlockWithPK",accountController.unlockWithPK);
//通过配置文件解锁账户
router.post("/unlockWithKS",accountController.unlockWithKS);

module.exports = router

(4) 阶段性测试

执行账户解锁

执行转账交易

以太坊私有链终端产生交易,执行挖矿

显示交易Hash以及区块Hash

通过交易Hash返回交易详情

3. 实现Token转账

以上内容,我们实现了对以太币的转账与查询功能,而现实情况中,我们很多区块链公司都会开发适用于以太坊与自己Token的钱包,本文直接沿用以太坊智能合约项目-Token合约开发与部署中编写的合约代码。

(1) 获取合约信息

  • 获取Token的ABI

  • 获取Token合约地址

通过Remix进行部署后获取地址:0xa77a5c71b9cf71e89215ceec9767c536e79ced68

(2) 创建控制器

controller/token.js


var { success, fail } = require("../utils/myUtils")
var web3 = require("../utils/myUtils").getWeb3()
var myContract = require("../models/contract").getContract()

module.exports = {
    sendTokenTransaction: async (ctx) => {
        var { fromAddress, toAddress, amount, privateKey } = ctx.request.body
        var Tx = require(‘ethereumjs-tx‘);
        var privateKey = new Buffer(privateKey.slice(2), ‘hex‘)
        var nonce = await web3.eth.getTransactionCount(fromAddress)
        var gasPrice = await web3.eth.getGasPrice()
        //获取Token合约的decimals
        var decimals = await myContract.methods.decimals().call()
        var amountToWei = amount * Math.pow(10, decimals)

        var myBalance = await myContract.methods.balanceOf(fromAddress).call()
        if (myBalance < amountToWei) {
            ctx.response.body = fail("余额不足")
            return
        }
        var tokenData = await myContract.methods.transfer(toAddress, amountToWei).encodeABI()

        var rawTx = {
            nonce: nonce,
            gasPrice: gasPrice,
            gasLimit: ‘0x2710‘,
            to: myContract.options.address, //如果是发送token ,此处应该填写合约地址,此处需要注意
            value: amountToWei,
            // data: tokenData
            data: "0x00"
        }
        //对交易的数据进行gas计算,然后将gas值设置到参数中
        var gas = await web3.eth.estimateGas(rawTx)
        rawTx.gas = gas

        var tx = new Tx(rawTx);
        tx.sign(privateKey);

        var serializedTx = tx.serialize();

        var responseData;

        await web3.eth.sendSignedTransaction(‘0x‘ + serializedTx.toString(‘hex‘), function (err, data) {
            console.log(err)
            console.log(data)
            if (err) {
                responseData = fail(err)
            }
        }).then(function (data) {
            console.log(data)
            if (data) {
                responseData = success({
                    "blockHash": data.blockHash,
                    "transactionHash": data.transactionHash
                })
            } else {
                responseData = fail("交易失败")
            }
        })
        ctx.response.body = responseData

    }
}

(3) 创建模型

models/contract.js

var web3 = require("../utils/myUtils").getWeb3()

module.exports = {
    getContract :(ctx)=> {
        var ABI = [
            {
                "constant": true,
                "inputs": [],
                "name": "name",
                "outputs": [
                    {
                        "name": "",
                        "type": "string"
                    }
                ],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": false,
                "inputs": [
                    {
                        "name": "_spender",
                        "type": "address"
                    },
                    {
                        "name": "_value",
                        "type": "uint256"
                    }
                ],
                "name": "approve",
                "outputs": [
                    {
                        "name": "success",
                        "type": "bool"
                    }
                ],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [],
                "name": "totalSupply",
                "outputs": [
                    {
                        "name": "",
                        "type": "uint256"
                    }
                ],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": false,
                "inputs": [
                    {
                        "name": "_from",
                        "type": "address"
                    },
                    {
                        "name": "_to",
                        "type": "address"
                    },
                    {
                        "name": "_value",
                        "type": "uint256"
                    }
                ],
                "name": "transferFrom",
                "outputs": [
                    {
                        "name": "success",
                        "type": "bool"
                    }
                ],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [],
                "name": "decimals",
                "outputs": [
                    {
                        "name": "",
                        "type": "uint8"
                    }
                ],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [
                    {
                        "name": "_owner",
                        "type": "address"
                    }
                ],
                "name": "balanceOf",
                "outputs": [
                    {
                        "name": "balance",
                        "type": "uint256"
                    }
                ],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [],
                "name": "symbol",
                "outputs": [
                    {
                        "name": "",
                        "type": "string"
                    }
                ],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "constant": false,
                "inputs": [
                    {
                        "name": "_to",
                        "type": "address"
                    },
                    {
                        "name": "_value",
                        "type": "uint256"
                    }
                ],
                "name": "transfer",
                "outputs": [
                    {
                        "name": "success",
                        "type": "bool"
                    }
                ],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "function"
            },
            {
                "constant": true,
                "inputs": [
                    {
                        "name": "_owner",
                        "type": "address"
                    },
                    {
                        "name": "_spender",
                        "type": "address"
                    }
                ],
                "name": "allowance",
                "outputs": [
                    {
                        "name": "remaining",
                        "type": "uint256"
                    }
                ],
                "payable": false,
                "stateMutability": "view",
                "type": "function"
            },
            {
                "inputs": [
                    {
                        "name": "_name",
                        "type": "string"
                    },
                    {
                        "name": "_symbol",
                        "type": "string"
                    },
                    {
                        "name": "_decimals",
                        "type": "uint8"
                    },
                    {
                        "name": "_totalSupply",
                        "type": "uint256"
                    }
                ],
                "payable": false,
                "stateMutability": "nonpayable",
                "type": "constructor"
            },
            {
                "anonymous": false,
                "inputs": [
                    {
                        "indexed": true,
                        "name": "_from",
                        "type": "address"
                    },
                    {
                        "indexed": true,
                        "name": "_to",
                        "type": "address"
                    },
                    {
                        "indexed": false,
                        "name": "_value",
                        "type": "uint256"
                    }
                ],
                "name": "Transfer",
                "type": "event"
            },
            {
                "anonymous": false,
                "inputs": [
                    {
                        "indexed": true,
                        "name": "_owner",
                        "type": "address"
                    },
                    {
                        "indexed": true,
                        "name": "_spender",
                        "type": "address"
                    },
                    {
                        "indexed": false,
                        "name": "_value",
                        "type": "uint256"
                    }
                ],
                "name": "Approval",
                "type": "event"
            }
        ]

        var contractAddress = "0xa77a5c71b9cf71e89215ceec9767c536e79ced68"
        var myContract = new web3.eth.Contract(ABI,contractAddress)
        return myContract

    }
}

(4) 修改控制器

controller/account.js

var web3 = require("../utils/myUtils").getWeb3()
var { success, fail } = require("../utils/myUtils")
var fs = require("fs")
var myContract = require("..//models/contract").getContract()

async function getAccountBalance(address) {
    var balance = await web3.eth.getBalance(address);
    var balanceEther = web3.utils.fromWei(balance, ‘ether‘)
    return balanceEther;
}

async function setResponseData(account) {
    //配置返回给前端的数据:以太币跟Token的数据
    var balance = await getAccountBalance(account.address)
    console.log(balance)
    //获取Token数据
    var tokenBalance = await myContract.methods.balanceOf(account.address).call()
    var tokenSymbol = await myContract.methods.symbol().call()
    return success({
        balance: balance,
        address: account.address,
        privatekey: account.privateKey,
        tokenBalance: tokenBalance,
        tokenSymbol: tokenSymbol

    })

}

module.exports = {
    unlockWithPK: async (ctx) => {
        //1.获取私钥
        var privateKey = ctx.request.body.privatekey
        console.log(privateKey)
        //2.通过私钥解锁账户
        var account = web3.eth.accounts.privateKeyToAccount(privateKey);
        console.log(account)
        //3.获取账户余额
        var balance = await getAccountBalance(account.address)
        //将账户信息返回给前端
        ctx.response.body = await setResponseData(account)
    },

    unlockWithKS: async (ctx) => {
        //获取前端传递的数据,password跟keystore
        var password = ctx.request.body.password
        console.log(password)
        var keystore = ctx.request.files.file
        console.log(keystore)
        //读取缓存文件中keystore的数据
        var keystoreData = fs.readFileSync(keystore.path, "utf8")
        console.log(keystoreData)
        // 通过keystore和密码解锁账户
        var account = web3.eth.accounts.decrypt(JSON.parse(keystoreData), password)
        console.log(account)
        //将账户信息返回给前端
        ctx.response.body = await setResponseData(account)

    }
}

修改前端页面

<html>

<head>
    <title>转账</title>
    <script src="js/lib/jquery-3.3.1.min.js"></script>
    <script src="js/lib/jquery.url.js"></script>
    <script src="js/wallet.js"></script>
    <link rel="stylesheet" href="css/wallet.css">
</head>

<body>
    <div id="nav">
        <script>
            $("#nav").load("html/nav.html")
        </script>
    </div>
    <div id="main">
        <h3>发送以太币/Token</h3>
        <div id="transaction0">
            <input type="radio" id="unlockAccountType0" name="unlockAccountType" value="0">
            <label for="unlockAccountType0">Keystore File</label>
            <br>
            <input type="radio" id="unlockAccountType1" name="unlockAccountType" vaule="1">
            <label for="unlockAccountType1">Private Key</label>
            <!-- unlockAccount0 表示通过Keystore解锁账户 unlockAccount1 表示通过私钥解锁账户-->
            <div id="unlockAccount0" style="display:none">
                <h3>请选择KeyStore文件</h3>
                <input type="file" id="inputAccountType0">
                <span>输入密码:</span>
                <input type="password" id="inputAccountTypePassword">
                <br>
                <button onclick="unlockAccountWithKS()">解锁</button>

            </div>

            <div id="unlockAccount1" style="display:none">
                <h3>请输入钱包私钥[请认准官网,防止钓鱼]</h3>
                <textarea id="inputAccountType1" cols="" rows="3"></textarea>
                <button onclick="unlockAccountWithPK()">解锁</button>
            </div>
        </div>

        <div id="transaction1" style="display: none">
            <div id="sendTransaction">
                <form id="sendTransactionForm">
                    <div>
                        <span>对方地址</span>
                        <input type="text" name="toAddress">
                    </div>
                    <div>
                        <span>发送金额</span>
                        <input type="text" name="amount">
                    </div>
                    <input name="fromAddress" hidden="hidden">
                    <input name="privateKey" hidden="hidden">
                    <button type="submit">发送交易</button>
                </form>

            </div>

            <div id="accountInfo"></div>
            <div>
                <span>账户地址:</span> <span id="accountAddress"></span>
            </div>

            <div>
                <span>账户余额:</span> <span id="accountBalance"></span>
                <br>
                <span id="accountTokenInfo"></span>
            </div>

            <div id="transactionComplete" style="display:none">
                <div>
                        <span>交易Hash:</span>
                        <span id="transactionCompleteHash0"></span>
                </div>
                <div>
                        <span>Block Hash:</span>
                        <span id="transactionCompleteHash1"></span>
                </div>

            </div>

        </div>

    </div>
</body>

</html>

修改wallet.js文件

function saveKeystoreNext() {
    //隐藏保存keystore页面
    $("#save-keystore").hide()
    //显示保存private页面
    $("#save-privatekey").show()
}

function configAccountInfo(data) {
    $("#accountAddress").text(data.address)
    $("#accountBalance").text(data.balance + " ETH")
    // 隐藏
    $("#transaction0").hide()
    $("#transaction1").show()

    $("input[name=fromAddress]").val(data.address)
    $("input[name=privateKey]").val(data.privatekey)

    $("#accountTokenInfo").text(data.tokenBalance + " " + data.tokenSymbol)
    $("#TokenSymbol").text(data.tokenSymbol)
}

function unlockAccountWithPK() {
    var privateKey = $("#inputAccountType1").val()
    console.log(privateKey)
    //将私钥传至服务端
    $.post("/unlockWithPK", `privatekey=${privateKey}`, function (res, status) {
        console.log(status + JSON.stringify(res))
        //将服务端返回的账户信息显示到页面上
        if (res.code == 0) {
            configAccountInfo(res.data)
        }
    })
}

function unlockAccountWithKS() {
    var filedata = $("#inputAccountType0").val()
    if (filedata.length <= 0) {
        alert("未选择文件,请选择文件上传!")
        return
    }
    //文件上传通过Formdata去存储文件的数据
    var data = new FormData()
    data.append("file", $("#inputAccountType0")[0].files[0])
    data.append("password", $("#inputAccountTypePassword").val())
    //提交到后端的路径
    var urlStr = "/unlockWithKS"
    $.ajax({
        url: urlStr,
        type: "post",
        dataType: "json",
        contentType: false,
        data: data,
        processData: false,
        success: function (res, status) {
            alert("解锁成功,可以使用该账户进行转账操作")
            if (res.code == 0) {
                configAccountInfo(res.data)
            }

        },
        error: function (res, status) {
            alert("KeyStore文件与密码不匹配")

        }
    })

}

//转账 对元素的操作需要等文档加载完毕后才能调用成功
$(document).ready(function () {
    $("input[name=unlockAccountType]").change(function () {
        if (this.value == 0) {
            //如果点击keystore,则显示keystore操作
            $("#unlockAccount0").show()
            $("#unlockAccount1").hide()
        } else {
            $("#unlockAccount0").hide()
            $("#unlockAccount1").show()
        }
    })

    $("#sendTransactionForm").validate({
        rules: {
            toAddress: {
                required: true,
            },
            amount: {
                required: true,
            },
        },
        messages: {
            toAddress: {
                required: "请输入对方钱包地址",
            },
            amount: {
                required: "请输入转账金额",
            },
        },
        submitHandler: function (form) {
            var urlStr
            var tokenType = $("#TokenType").val()
            if (tokenType == 0) {
                urlStr = "/sendtransaction"
            }else {
                urlStr = "/sendToken"
            }

            alert("urlStr:" + urlStr)
            $(form).ajaxSubmit({
                url: urlStr,
                type: "post",
                dataType: "json",
                success: function (res, status) {
                    console.log(status + JSON.stringify(res))
                    if (res.code == 0) {
                        $("#transactionCompleteHash0").text(res.data.transactionHash)
                        $("#transactionCompleteHash1").text(res.data.blockHash)
                        $("#transactionComplete").show()
                    }
                },
                error: function (res, status) {
                    console.log(status + JSON.stringify(res))

                }
            })
        }
    })

})

//查询交易详情
function queryTransaction() {
    var txHash = $("#txHash").val()
    $.post("/queryTransaction", "txHash=" + txHash, function (res, status) {
        console.log(status + JSON.stringify(res))
        if (res.code == 0) {
            alert("查询成功")
            $("#transactionInfo").text(JSON.stringify(res.data, null, 4))
        } else {
            alert("查询失败")
        }
    })
}

(5) 配置路由

var router = require("koa-router")();
var newAccount = require("../controllers/newAccount");
var transactionController = require("../controllers/transaction");
var accountController = require("../controllers/account")
var tokenController = require("../controllers/token")

router.get("/",newAccount.homeHtml);
//创建账号的页面
router.get("/newaccount.html",newAccount.newAccountHtml);
//提交创建账号表单
router.post("/newaccount",newAccount.newAccount);

//获取转账页面
router.get("/transaction.html",transactionController.transactionHtml);
//发送交易
router.post("/sendtransaction",transactionController.sendTransaction)
//查询交易详情
router.get("/queryTransaction.html",transactionController.queryTransactionHtml)
router.post("/queryTransaction",transactionController.queryTransaction)
//通过私钥解锁账户
router.post("/unlockWithPK",accountController.unlockWithPK);
//通过配置文件解锁账户
router.post("/unlockWithKS",accountController.unlockWithKS);

//token转账
router.post("/sendToken", tokenController.sendTokenTransaction)

module.exports = router

原文地址:http://blog.51cto.com/clovemfong/2306700

时间: 2024-10-07 10:04:51

以太坊Dapp项目-网页钱包开发手册的相关文章

以太坊Dapp项目-拍卖网站-智能合约编写测试

修订日期 姓名 邮箱 2018-10-18 brucefeng [email protected] 前言 写这篇文章的初衷其实很简单,在MyEtherWallet上申请以太坊ENS的时候,竞标的以太币两次被吞,而且是在规定时间点进行了价格公告,这篇文章的设计思路其实就是跟ENS的竞标流程类似,希望对大家有所帮助,所以,准备写完之后,再重新去整一次ENS的申请,如果再被吞,我就要举报了:-),本文主要是本人用于项目整理,便于自己查询,不做任何商业用途. 现在回归到技术上来,这个项目其实涉及到蛮多的

以太坊智能合约及应用开发简介

在这个入门教程中我们将建立以太坊应用开发环境并学习编写一个投票智能合约. 在这个教程中,让我们构建一个简单的"Hello World!" 应用程序, 这是一个投票应用程序. 该应用程序非常简单,它所做的只是初始化一组候选人,让任何人投票给候选人,并显示每个候选人收到的总票数. 我有意避免使用任何DAPP框架构建这个应用程序,因为框架抽象掉很多细节,你不了解系统的内部.此外,当你使用框架时,将对框架所做的繁重工作有更多的体会! 1. 设置开发环境 我们使用一个模拟的内存区块链(ganac

天然工坊平台项目模式系统开发

随着微信产品的日趋成熟,各行各业的企业都蠢蠢欲动,想吃下微信这款蛋糕,湖南天然工坊就开启了一条新型的互联网之路,该公司研发的竹妃纸巾,通过微信公众号平台以及搭载的自有web电商平台,通过口碑引爆传播,天然工坊从竹妃纸巾切入新兴消费品市场,在产品主张.销售模式都突破了以往既定的路径.天然工坊平台项目模式系统开发找何经理.天然工坊系统项目开发.天然工坊模式平台项目开发等--(188.264.66502 微/电) 最近几年随着响应式布局的发展,一次开发多次使用,自适应屏幕的响应式网站的需求越来越多.但

以太坊 DApp 开发入门实战! 用Node.js和truffle框架搭建——区块链投票系统!

第一节 概述 面向初学者,内容涵盖以太坊开发相关的基本概念,并将手把手地教大家如何构建一个 基于以太坊的完整去中心化应用 -- 区块链投票系统. 通过学习,你将掌握: 以太坊区块链的基本知识 开发和部署以太坊合约所需的软件环境 使用高级语言(solidity)编写以太坊合约 使用NodeJS编译.部署合约并与之交互 使用Truffle框架开发分布式应用 使用控制台或网页与合约进行交互 前序知识要求 为了顺利完成,最好对以下技术已经有一些基本了解: 一种面向对象的开发语言,例如:Python,Ru

区块链学习(3)--以太坊Dapp开发

DApp是Decentralized Application的缩写,译为:分散式的应用程序.App我们都知道,我们在智能手机上安装的应用程序也就是App.而DApp比App多了一个'D','D'的意思是分散式的.意思是 分散式的应用程序/去中心化的应用程序.与传统的App最大的区别是:DApp运行在去中心化的网络上,也就是区块链网络中.这里的DApp开发用以太坊智能合约为例,智能合约是记录在链上的一段能够控制链行为事件的一段协议,如:合约下关联账户转币.查账.投票.购买等等,合约里涉及的变量.常

如何开发一款以太坊(安卓)钱包系列1 - 通过助记词创建账号

上周我开源了一款钱包,反映很好,一周时间不到已经快到100 Star.接下来我会几篇系列文章把开发以太坊钱包的核心要点写出来,也算是对代码的一个解读. 写在前面 钱包是使用Android安卓平台编写,使用的是原生代码Java 语言编写, 是基于Java 1.8 版本,也使用了Java 1.8 中一些较新的语言特性,如 Lambda表达式等:另外还较多使用了ReactiveX/RxAndroid响应式编程用法. 在本系列文章中,重点是介绍以太坊钱包账号.交易等逻辑,有时可能会假定读者已经了解And

基于Ubuntu系统搭建以太坊go-ethereum源码的开发环境

第一.先安装geth的CLI环境sudo apt-get install geth,这个很重要 第二.下载源代码 git clone https://github.com/ethereum/go-ethereum 我下载到啦 /home/siegel/ethereum/ 更改文件夹权限为可写  chmod -R 777 /home/siegel/ethereum/go-ethereum 第三步 sudo apt-get install software-properties-common sud

如何从零开始学习区块链技术——推荐从以太坊开发DApp开始

很多人迷惑于区块链和以太坊,不知如何学习,本文简单说了一下学习的一些方法和资源. 一. 以太坊和区块链的关系 从区块链历史上来说,先诞生了比特币,当时并没有区块链这个技术和名词,然后业界从比特币中提取了技术架构和体系,称之为区块链技术.从比特币提取的区块链技术称之为区块链1.0时代,那个时候的应用主要以电子货币和去中心化交易为主,比如各种山寨币.而以太坊将区块链带入了2.0的时代,区块链2.0不是推翻了1.0,而是在1.0的基础上实现了区块知晓.价值知晓.图灵完备,并进行了细节优化,从而形成了以

以太坊智能合约项目-Token合约开发与部署

修订日期 姓名 邮箱 2019-09-05 brucefeng [email protected] 一. 钱包环境安装 以太坊钱包顾名思义,就是管理以太坊地址,存储以太坊Token的工具,再简单点说,任何区块链网络都需要我们有自己的账户,管理账户的软件可称之为钱包,无论是炒币的还是研究以太坊开发的,钱包都是必不可少的. 1.钱包分类 1.1 Mist 说到以太坊钱包,第一个要说的当然就是Ethereum官方钱包+浏览器 Mist.Mist是一个全节点钱包(全节点钱包通俗的来说就是同步了全部的以太