以太坊HD錢包開發 三 —— 程式碼實現
阿新 • • 發佈:2019-01-09
以太坊HD錢包開發 一 —— 錢包概念介紹
https://blog.csdn.net/bondsui/article/details/85780452
以太坊HD錢包開發 二 —— BIP協議介紹
https://blog.csdn.net/bondsui/article/details/85780675
以太坊HD錢包開發 三 —— 程式碼實現
https://blog.csdn.net/bondsui/article/details/85780940
文章目錄
github原始碼在結尾
7、web錢包
1、建立工程
create-react-app webwallet
匯入引用的包 bip、ethers、file-saver、pubsub、semantic
"dependencies": {
"bip39": "^2.5.0",
"ethers" : "^4.0.20",
"file-saver": "^2.0.0",
"json-format": "^1.0.1",
"pubsub-js": "^1.7.0",
"react": "^16.7.0",
"react-dom": "^16.7.0",
"react-scripts": "2.1.2",
"semantic-ui-css": "^2.4.1",
"semantic-ui-react": "^0.84.0"
}
建立目錄
➜ src git:(master) ✗ tree
├── App.css
├── App.js
├── service
│ └── service.js
├── serviceWorker.js
├── utils
└── view
├── login
│ ├── login.js
│ ├── tab_keystore.js
│ ├── tab_mnemonic.js
│ └── tab_private.js
└── wallet
├── tab_account.js
├── tab_settings.js
├── tab_transaction.js
└── wallet.js
2、首頁
首頁引入PubSub.js,事件釋出訂閱,如果未登入,顯示匯入頁面,否則顯示錢包頁面
程式碼
import React, {Component} from 'react';
import PubSub from "pubsub-js";
import './App.css';
import LoginForm from './view/login/login'
import Wallet from './view/wallet/wallet'
import {Container} from "semantic-ui-react";
class App extends Component {
state = {
login: false,
loading: false,
loginEvent: '',
wallets: []
}
// 頁面載入,訂閱訊息,儲存訂閱id
componentWillMount() {
let loginEvent = PubSub.subscribe("onLoginSucc", this.onLoginSucc)
this.setState({loginEvent})
}
onLoginSucc = (msg, data) => {
console.log("登陸成功")
console.log(data)
this.setState({
login: true,
wallets: data
})
}
// 頁面解除安裝 取消訊息訂閱
componentWillUnmount() {
PubSub.unsubscribe(this.state.loginEvent)
}
render() {
let {login} = this.state
let content = login ? <Wallet wallets={this.state.wallets}/> : <LoginForm/>
return (
<Container>
{content}
</Container>
);
}
}
export default App;
3、登陸頁面
3.1 登入首頁
登陸頁面為3個tab,點選切換匯入方式
登陸成功後釋出訊息
login.js
import React,{Component} from 'react'
import {Grid,Header, Image, Tab} from 'semantic-ui-react'
import PrivateLogin from "./tab_private"
import MmicLogin from "./tab_mnemonic"
import KeyStoreLogin from "./tab_keystore"
const panes = [
{menuItem: '私鑰', render: () => <Tab.Pane attached={false}><PrivateLogin/></Tab.Pane>},
{menuItem: '助記詞', render: () => <Tab.Pane attached={false}><MmicLogin/></Tab.Pane>},
{menuItem: 'KeyStore', render: () => <Tab.Pane attached={false}><KeyStoreLogin/></Tab.Pane>},
]
export default class Login extends Component {
render() {
return (
<Grid textAlign='center' verticalAlign='middle'>
<Grid.Column style={{maxWidth: 450, marginTop: 100}}>
<Header as='h2' color='teal' textAlign='center'>
<Image src='images/logo.png'/> EHT錢包
</Header>
<Tab menu={{text: true}} panes={panes} style={{maxWidth: 450}}/>
</Grid.Column>
</Grid>
)
}
}
3.2 私鑰新建或匯入
新建賬號,隨機一個私鑰,
對使用者輸入的私鑰進行校驗 service.checkPrivate(key)
登陸成功後釋出訊息
import {Button, Form, Segment} from 'semantic-ui-react'
import React, {Component} from 'react'
import PubSub from 'pubsub-js'
let service = require('../../service/service')
export default class PrivateLogin extends Component {
state = {
privateKey: "",
}
// 隨機建立
handleCreateClick = () => {
let privateKey = service.newRandomKey()
this.setState({privateKey})
}
// 處理輸入繫結
handleChange = (e, {name, value}) => {
this.setState({[name]: value})
}
// 私鑰登陸
onPrivateLoginClick = () => {
let key = this.state.privateKey
let err = service.checkPrivate(key)
if (err !== "") {
alert(err)
return;
}
if (key.substring(0, 2).toLowerCase() !== '0x') {
key = '0x' + key;
}
console.log("開始建立錢包", key)
let wallets = service.newWalletFromPrivateKey(key)
if (wallets) {
PubSub.publish("onLoginSucc", wallets)
} else {
alert("匯入出錯")
}
}
render() {
return (
<Form size='large'>
<Segment>
<Form.Input
fluid icon='lock' iconPosition='left'
placeholder='private key'
name="privateKey"
value={this.state.privateKey}
onChange={this.handleChange}/>
<a href='#' onClick={this.handleCreateClick}>隨機生成</a>
<br/>
<br/>
<Button
color='teal' fluid size='large'
onClick={this.onPrivateLoginClick}>
私鑰匯入
</Button>
</Segment>
</Form>
)
}
}
// 私鑰校驗
function checkPrivate(key) {
if (key === '') {
return "不能為空"
}
if (key.length != 66 && key.length != 64) {
return false, "祕鑰長度必須為66或者64"
}
if (!key.match(/^(0x)?([0-9A-fa-f]{64})+$/)) {
return "祕鑰為16進製表示[0-9A-fa-f]"
}
return ""
}
// 隨機私鑰
function newRandomKey() {
let randomByte = ethers.utils.randomBytes(32)
let randomNumber = ethers.utils.bigNumberify(randomByte);
return randomNumber.toHexString()
}
// 通過私鑰建立錢包
function newWalletFromPrivateKey(privateKey) {
let wallets = []
let wallet = new ethers.Wallet(privateKey)
wallets.push(wallet)
return wallets
}
3.3 助記詞新建或匯入
助記詞匯入執行bip44協議,定義路徑,預設**“m/44’/60’/0’/0/0”**,也可以進行多賬號匯入,修改index值即可
import {Button, Loader,Form, Grid, Header, Image, Message, Segment} from 'semantic-ui-react'
import PubSub from 'pubsub-js'
import React, {Component} from 'react'
let service = require('../../service/service')
// disorder timber among submit tell early claw certain sadness embark neck salad
export default class MmicLogin extends Component {
state = {
privateKey: "",
mmic: "",
pwd: "",
path: "m/44'/60'/0'/0/0",
}
// 處理輸入文字繫結
handleChange = (e, {name, value}) => {
this.setState({[name]: value})
}
// 生成助記詞
handleGenMicc = () => {
let mmic = service.genMmic()
this.setState({mmic})
}
// 助記詞匯入
onMMICClick = () => {
let {mmic, path} = this.state
let wallets = service.newWalletFromMmic(mmic, path)
PubSub.publish("onLoginSucc", wallets)
}
render() {
return (
<Form size='large' onSubmit={this.onMMICClick}>
<Segment stacked>
<Form.TextArea
placeholder='12 words'
name="mmic"
value={this.state.mmic}
onChange={this.handleChange}/>
<Form.Input
fluid
icon='user'
iconPosition='left'
type='path'
value={this.state.path}
onChange={this.handleChange}
/>
<a onClick={this.handleGenMicc}>隨機生成</a>
<br/>
<br/>
{/*<Form.Input*/}
{/*fluid*/}
{/*icon='lock'*/}
{/*iconPosition='left'*/}
{/*placeholder='Password'*/}
{/*type='password'*/}
{/*value={this.state.pwd}*/}
{/*onChange={this.handleChange}*/}
{/*/>*/}
<Form.Button color='teal' fluid size='large'>
助記詞匯入
</Form.Button>
</Segment>
</Form>
)
}
}
// 生成助記詞
function genMmic() {
let words = ethers.utils.HDNode.entropyToMnemonic(ethers.utils.randomBytes(16));
return words
}
// 通過助記詞建立錢包
function newWalletFromMmic(mmic, path) {
let wallets = []
for (let i = 0; i < 10; i++) {
path = PATH_PREFIX + i
let wallet = ethers.Wallet.fromMnemonic(mmic, path)
wallets.push(wallet)
console.log(i, wallets)
}
return wallets
}
3.4 keystore匯入
wallet 需要連線provider 才可以使用
wallet balance為Object型別,金額需要手工轉換(ethers.utils)
匯出需要密碼,
import {Button, Form, Grid, Header, Image, Loader, Message, Segment} from 'semantic-ui-react'
import PubSub from 'pubsub-js'
import _ethets2 from "ethers"
import React, {Component} from 'react'
let service = require('../../service/service')
export default class KeyStoreLogin extends Component {
state = {
keyStore: "",
pwd: '',
loading:false
}
handleChange = (e, {name, value}) => {
this.setState({[name]: value})
}
// 處理匯入
handleKeyImport = () => {
let {keyStore, pwd} = this.state
if (keyStore==""){
return
}
console.log(service.checkJsonWallet(keyStore))
this.setState({loading:true})
service.newWalletFromJson(keyStore, pwd).then(wallets => {
PubSub.publish("onLoginSucc", wallets)
this.setState({loading:false})
}).catch(e => {
console.log(e)
alert("匯入出錯" + e)
this.setState({loading:false})
})
}
onFileChooseClick = ()=>{
}
render() {
return (
<Form size='large'>
<Loader active={this.state.loading} inline />
<Segment>
<Form.TextArea
placeholder='keystore為json格式'
name="keyStore"
value={this.state.keyStore}
onChange={this.handleChange}/>
<Form.Input
fluid
icon='lock'
iconPosition='left'
placeholder='Password'
type='password'
name = "pwd"
value={this.state.pwd}
onChange={this.handleChange}
/>
<Button
color='teal' fluid size='large'
onClick={this.handleKeyImport}>
匯入
</Button>
</Segment>
</Form>
)
}
}
// 從keystore匯入錢包,需要密碼
function newWalletFromJson(json, pwd) {
return new Promise(async (resolve, reject) => {
try {
let wallets = []
let wallet = await ethers.Wallet.fromEncryptedJson(json, pwd, false)
wallets.push(wallet)
resolve(wallets)
} catch (e) {
reject(e)
}
})
}
通過keystorejson檔案校驗是否包含地址
// 校驗地址
function checkJsonWallet(data) {
return ethers.utils.getJsonWalletAddress(data)
}
4、錢包頁面
獲取錢包資訊需要連線以太環境,請提前確認開啟埠
錢包欄位資訊獲取為非同步,注意不可以直接