准备工作
项目创建
版本信息:
Node 16.14
安装vue-cli
1 | npm install -g @vue/cli |
验证vue-cli 安装
1 | $ vue -V |
通过vue-cli创建项目
1 | $ vue create <项目名称> |
选择自定义配置
1 | ? Check the features needed for your project: (Press <space> to select, <a> to |
选择vue版本
1 | ? Choose a version of Vue.js that you want to start the project with (Use arrow |
选择路由设置
1 | ? Use history mode for router? (Requires proper server setup for index fallback |
选择预处理语言
1 | ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported |
选择配置文件存放目录
1 | ? Where do you prefer placing config for Babel, ESLint, etc.? |
安装成功后 运行测试
1 | $ cd 项目名称 |
成功界面
第三方包安装
web3相关第三方包
1 | npm install web3 bip39 ethereumjs-tx@1.3.7 ethereumjs-util ethereumjs-wallet |
注: ethereumjs-tx 使用1.3.7 版本
node-polyfill 兼容文件配置
下载polyfill 插件
1
npm install node-polyfill-webpack-plugin -D
在vue.config.js 文件中配置插件
1
2
3
4
5
6
7
8
9
10
11const { defineConfig } = require('@vue/cli-service')
// 引入插件
++ const NodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
transpileDependencies: true,
++ configureWebpack: {
++ plugins: [
++ new NodePolyfillWebpackPlugin()
],
},
})
配置vant-ui ui组件库
https://vant-contrib.gitee.io/vant/#/zh-CN
- 安装
1 | $ npm i vant |
- 在vue.config.js 文件中配置插件
1 | const { defineConfig } = require("@vue/cli-service"); |
- 测试 在app.vue文件添加代码
1 | <template> |
通过vw配置响应式
https://www.cnblogs.com/hongrun/p/16130707.html
- 安装
1 | npm install postcss-px-to-viewport -D |
- 在根目录下创建 名为
postcss.config.js
文件
1 | module.exports = { |
web3连接到以太坊网络(测试网、主网)
- 什么是web3
web3是以太坊官方开提供的一个连接以太坊区块链的模块,允许您使用HTTP或IPC与本地或远程以太坊节点进行交互,它包含以太坊生态系统的几乎所有功能。web3模块主要连接以太坊暴露出来的RPC层。开发者利用web3连接RPC层,可以连接任何暴露了RPC接口的节点,从而与区块链交互。web3是一个集合库,支持多种开发语言使用wbe3,其中的JavaScript API叫做web3.js、另外还有web3.py、web3j,web3.js将是我们钱包开发项目的重点。
web3.eth:用于与以太坊区块链和智能合约之间的交互。
web3.utils:包含一些辅助方法。
web3.shh:用于协议进行通信的P2P和广播。
web3.bzz:用于与群网络交互的Bzz模块。
github地址:https://github.com/web3/web3.js/tree/v1.0.0-beta.34
web3.js开发文档:https://web3js.readthedocs.io/en/v1.8.1/
web3.js 中文文档 : https://learnblockchain.cn/docs/web3.js/
- 实例化web3对象
web3要与以坊节点进行交互,需要创建一个web3对象,下面看看如何创建。
1 | var Web3 = require('web3'); |
根据API可知需要指定节点地址,我们将ws://some.local-or-remote.node:8546换成其它连接到以太坊网络的节点的地址,以此来确定连接的以太坊的网络。那么连接到以太坊网络的节点的地址是多少呢?这里我们需要使用到infura。
- 获取连接到以太坊网络的节点地址
infura提供公开的 Ethereum主网和测试网络节点,到infura.io网站注册后即可获取各个网络的地址。请按照如下步骤获取地址。
第一步:打开 infura网站地址:https://infura.io/dashboard,使用邮箱注册后登陆
第二步:点击上图标记的“create new project”按钮创建一个新项目。然后弹出如下弹框,在输入框输入项目名,如”MyEtherWallet“,然后点击“create project”按钮创建。
第三步:然后会显示如下界面,点击下图中的选择框,可以看到提供主网、Kovan测试网络、Ropsten测试网络、GoerLi测试网络的节点地址。
第四步:选择GoerLi测试网络,然后复制地址,将获取到类似这样的地址:https://kovan.infura.io/v3/d93f......cd67,如下。
- 连接到以太坊GoerLi测试网络
现在将复制的地址替换掉实例化web对象的地址,如下
1 | var Web3 = require("web3") |
连接到以太坊主网与GoerLi测试网络一样的,只需复制主网节点的地址去实例化web3即可。由于在主网上交易需要花费gas,因此我们基于GoerLi测试网络进行开发,后续开发完成后可再切换到主网。
Web3js 高频 Api
账号创建
- 创建账号需要使用web3.js的如下API
API
1 | web3.eth.accounts.create([entropy]); |
参数:
entropy - String (可选): 它是一个可选项,是一个随机字符串,将作为解锁账号的密码。如果没有传递字符串,则使用random生成随机字符串。
返回值:
Object:包含以下字段的一个帐户对象:
address- string:帐户地址。
privateKey- string:帐户私钥。前端永远不应该在localstorage中以未加密的方式共享或存储!
signTransaction(tx [, callback])- Function:签名交易的方法。
sign(data)- Function:签名二进制交易的方法。
例子
1 | web3.eth.accounts.create('2435@#@#@±±±±!!!!678543213456764321§34567543213456785432134567'); |
1 | web3.eth.accounts.create('2435@#@#@±±±±!!!!678543213456764321§34567543213456785432134567'); |
- 获取地址
使用APIweb3.eth.accounts.create()
创建了新账户后生成了一个账户对象,在该对象中拥有addreds
属性,即账户的私钥。
1 | let account = web3.eth.accounts.create("123456") |
3.获取私钥
使用APIweb3.eth.accounts.create()
创建了新账户后生成了一个账户对象,在该对象中拥有privateKey
属性,即账户的私钥。
1 | let account = web3.eth.accounts.create() |
余额获取
根据地址获取以wei为单位余额
1 | web3.eth.getBalance(address).then((ret) => { |
单位转换
- Eth 转为 wei
1 | # Web3 或者实例后的web3对象都可以 |
- wei 转为Eth
1 | # Web3 或者实例后的web3对象都可以 |
Eth转账
API
1 | web3.eth.sendSignedTransaction(signedTransactionData [, callback]) |
参数
signedTransactionData
-String
:以HEX格式签名的交易数据。交易数据对象可以包含如下字段:
from
-String|Number
:发送帐户的地址。如果未指定,则使用web3.eth.defaultAccount属性。或web3.eth.accounts.wallet中本地钱包的地址。to
-String
:(可选)消息的目标地址,若未定义则为合同发送消息。value
-Number|String|BN|BigNumber
:(可选)为wei中的交易转移的数量,如果是合约发送消息,则是捐赠给合约地址。gas
-Number
:(可选,默认:待定)用于交易的gas(未使用的gas会退还)。gasPrice
-Number|String|BN|BigNumber
:(可选)此交易的gas价格,以wei为单位,默认为web3.eth.gasPrice。data
-String
:(可选)包含合同上函数调用数据的ABI字节字符串。nonce
-Number
:(可选)随机数的整数。
callback
-Function
:(可选)可选回调,将错误对象作为第一个参数返回,结果作为第二个参数返回。
返回
PromiEvent
:promise组合的事件,将在交易完成时调用。包含以下事件
"transactionHash"
返回String
:在发送事务并且事务哈希可用之后立即触发。"receipt"
返回Object
:在交易确认时触发。"confirmation"
返回Number
,Object
:每次确认都会被调用,直到第12次确认。接收确认编号作为第一个参数,将数据作为第二个参数。"error"
返回Error
:如果在发送过程中发生错误,则会触发。
构建转账参数
区块链转账和支付宝转账类似,需要
发送方
、接收方
、金额
、密码
另外需要添加部分区块链参数:
矿工费gas
、地址转账交易次数
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 获取账户交易次数
let nonce = await web3.eth.getTransactionCount(fromaddress);
// 获取预计转账gas费
let gasPrice = await web3.eth.getGasPrice();
// 转账金额以wei为单位
let balance = await web3.utils.toWei(number);
var rawTx = {
from: fromaddress,
nonce: nonce,
gasPrice: gasPrice,
to: toaddress,
value: balance,
data: "0x00", //转Token代币会用到的一个字段
};通过转账参数计算最终gas费用,并将通过私钥将转账参数进行编码加密
ethereumjs-tx 第三方库请选择1.3.7版本
1
2
3
4
5
6
7
8
9
10import Tx from "ethereumjs-tx";
// 将私钥去除“ox”后进行hex转化
var privateKey = new Buffer(privatekey.slice(2), "hex");
//需要将交易的数据进行预估gas计算,然后将gas值设置到数据参数中
let gas = await web3.eth.estimateGas(rawTx);
rawTx.gas = gas;
// 通过 ethereumjs-tx 实现私钥加密Ï
var tx = new Tx(rawTx);
tx.sign(privateKey);
var serializedTx = tx.serialize();通过
sendSignedTransaction
api发送转账交易,并且获取交易id
1 |
|
区块链浏览器或者目标钱包产看转账结果
goerli区块链浏览器 https://goerli.etherscan.io/tx/交易id
账户系统
简介
在前面的教程中我们对以太坊钱包已经有了一定的认识,上一章也重点介绍了账号地址的生成过程,在以太坊钱包中一个重点就是账户系统,在这个模块中很多初学同学不是很清楚密码、keystore、助记词与私钥它们之间的关系。下面我们来看看它们之间到底有着怎样的爱恨情仇,让大家琢磨不透。
密码
密码不是私钥,它是在创建账户时候的密码(可以修改)
密码在以下情况下会使用到:
- 作为转账的支付密码
- 用keystore导入钱包的时候需要输入的密码,用来解锁keystore的
私钥 Private Key
私钥由64位长度的十六进制的字符组成,比如:0xA4356E49C88C8B7AB370AF7D5C0C54F0261AAA006F6BDE09CD4745CF54E0115A
,一个账户只有一个私钥且不能修改。
通常一个钱包中私钥和公钥是成对出现的,有了私钥,我们就可以通过一定的算法生成公钥,再通过公钥经过一定的算法生成地址,这一过程都是不可逆的。私钥一定要妥善保管,若被泄漏别人可以通过私钥解锁账号转出你的该账号的数字货币。
公钥 Public Key
公钥(Public Key)是和私钥成对出现的,和私钥一起组成一个密钥对,保存在钱包中。公钥由私钥生成,但是无法通过公钥倒推得到私钥。公钥能够通过一系列算法运算得到钱包的地址,因此可以作为拥有这个钱包地址的凭证。
Keystore
Keystore常见于以太坊钱包,它是将私钥以加密的方式保存为一份 JSON 文件,这份 JSON 文件就是 keystore,所以它就是加密后的私钥。Keystore必须配合钱包密码才能导入并使用该账号。当黑客盗取 Keystore 后,在没有密码情况下, 有可能通过暴力破解 Keystore 密码解开 Keystore,所以建议使用者在设置密码时稍微复杂些,比如带上特殊字符,至少 8 位以上,并安全存储。
助记词 Mnemonic
私钥是64位长度的十六进制的字符,不利于记录且容易记错,所以用算法将一串随机数转化为了一串12 ~ 24个容易记住的单词,方便保存记录。注意:
- 助记词是私钥的另一种表现形式
- 助记词=私钥,这是不正确的说法,通过助记词可以获取相关联的多个私钥,但是通过其中一个私钥是不能获取助记词的,因此助记词≠私钥。
BIP
要弄清楚助记词与私钥的关系,得清楚BIP协议,是Bitcoin Improvement Proposals
的缩写,意思是Bitcoin 的改进建议,用于提出 Bitcoin 的新功能或改进措施。BIP协议衍生了很多的版本,主要有BIP32、BIP39、BIP44。
BIP32
BIP32是 HD钱包的核心提案,通过种子来生成主私钥,然后派生海量的子私钥和地址,种子是一串很长的随机数。
BIP39
由于种子是一串很长的随机数,不利于记录,所以我们用算法将种子转化为一串12 ~ 24个的单词,方便保存记录,这就是BIP39,它扩展了 HD钱包种子的生成算法。
BIP44
BIP44 是在 BIP32 和 BIP43 的基础上增加多币种,提出的层次结构非常全面,它允许处理多个币种,多个帐户,每个帐户有数百万个地址。
在BIP32路径中定义以下5个级别:
1 | m/purpse'/coin_type'/account'/change/address_index |
- purpose:在BIP43之后建议将常数设置为44’。表示根据BIP44规范使用该节点的子树。
- Coin_type:币种,代表一个主节点(种子)可用于无限数量的独立加密币,如比特币,Litecoin或Namecoin。此级别为每个加密币创建一个单独的子树,避免重用已经在其它链上存在的地址。开发人员可以为他们的项目注册未使用的号码。
- Account:账户,此级别为了设置独立的用户身份可以将所有币种放在一个的帐户中,从0开始按顺序递增。
- Change:常量0用于外部链,常量1用于内部链,外部链用于钱包在外部用于接收和付款。内部链用于在钱包外部不可见的地址,如返回交易变更。
- Address_index:地址索引,按顺序递增的方式从索引0开始编号。
BIP44的规则使得 HD钱包非常强大,用户只需要保存一个种子,就能控制所有币种,所有账户的钱包,因此由BIP39 生成的助记词非常重要,所以一定安全妥善保管,那么会不会被破解呢?如果一个 HD 钱包助记词是 12 个单词,一共有 2048 个单词可能性,那么随机的生成的助记词所有可能性大概是5e+39
,因此几乎不可能被破解。
HD钱包
通过BIP协议生成账号的钱包叫做HD钱包。这个HD钱包,并不是Hardware Wallet硬件钱包,这里的 HD 是Hierarchical Deterministic
的缩写,意思是分层确定性,所以HD钱包的全称为比特币分成确定性钱包 。
密码、私钥、keystore与助记词的关系
钱包的核心:私钥
基于以上的分析,我们对以太坊钱包的账号系统有了一个很好的认识,那么我们在使用钱包的过程中,该如何保管自己的钱包呢?主要包含以下几种方式:
- 私钥(Private Key)
- Keystore+密码(Keystore+Password)
- 助记词(Mnemonic code)
通过以上三种中的一种方式都可以解锁账号,然后掌控它,所以对于每种方式中的数据都必须妥善包括,如有泄漏,请尽快转移数字资产。
我们可以得到以下总结:
- 通过私钥+密码可以生成keystore,即加密私钥;
- 通过keystore+密码可以获取私钥,即解密keystore。
- 通过助记词根据不同的路径获取不同的私钥,即使用HD钱包将助记词转化成种子来生成主私钥,然后派生海量的子私钥和地址。
可以看出这几种方式的核心其实都是为了获得私钥,然后去解锁账号,因此钱包的核心功能是私钥的创建、存储和使用。
参考资料
https://web3js.readthedocs.io/en/1.0/web3-eth-accounts.html
https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki
https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki
https://github.com/ethereum/EIPs/issues/84
https://github.com/ethereum/EIPs/issues/85
创建账户
从无到有创建一个新的账户
web3 直接创建账户
1 | web3.eth.accounts.create('2435@#@#@±±±±!!!!678543213456764321§34567543213456785432134567'); |
助记词 创建账户
需要使用bip39
协议将助记词转换成种子,再通过ethereumjs-wallet
库生成hd钱包,根据路径的不同从hd钱包中获取不同的keypair,keypair中就包含有公钥、私钥,再通过ethereumjs-util
库将公钥生成地址,从而根据助记词获取所有关联的账号,能获取到公钥、私钥、地址等数据信息。
1. 依赖库
需要用到三个库:bip39、ethereumjs-wallet/hdkey、ethereumjs-util。先安装依赖库,cd
到项目跟路径运行命令npm i bip39 ethereumjs-wallet ethereumjs-util
。
- bip39:随机产生新的 mnemonic code,并可以将其转成 binary 的 seed。
- ethereumjs-wallet:生成和管理公私钥,下面使用其中 hdkey 子套件来创建 HD 钱包。
- ethereumjs-util:Ethereum 的一个工具库。
- https://iancoleman.io/bip39/
2. 通过助记词创建账号
创建助记词
1
2
3
4
5
6
7// 引入bip39模块
import * as bip39 from "bip39";
// 创建助记词
let mnemonic = bip39.generateMnemonic();
console.log(mnemonic);
// 结果 12位助记词
// vote select solar shy embrace immense lizard stamp scrub vague negative forward根据助记词生成密钥对 keypair
1 | // 导入分层钱包模块 |
keypair 密钥对
3. 由keypair 获取钱包地址和私钥
1 | // 获取钱包对象 |
导出账户
一个已经存在的账户导出 私钥 和 keystore
- 通过分层钱包对象 + 密码 创建keystore
1 | let keystore = await wallet.toV3(data.pass1); // 参数必须为 字符串 |
- 通过私钥和密码创建 keystore
1 | const keystore = await web3.eth.accounts.encrypt("账户私钥","密码"); |
1 | // 模拟keystore数据 |
- 通过keystore解密私钥
1 | import ethwallet from "ethereumjs-wallet"; |
导入账户
通过 私钥、助记词、keystore 导入一个已经存在的钱包账户 地址 和 私钥
- 通过keystore获取 私钥和地址
1 | import ethwallet from "ethereumjs-wallet"; |
- 通过助记词 获取地址和私钥
1 | let mnemonic=prompt("请输入助记词") |
通过私钥获取 地址
1
2
3
4
5import ethwallet from "ethereumjs-wallet";
let privatekey=new Buffer( prompt("请输入私钥"), 'hex' )
let wallet = ethwallet.fromPrivateKey(privatekey)
// 获取钱包地址
let lowerCaseAddress = wallet.getAddressString();
区块链钱包项目流程
1.项目准备
直接采用随堂demo创建的项目 不需要重新创建
web3相关第三方包
1 | npm install web3 bip39 ethereumjs-tx@1.3.7 ethereumjs-util ethereumjs-wallet |
注: ethereumjs-tx 使用1.3.7 版本
node-polyfill 兼容文件配置
下载polyfill 插件
1
npm install node-polyfill-webpack-plugin -D
在vue.config.js 文件中配置插件
1
2
3
4
5
6
7
8
9
10
11const { defineConfig } = require('@vue/cli-service')
// 引入插件
++ const NodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");
module.exports = defineConfig({
transpileDependencies: true,
++ configureWebpack: {
++ plugins: [
++ new NodePolyfillWebpackPlugin()
],
},
})
配置vant-ui ui组件库
https://vant-contrib.gitee.io/vant/#/zh-CN
- 安装
1 | $ npm i vant |
- 在vue.config.js 文件中配置插件
1 | const { defineConfig } = require("@vue/cli-service"); |
- 测试 在app.vue文件添加代码
1 | <template> |
通过vw配置响应式
https://www.cnblogs.com/hongrun/p/16130707.html
- 安装
1 | npm install postcss-px-to-viewport -D |
- 在根目录下创建 名为
postcss.config.js
文件
1 | module.exports = { |
封装缓存函数
整个项目为了保证钱包的安全性,所有账户相关的操作,不经过中心化服务器,只在缓存使用
在这里可以参考webstorage
增加过期时间,cookie 等的封装
创建 src/utils/storage.js 文件
1 | class Storage { |
app.vue 文件引入
在app.vue 引入文件并且初始化web3.js
1 | <template> |
2.通过Mnemonic助记词创建钱包
判断缓存是否有 钱包对象
- 有钱包对象显示钱包信息 地址 私钥 余额
- 没有钱包对象显示创建钱包按钮
1 | <template> |
3.显示余额
1 | const balance = ref("0"); |
4.转账交易
1 | 1. 获取用户输入金额 与 地址 |
5.导出账户信息
- 导出私钥
1 | async function exportKey() { |
- 导出keystore
1 | 缓存对象 |
6.解锁账户信息
助记词解锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32添加助记词导入钱包方法
async function importWallet() {
const mnemonic = prompt("请输入助记词");
const pass = prompt("请输入您的钱包密码");
if (!pass) return false;
//1.将助记词转成seed
let seed = await bip39.mnemonicToSeed(mnemonic);
//3.通过hdkey将seed生成HD Wallet
let hdWallet = hdkey.fromMasterSeed(seed);
//4.生成钱包中在m/44'/60'/0'/0/i路径的keypair
let keyPair = hdWallet.derivePath("m/44'/60'/0'/0/0");
// 获取钱包对象
let wallet = keyPair.getWallet();
// 获取钱包地址
let lowerCaseAddress = wallet.getAddressString();
// 获取钱包校验地址
let CheckSumAddress = wallet.getChecksumAddressString();
// 获取私钥
let prikey = wallet.getPrivateKey().toString("hex");
let keystore = await wallet.toV3(pass);
console.log(keystore);
// 保存钱包信息
const walletInfo = {
address: lowerCaseAddress,
prikey,
keystore,
balance: 0,
mnemonic, // 助记词不应该记录下来仅仅是为了便于演示
};
storage.setItem("wallet", walletInfo);
wallet = walletInfo;
}
- 私钥解锁
1 | async function importByPrivateKey() { |
- 未来展望
- 通过uniapp 、rn、electron 将项目变为app和桌面端应用
- app添加扫码转账功能
- 增加erc20代币转账功能
- 增加nft数字藏品商城功能
- 增加Dao 应用
智能合约
通过智能合约文件 获取abi
1
import { abi } from "@/contract/HHC.json";
在web3实例的基础上创建智能合约实例
1 | const hhc = new this.web3.eth.Contract( |
- 通过智能合约实例获取代币余额
1 | let num = await hhc.methods.balanceOf(address).call(); |
- 智能合约交易hash 创建
1 | async createCoinTransationHx(contractInstance, method, to, value) { |
智能合约代币转账
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20this.web3.eth
.sendSignedTransaction(hx)
.on("transactionHash", (txid) => {
console.log("交易成功,请在区块链浏览器查看");
console.log("交易id", txid);
console.log(`https://goerli.etherscan.io/tx/${txid}`);
})
.on("receipt", (ret) => {
cb && cb(ret);
console.log("receipt", ret);
const { transactionHash } = ret;
this.createOrderData(transactionHash);
})
.on("latestBlockHash", (...arg) => {
console.log("latestBlockHash", arg);
})
.on("error", (err) => {
console.log("error:");
console.log(err);
});
以上内容来自千锋教育,并无商业用途,仅供学习使用。