Nodejs实现以太坊钱包

一、创建项目

zhangqiangdeMac-mini:~ zhqmac$ mkdir myWallet

zhangqiangdeMac-mini:~ zhqmac$ cd myWallet

zhangqiangdeMac-mini:myWallet zhqmac$ npm init

将下面的依赖添加到生成的package.json文件中

 "dependencies": {
    "bignumber.js": "^7.2.1",
    "ejs": "^2.6.1",
    "koa": "^2.5.2",
    "koa-body": "^4.0.4",
    "koa-bodyparser": "^4.2.1",
    "koa-router": "^7.4.0",
    "koa-static": "^5.0.0",
    "koa-views": "^6.1.4",
    "web3": "^1.0.0-beta.34"
  }
  

安装依赖

npm install

二、初始化项目

  1. 新建文件index.js,项目入口
  2. 新建目录controller,封装请求处理
    • account.js 与账户相关的处理
    • createAccount.js创建账户处理
    • transaction.js 与交易相关的处理
  3. 新建目录model,模型封装 * responsedata.js,封装应答响应对象
  4. 新建目录routers,路由处理
    • router.js
  5. 新建目录utils,工具
    • myUtils.js

三、响应对象

responsedata.js

function response(code, msg, data) {
    this.code = code;//状态码
    this.msg = msg;//状态描述
    this.data = data;//返回的数据
}
module.exports = response;

四、开启服务

index.js

const Koa = require("koa");
const app = new Koa();
const koaBody = require("koa-body");
const router = require("./routers/router");
app.use(async (ctx, next) => {
    console.log(`Process ${ctx.request.method} ${ctx.request.url} ...`);
    await next();
});
//解析post请求
app.use(koaBody({
    multipart:true,
}));
app.use(router.routes());
app.listen("3000");
console.log("服务已开启,监听端口:3000");

五、设置路由

router.js

const router = require("koa-router")();
const createAccountController = require("../controller/createAccount");
const accountController = require("../controller/account");
const transactionController = require("../controller/transaction");
//创建账号
router.post("/createaccount",createAccountController.createAccount);
//私钥解锁账户
router.post("/unlockaccountwithprivatekey",accountController.unlockAccountWithPrivateKey);
//keystore解锁账户
router.post("/unlockaccountwithkeystore",accountController.unlockAccountWithKeyStore);
//发起交易
router.post("/createtransaction",transactionController.createTransaction);
//确认交易
router.post("/sendtransaction",transactionController.sendTransaction);
//交易详情
router.post("/transactionstatus",transactionController.transactionStatus);
module.exports = router;

项目中所有请求都采用post的方式

六、实例化web3

myUtils.js

const Web3 = require("web3");
var fs = require("fs");
getWeb3 = ()=>{
    //初始化web3访问节点为私有链节点
    const web3 = new Web3(Web3.givenProvider||"http://localhost:8545");
    //以太坊测试链
    // const web3 = new Web3(Web3.givenProvider || "https://kovan.infura.io/v3/4abf7f8865064ed6b99ca3fdac820921");
    return web3;
};
module.exports={
    getWeb3,
};

调用getWeb3()获取web3对象

var web3 = new Web3(Web3.givenProvider||"http://localhost:8545");
  • 第一次调用getWeb3()方法时Web3.givenProvider为空,所以需要设置一个种子节点。
  • http://localhost:8545为本地私有链地址,怎么搭建私链
  • https://kovan.infura.io/v3/4abf7f8865064ed6b99ca3fdac820921是以太网kovan测试链地址

获取测试链地址

打开 INFURA https://infura.io/dashboard 注册,登录 登录成功后来到下面页面

可以看到主网和其他三个测试链,按需选择ENDPOINT下的地址就是我们所需要的地址

七、请求处理

<font color=red>在执行下面操作前需开启一个私链或者测试链</font>

1、创建账户

createAccount.js

const web3 = require("../utils/myUtils").getWeb3();
const path = require("path");
const fs = require("fs");
var respones = require("../model/responsedata");
//创建钱包
createAccount = async ctx => {
    var responseData = new respones(0,"success",{});
    // console.log(ctx.request.body);
    var body = ctx.request.body;
    //创建钱包
    var account = web3.eth.accounts.create();
    //生成keyStore文件
    //keyStore是将私钥与用户密码拼接,将拼接结果对称加密得到
    var keyStoreJson = account.encrypt(body.pwd);
    //保存keyStore
    //写入文件的keyStore数据
    // var keyStoreStr = JSON.stringify(keyStoreJson);
    // //keyStore文件名
    // var keyStoreFileName = "UTC--"+new Date().toISOString()+"--"+account.address;
    // //文件保存路径
    // var keyStoreFilePath = path.join(__dirname,"../static/keystore",keyStoreFileName);
    // await fs.writeFile(keyStoreFilePath,keyStoreStr,()=>{});
    // var responseData = new respones(0,"success",{
    //     "downloadUrl":"/keystore/"+keyStoreFileName,
    //     "privateKey":account.privateKey
    // });
    responseData.data.keyStore = keyStoreJson;
    ctx.body = responseData;
};
module.exports={
    createAccount
};

keystore文件在客户端自己保存,服务端不保存

2、账号操作

account.js


