trufflesuite/truffle-hdwallet-provider
https://github.com/trufflesuite/truffle-hdwallet-provider/blob/master/index.js
实现代码
truffle-hdwallet-provider/index.js
const bip39 = require("bip39"); const ethJSWallet = require('ethereumjs-wallet'); const hdkey = require('ethereumjs-wallet/hdkey'); const debug = require('debug')('truffle-hdwallet-provider') const ProviderEngine = require("web3-provider-engine"); const FiltersSubprovider = require('web3-provider-engine/subproviders/filters.js'); const NonceSubProvider = require('web3-provider-engine/subproviders/nonce-tracker.js'); const HookedSubprovider = require('web3-provider-engine/subproviders/hooked-wallet.js'); const ProviderSubprovider = require("web3-provider-engine/subproviders/provider.js"); const Web3 = require("web3"); const Transaction = require('ethereumjs-tx'); const ethUtil = require('ethereumjs-util'); // This line shares nonce state across multiple provider instances. Necessary // because within truffle the wallet is repeatedly newed if it's declared in the config within a // function, resetting nonce from tx to tx. An instance can opt out // of this behavior by passing `shareNonce=false` to the constructor. // See issue #65 for more const singletonNonceSubProvider = new NonceSubProvider(); function HDWalletProvider( mnemonic, provider, address_index=0,//从给的mnemonic数组的第几下标开始取 num_addresses=1,//取里面的几个mnemonic,即生成几个address shareNonce=true, //共享nonce状态,这样钱包进行刷新时nonce值还是原来的值 wallet_hdpath="m/44'/60'/0'/0/"//指明到第四层,即上面的操作是要在该账户的外链上生成num_addresses个公钥 ) { if (mnemonic && mnemonic.indexOf(' ') === -1 || Array.isArray(mnemonic)) {//传入的是privateKey,一个或多个(数组) const privateKeys = Array.isArray(mnemonic) ? mnemonic : [mnemonic];//如果是一个也要将其转成数组形式 this.wallets = {}; this.addresses = []; for (let i = address_index; i < address_index + num_addresses; i++){ const privateKey = Buffer.from(privateKeys[i].replace('0x', ''), 'hex');//将privateKey转成Buffer格式 if (ethUtil.isValidPrivate(privateKey)) {//查看privateKey是否正确 const wallet = ethJSWallet.fromPrivateKey(privateKey);//正确则能从主私钥得到wallet const address = wallet.getAddressString();//得到wallet的address this.addresses.push(address);//并记录到wallets和addresses中 this.wallets[address] = wallet; } } } else {//否则传入的就是一个mnemonic this.mnemonic = mnemonic; this.hdwallet = hdkey.fromMasterSeed(bip39.mnemonicToSeed(mnemonic));//根据mnemonic生成hdwallet this.wallet_hdpath = wallet_hdpath; this.wallets = {}; this.addresses = []; if (!bip39.validateMnemonic(mnemonic)) {//如果mnemonic不对则报错 throw new Error("Mnemonic invalid or undefined") } for (let i = address_index; i < address_index + num_addresses; i++){ const wallet = this.hdwallet.derivePath(this.wallet_hdpath + i).getWallet();// getWallet()-return a Wallet instance,这样这个m/44'/60'/0'/0/i就能够使用Wallet的API了 const addr = '0x' + wallet.getAddress().toString('hex');//得到m/44'/60'/0'/0/i的address this.addresses.push(addr);//并记录下来 this.wallets[addr] = wallet; } } //得到上面根据privateKey和mnemonic生成的账户address和wallet const tmp_accounts = this.addresses; const tmp_wallets = this.wallets; this.engine = new ProviderEngine(); this.engine.addProvider(new HookedSubprovider({ //先将HookedSubprovider添加进engine,并定义能够进行的rpc操作 getAccounts: function(cb) { cb(null, tmp_accounts) }, getPrivateKey: function(address, cb) { if (!tmp_wallets[address]) { return cb('Account not found'); } else { cb(null, tmp_wallets[address].getPrivateKey().toString('hex')); } }, signTransaction: function(txParams, cb) { let pkey; const from = txParams.from.toLowerCase() if (tmp_wallets[from]) { pkey = tmp_wallets[from].getPrivateKey(); } else { cb('Account not found'); } const tx = new Transaction(txParams); tx.sign(pkey); const rawTx = '0x' + tx.serialize().toString('hex'); cb(null, rawTx); }, signMessage(message, cb) { const dataIfExists = message.data; if (!dataIfExists) { cb('No data to sign'); } if (!tmp_wallets[message.from]) { cb('Account not found'); } let pkey = tmp_wallets[message.from].getPrivateKey(); const dataBuff = ethUtil.toBuffer(dataIfExists); const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff); const sig = ethUtil.ecsign(msgHashBuff, pkey); const rpcSig = ethUtil.toRpcSig(sig.v, sig.r, sig.s); cb(null, rpcSig); } })); (!shareNonce)//NonceSubProvider在HookedSubprovider和ProviderSubprovider之间被添加,以免多个钱包处理同一类数字资产的情况下同时生成交易时会导致nonce值相同造成冲突 ? this.engine.addProvider(new NonceSubProvider())//如果shareNonce = false,不共享nonce值,则添加的NonceSubProvider为局部对象 : this.engine.addProvider(singletonNonceSubProvider);////如果shareNonce = true,共享nonce值,则添加的singletonNonceSubProvider为全局常量 this.engine.addProvider(new FiltersSubprovider());//添加FiltersSubprovider if (typeof provider === 'string') {//添加ProviderSubprovider this.engine.addProvider(new ProviderSubprovider(new Web3.providers.HttpProvider(provider))); } else { this.engine.addProvider(new ProviderSubprovider(provider)); } this.engine.start(); // Required by the provider engine. }; HDWalletProvider.prototype.sendAsync = function() { this.engine.sendAsync.apply(this.engine, arguments); }; HDWalletProvider.prototype.send = function() { return this.engine.send.apply(this.engine, arguments); }; // returns the address of the given address_index, first checking the cache HDWalletProvider.prototype.getAddress = function(idx) { debug('getting addresses', this.addresses[0], idx) if (!idx) { return this.addresses[0]; } else { return this.addresses[idx]; } } // returns the addresses cache HDWalletProvider.prototype.getAddresses = function() { return this.addresses; } module.exports = HDWalletProvider;
使用:
安装:
npm install truffle-hdwallet-provider //+ truffle-hdwallet-provider@0.0.6
var HDWalletProvider = require("truffle-hdwallet-provider"); var Web3 = require("web3"); var web3 = new Web3(); const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; var provider = new HDWalletProvider(mnemonic, "http://localhost:8202", 0, 1); console.log(provider.getAddresses()); web3.setProvider(provider); web3.eth.getBlockNumber((err, number) => { console.log(number); }); // At termination, `provider.engine.stop()' should be called to finish the process elegantly. provider.engine.stop();//一定要记得添加 //不能运行下面的内容,因为Error: Web3ProviderEngine does not support synchronous requests. // web3.eth.coinbase((err, coinbase) => { // console.log(coinbase); // }); // web3.eth.mining((err, state) => { // console.log(state); // });
返回:
就成功生成了m/44'/60'/0'/0/0的address
userdeMacBook-Pro:test-hd-wallet user$ node index.js [ '0x627306090abab3a6e1400e9345bc60c78a8bef57' ] 301
如果改成:
var provider = new HDWalletProvider(mnemonic, "http://localhost:8202", 0, 5);
则为:
userdeMacBook-Pro:test-hd-wallet user$ node index.js [ '0x627306090abab3a6e1400e9345bc60c78a8bef57', '0xf17f52151ebef6c7334fad080c5704d77216b732', '0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef', '0x821aea9a577a9b44299b9c15c88cf3087f3b5544', '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2' ] 301
在geth中运行sendTransaction后:
> web3.eth.sendTransaction({from:eth.accounts[0],to:'0x627306090abab3a6e1400e9345bc60c78a8bef57',value:web3.toWei(4,'ether')}) "0xd45b223fa40b36b214f178bb2c343d8a9cc7c94a7bbfacd8d69efd297f346786"
再查看账户余额:
var addresses = provider.getAddresses(); console.log(addresses); web3.setProvider(provider); web3.eth.getBlockNumber((err, number) => { console.log(number); }); web3.eth.getBalance(addresses[0],(err,balance) => { if(!err){ console.log(balance.toString()); } });
返回:
userdeMacBook-Pro:test-hd-wallet user$ node index.js [ '0x627306090abab3a6e1400e9345bc60c78a8bef57', '0xf17f52151ebef6c7334fad080c5704d77216b732', '0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef', '0x821aea9a577a9b44299b9c15c88cf3087f3b5544', '0x0d1d4e623d10f9fba5db95830f7d3839406c6af2' ] 4000000000000000000 328
接下来部署一个合约看看:
var HDWalletProvider = require("truffle-hdwallet-provider"); var Web3 = require("web3"); const fs = require("fs"); const solc = require("solc"); var web3 = new Web3(); const mnemonic = 'candy maple cake sugar pudding cream honey rich smooth crumble sweet treat'; var provider = new HDWalletProvider(mnemonic, "http://localhost:8202", 0, 5); var addresses = provider.getAddresses(); // console.log(addresses); web3.setProvider(provider); web3.eth.getBlockNumber((err, number) => { console.log(number); }); web3.eth.getBalance(addresses[0],(err,balance) => { if(!err){ console.log(balance.toString()); } }); let source = fs.readFileSync("./Adoption.sol",'utf8');//读取sol智能合约文件 //对智能合约进行编译,第二个参数设置为1可以激活优化器optimiser let compiledContract = solc.compile(source,1); var bytecode,abi; for (let contractName in compiledContract.contracts) { bytecode = compiledContract.contracts[contractName].bytecode;//获得编译后合约的bytecode console.log(bytecode) abi = JSON.parse(compiledContract.contracts[contractName].interface); //获得编译后合约的abi并写成json形式 console.log(abi) } let MyContract = web3.eth.contract(abi); var instance = MyContract.new({from:addresses[0],data:'0x'+bytecode,gas:30000000},function(e,contract){ if(typeof contract.address !== 'undefined'){ console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash); }else{ console.log(e); } }); setTimeout(()=>{ web3.eth.getBalance(addresses[0],(err,balance) => { if(!err){ console.log(balance.toString()); } }) },3000);
部署完之后就可以直接使用该合约:
var instance = MyContract.at('0xF12b5dd4EAD5F743C6BaA640B0216200e89B60Da'); instance.adopt(1,{from:addresses[0],gas:3000000},(err,result) => { if(!err){ console.log(result); }else{ console.log(err); } });
然后再调用:
instance.getAdopters({gas:3000000},(err,result) =>{ if(!err){ console.log(result); }else{ console.log(err); } });
可以得到结果:
[ '0x627306090abab3a6e1400e9345bc60c78a8bef57', '0x627306090abab3a6e1400e9345bc60c78a8bef57', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000', '0x0000000000000000000000000000000000000000' ]