1. 程式人生 > >以太坊HD錢包開發 三 —— 程式碼實現

以太坊HD錢包開發 三 —— 程式碼實現

以太坊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、錢包頁面

獲取錢包資訊需要連線以太環境,請提前確認開啟埠

錢包欄位資訊獲取為非同步,注意不可以直接