const web3 = require("../utils/myUtils").getWeb3();
const myUtil = require("../utils/myUtils");
var response = require("../model/responsedata");
setResponseDataWithAccount = async(account, responseData)=> {
    responseData.data.address = account.address;
    responseData.data.privateKey = account.privateKey;
    responseData.data.balance = await getBalanceWithAddress(account.address);
    return responseData;
};

getBalanceWithAddress = async(address)=> {
    console.log("account:" + address);
    var balance = await web3.eth.getBalance(address);
    return web3.utils.fromWei(balance, "ether");
};
//使用私钥解锁账户
unlockAccountWithPrivateKey = async(ctx)=> {
    var responseData = new response(0, "success", {});
    var body = ctx.request.body;
    var privateKey = body.privateKey;
    console.log("privateKey:" + privateKey);
    var account = web3.eth.accounts.privateKeyToAccount(privateKey);
    // ctx.body = {name:"解锁"};
    var data = await setResponseDataWithAccount(account, responseData);
    console.log(data);
    ctx.body = data;
};
//使用keyStore文件
unlockAccountWithKeyStore = async(ctx)=> {
    var responseData = new response(0, "success", {});
    var body = ctx.request.body;
    var pwd = body.pwd;
    var keyStore = body.keyStore;
    try {
        var account = web3.eth.accounts.decrypt(keyStore, pwd);
        ctx.body = await setResponseDataWithAccount(account, responseData);
    }catch (error){
        console.log(error)
        responseData.code = 1;
        responseData.message = "failed";
        responseData.data = {error: error.message};
        ctx.body = responseData;
    }
};
module.exports = {
    unlockAccountWithPrivateKey,
    unlockAccountWithKeyStore,
};

3、交易处理

transaction.js

const web3 = require("../utils/myUtils").getWeb3();
var response = require("../model/responsedata");
var bignumber = require("bignumber.js")

//发起交易
createTransaction = async(ctx)=> {
    console.log("createTransaction");
    var responseData = new response(0, "success", {});
    var body = ctx.request.body;
    var fromAddress = body.from;
    var toAddress = body.to;
    //将输入的金额换算成Wei
    var money = web3.utils.toWei(body.money, "ether");
    var gasPrice = await web3.eth.getGasPrice();
    //获取交易的nonce值,一个顺序累加的值
    var nonce = await web3.eth.getTransactionCount(fromAddress);
    var transactionData = {
        from: fromAddress,
        to: toAddress,
        value: money,
        gasPrice: gasPrice,
        data: '0x00',//当使用代币转账或者合约调用时
        nonce: nonce,
    };
    //estimateGas()方法会将transactionData数据做一些操作,导致,transactionData一些值的类型变化,所以下面对transactionData重新赋值
    var gas = await web3.eth.estimateGas(transactionData);
    transactionData = {
        from: fromAddress,
        to: toAddress,
        value: money,
        gasPrice: gasPrice,
        data: '0x00',//当使用代币转账或者合约调用时
        nonce: nonce,
        gas:gas,
    };
    console.log(transactionData);
    responseData.data = transactionData;
    ctx.body = responseData;

};

创建一笔交易,返回交易的gasgasPrice供用户确认

//确认交易
/*
* 1、签名交易
* 2、发送交易
* */
sendTransaction = async(ctx)=> {

    console.log("sendTransaction");

    var responseData = new response(0, "success", {});
    var body = ctx.request.body;
    var transactionData = {
        from: body.from,
        to: body.to,
        value: body.value,
        gasPrice: body.gasPrice,
        data: body.data,//当使用代币转账或者合约调用时
        nonce: body.nonce,
        gas: body.gas
    };
    //privateKey为了安全,需要进行加密处理
    var privateKey = body.privateKey;
    //签名交易
    console.log(transactionData);
    console.log(privateKey);
    var signTransactionData = await web3.eth.accounts.signTransaction(transactionData, privateKey);
    try {
        //发送交易
        await web3.eth.sendSignedTransaction(signTransactionData.rawTransaction, (error, hash)=> {
            if (!error) {
                responseData.data.hash = hash;
            } else {
                responseData.code = 1;
                responseData.message = "failed";
                responseData.data = {error: error.message};
            }
        })
    } catch (error) {
        console.log(error);
        responseData.code = 1;
        responseData.message = "failed";
        responseData.data = {error: error.message};
    }
    ctx.body = responseData
};

确认一笔交易,这里才是真正提交一笔交易,这时不会直接返回结果,交易提交后,如果连接的是测试链,需要自己去挖矿,执行命令miner.start(),直到矿工确认这笔交易后,才会返回交易信息。如果是测试链,因为它有自己的生态,不需要做其他操作。

//根据txHash查询交易状态
transactionStatus = async(ctx)=> {
  var responseData = new response(0,"success",{});
    var data = ctx.request.body;
    var txHash = data.txHash;
    var result = await web3.eth.getTransactionReceipt(txHash);
    if (result != null){
        responseData.data = result;
    }
    ctx.body = responseData;
};
module.exports = {
    createTransaction,
    sendTransaction,
    transactionStatus,
};

根据交易hash,返回

注 :createTransaction方法中调用estimateGas()方法会将transactionData数据做一些操作,导致,transactionData一些值的类型变化,所以下面对transactionData重新赋值

demo下载 https://github.com/sweetMegan/myWalletDemo

myWallet为nodejs后台

myWalletClient为iOS前端

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