修订日期 | 姓名 | 邮箱 |
---|---|---|
2018-10-10 | brucefeng | [email protected] |
在之前的一篇文章以太坊智能合约项目-Token合约开发与部署中,我们提到了钱包了钱包的概念以及主流的几种钱包,如Mist,MyEtherWallet,MetaMask等,之前我们主要将钱包作为一个开发工具使用,用于智能合约的开发与调试工作,使用较多的是浏览器插件钱包MetaMask。
在本文中,我们主要介绍MyEtherWallet以及如何实现一个简易版的MyEtherWallet网页版应用,在本文中,我们能够学习到如下内容
目前主流的钱包平台已经有太多了,而且有很多已经做得比较完善了,所以我们本文的开发工作只是为了学习以太坊开发技术,并非去设计一个新的钱包软件,重复造轮子几乎没有任何价值。
MyEtherWallet 是一个轻钱包,无需下载,所有操作在直接在网页上就可以完成
主要功能如下
由于操作比较简单,这里不做详细讲解,在下文中我们对其主要功能,如新建钱包,以太币或者Token转账,查看交易状态进行参照开发。
Node.js是一个JS运行时环境,可以解析,执行JavaScript代码,在这个执行环境中,为JS提供了一些服务器级别的操作API,如文件读写,网络通信,Http服务器等,其使用事件驱动,非阻塞IO模型(异步),轻量高效,大多数与JS相关的包都放在npm上,通过命令就可以下载不同的库跟框架,无需在去各个模块的官网上面单独下载,如安装koa直接通过npm install koa
即可完成。
web3.js是一个库集合,允许使用HTTP或者RPC连接与本地或者远程以太坊节点进行交互,包含以太坊生态系统的特定功能,开发者利用web3模块主要连接以太坊的RPC层,从而与区块链进行交互
官方文档: 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
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()运行下游中间件,否则下游中间件不会被正常执行。可以采用两种不同的方法来实现中间件
$ 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端口,通过浏览器访问
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");
在实际开发中,返回给用户的网页都是通过模板文件进行返回的,可以让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");
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
由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("服务启动完毕");
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("服务启动完毕");
由于原生路由使用比较繁琐,所以可以通过封装好的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("服务启动完毕");
一般在如下情况下需要使用到重定向
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)进行编码
客户端在请求获取服务的数据时,获取的URL中通常会携带各种参数,服务端如何获取到get请求的参数呢?
http://127.0.0.1:3003/hello/brucefeng
获取方式: ctx.params.name
http://127.0.0.1:3003/bye?name=brucefeng
获取方式: ctx.query.name
调用params获取参数的时候,params不是request的属性,需要通过ctx直接调用获取。
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
加载静态资源,如图片,字体,样式表,脚本等,编码指定静态资源的路径是相对于./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 = " 看我"
})
//静态资源的路径是相对于./static的路径
app.use(static(path.join(__dirname,"./static")))
//将router路由注册到中间件
app.use(router.routes());
app.listen(3003);
console.log("服务启动完毕");
启动服务,通过浏览器访问测试
模板引擎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
Page Title
姓名: <%= name %> 性别: <%= sex %>
static/views/home.html
Page Title
姓名: <%= name %>
$ mkdir ETHWalletDemo ;cd ETHWalletDemo
$ npm init -y
$ npm i web3 koa koa-body koa-static koa-views koa-router ejs
$ mkdir router ; cd router
$ vim router.js
var router = require("koa-router")()
//定义路由newaccount
router.get("/newaccount",(ctx,next)=>{
ctx.response.body = "创建钱包"
})
module.exports = router
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进行测试")
ETHWalletDemo
下创建
$ mkdir models views controllers #创建MVC框架目录
$ mkdir utils #创建帮助文件目录
$ mkdir -p static/{css,js,html} #创建静态文件目录
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/...进行测试")
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;
}
}
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 +" 创建成功";
}
}
views/newaccount.html
创建钱包
创建一个新的钱包
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
$ mkdir static/keystore
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目录中
view/downloadkeystore.html
保存KeyStore文件
由于涉及到了onclick,所以,我们现在需要创建js代码实现相关方法
实现
saveKeystoreNext
方法
$ mkdir js/lib
将jquery.url.js
与jquery-3.3.1.min.js
拷贝进lib目录中
static/js/wallet.js
function saveKeystoreNext(){
//隐藏保存keystore页面
$("#save-keystore").hide()
//显示保存private页面
$("#save-privatekey").show()
}
此处的导航页前端用的是蓝鲸智云的MagicBox组件,作为蓝鲸智云的忠实粉丝,推荐大家使用
http://magicbox.bk.tencent.com/
static/html/nav.html
将nav.html集成到所有相关网页中即可,如newaccount.html
创建钱包
创建一个新的钱包
在通过钱包转账之前,我们都需要先对钱包账户进行解锁后才能进行正常交易,目前主要的解锁方式为
本文我们主要讲解如何开发通过私钥解锁跟配置文件解锁来进行账户解锁。
我们模仿MyEtherWallet的方式,将解锁钱包的功能放在转账交易模块下面,便于整合。
controllers/transaction.js
module.exports = {
transactionHtml: async (ctx) =>{
await ctx.render("transaction.html")
},
}
views/transaction.html
转账
发送以太币/Token
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
views/transaction.html
转账
发送以太币/Token
修改
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()
}
})
})
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 = "解锁成功";
}
}
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
$ cd ETHWalletDemo
$ $ node index.js
钱包启动成功,请访问http://127.0.0.1:3003/...进行测试
http://127.0.0.1:3003/
输入密码,此处先不隐藏,后期设计即可
点击保存Keystore文件,并点击Next Step
注意:此处的账号私钥不做隐藏,仅仅是为了测试需要,大家自己实际使用的私钥请务必保存妥当!!!
后端返回
前端返回
前后端调试成功
账户刚刚创建完毕,余额为0,我们可以通过私链转账的方式给新账户转入10以太币,具体操作可以参考博客:以太坊联盟链-多节点私链搭建手册中转账交易章节。
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
}
}
views/transaction.html
转账
发送以太币/Token
修改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()
}
})
})
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
})
}
}
views/transaction.html
转账
发送以太币/Token
修改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()
}
})
})
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
选择配置文件,输入密码解锁
验证成功
显示账户信息
至此,我们现在完成了私钥解锁账户以及配置文件解锁账户的功能了。
$ npm install ethereumjs-tx
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("交易失败")
}
})
}
}
views/transaction.html
转账
发送以太币/Token
修改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))
}
})
}
})
})
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
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
}
}
views/queryTransaction.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("查询失败")
}
})
}
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
执行账户解锁
执行转账交易
以太坊私有链终端产生交易,执行挖矿
显示交易Hash以及区块Hash
通过交易Hash返回交易详情
以上内容,我们实现了对以太币的转账与查询功能,而现实情况中,我们很多区块链公司都会开发适用于以太坊与自己Token的钱包,本文直接沿用以太坊智能合约项目-Token合约开发与部署中编写的合约代码。
通过Remix进行部署后获取地址:0xa77a5c71b9cf71e89215ceec9767c536e79ced68
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
}
}
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
}
}
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)
}
}
修改前端页面
转账
发送以太币/Token
修改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("查询失败")
}
})
}
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