web3 17-34

619 阅读11分钟

准备工作

项目创建

版本信息:

Node 16.14

安装vue-cli

npm install -g @vue/cli

验证vue-cli 安装

$ vue -V
// 结果 出现vue版本号 安装成功
@vue/cli 5.0.8

通过vue-cli创建项目

$ vue create <项目名称>
// 选择配置
Vue CLI v5.0.8
? Please pick a preset: 
  Default ([Vue 3] babel, eslint) 
  Default ([Vue 2] babel, eslint) 
❯ Manually select features  `选择该项`

选择自定义配置

? Check the features needed for your project: (Press <space> to select, <a> to 
toggle all, <i> to invert selection, and <enter> to proceed)
 ◉ Babel `选择该项`
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◉ Router `选择该项`
 ◯ Vuex
❯◉ CSS Pre-processors `选择该项`
 ◯ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

选择vue版本

? Choose a version of Vue.js that you want to start the project with (Use arrow 
keys)
❯ 3.x 
  2.x 

选择路由设置

? Use history mode for router? (Requires proper server setup for index fallback 
in production) (Y/n) y  `输入y或者n`

选择预处理语言

? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported 
by default): 
  Sass/SCSS (with dart-sass) 
❯ Less 
  Stylus 

选择配置文件存放目录

? Where do you prefer placing config for Babel, ESLint, etc.? 
❯ In dedicated config files 
  In package.json 

安装成功后 运行测试

 $ cd 项目名称
 $ npm run serve

成功界面

image-20230102200435157

第三方包安装

web3相关第三方包

npm install web3 bip39 ethereumjs-tx@1.3.7 ethereumjs-util ethereumjs-wallet

注: ethereumjs-tx 使用1.3.7 版本

node-polyfill 兼容文件配置

  1. 下载polyfill 插件

    npm install node-polyfill-webpack-plugin -D 
    
  2. 在vue.config.js 文件中配置插件

    const { defineConfig } = require('@vue/cli-service')
    // 引入插件
    ++ const NodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");
    module.exports = defineConfig({
      transpileDependencies: true,
    ++  configureWebpack: {
    ++    plugins: [
    ++      new NodePolyfillWebpackPlugin()
        ],
      },
    })
    
    

配置vant-ui ui组件库

vant-contrib.gitee.io/vant/#/zh-C…

  1. 安装
$ npm i vant
$ npm i unplugin-vue-components -D
  1. 在vue.config.js 文件中配置插件
const { defineConfig } = require("@vue/cli-service");
// 引入插件
const NodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");
// vant
++ const { VantResolver } = require('unplugin-vue-components/resolvers');
++ const ComponentsPlugin = require('unplugin-vue-components/webpack');
module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    plugins: [
      new NodePolyfillWebpackPlugin(),
++    ComponentsPlugin({ resolvers: [VantResolver()]}),
    ],
  },
});

  1. 测试 在app.vue文件添加代码
<template>
 <van-button type="primary">test</van-button>
 <van-button type="success">test</van-button>
</template>

image-20230102202255947

通过vw配置响应式

www.cnblogs.com/hongrun/p/1…

  1. 安装
npm install postcss-px-to-viewport -D 
  1. 在根目录下创建 名为 postcss.config.js 文件
module.exports = {
  plugins: {
    "postcss-px-to-viewport": {
      unitToConvert: "px", // 要转化的单位
      viewportWidth: 375, // UI设计稿的宽度
      unitPrecision: 6, // 转换后的精度,即小数点位数
      propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw      selectorBlackList: ["wrap"], // 指定不转换为视窗单位的类名,
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
    },
  },
};

web3连接到以太坊网络(测试网、主网)

  1. 什么是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地址:github.com/web3/web3.j…

web3.js开发文档:web3js.readthedocs.io/en/v1.8.1/

web3.js 中文文档 : learnblockchain.cn/docs/web3.j…

  1. 实例化web3对象 web3要与以坊节点进行交互,需要创建一个web3对象,下面看看如何创建。
var Web3 = require('web3');
// "Web3.providers.givenProvider" will be set if in an Ethereum supported browser.
var web3 = new Web3(Web3.givenProvider || 'ws://some.local-or-remote.node:8546');

根据API可知需要指定节点地址,我们将ws://some.local-or-remote.node:8546换成其它连接到以太坊网络的节点的地址,以此来确定连接的以太坊的网络。那么连接到以太坊网络的节点的地址是多少呢?这里我们需要使用到infura。

  1. 获取连接到以太坊网络的节点地址 infura提供公开的 Ethereum主网和测试网络节点,到infura.io网站注册后即可获取各个网络的地址。请按照如下步骤获取地址。

第一步:打开 infura网站地址:infura.io/dashboard,使…

第二步:点击上图标记的“create new project”按钮创建一个新项目。然后弹出如下弹框,在输入框输入项目名,如”MyEtherWallet“,然后点击“create project”按钮创建。

第三步:然后会显示如下界面,点击下图中的选择框,可以看到提供主网、Kovan测试网络、Ropsten测试网络、GoerLi测试网络的节点地址。

image.png

第四步:选择GoerLi测试网络,然后复制地址,将获取到类似这样的地址:kovan.infura.io/v3/d93f....…

image.png

  1. 连接到以太坊GoerLi测试网络 现在将复制的地址替换掉实例化web对象的地址,如下
var Web3 = require("web3")
var web3 = new Web3(Web3.givenProvider || 'wss://goerli.infura.io/ws/v3/cb7e63cf28244e4499b4b6fb6162e746');
console.log("Web3:", web3)

连接到以太坊主网与GoerLi测试网络一样的,只需复制主网节点的地址去实例化web3即可。由于在主网上交易需要花费gas,因此我们基于GoerLi测试网络进行开发,后续开发完成后可再切换到主网。

image-20230102215334761

Web3js 高频 Api

账号创建

  1. 创建账号需要使用web3.js的如下API

API

web3.eth.accounts.create([entropy]);

image.png 参数:

entropy - String (可选): 它是一个可选项,是一个随机字符串,将作为解锁账号的密码。如果没有传递字符串,则使用random生成随机字符串。

返回值:

Object:包含以下字段的一个帐户对象:

address- string:帐户地址。

privateKey- string:帐户私钥。前端永远不应该在localstorage中以未加密的方式共享或存储!

signTransaction(tx [, callback])- Function:签名交易的方法。

sign(data)- Function:签名二进制交易的方法。

例子

web3.eth.accounts.create('2435@#@#@±±±±!!!!678543213456764321§34567543213456785432134567');
web3.eth.accounts.create('2435@#@#@±±±±!!!!678543213456764321§34567543213456785432134567');
{
address: "0xF2CD2AA0c7926743B1D4310b2BC984a0a453c3d4",
privateKey: "0xd7325de5c2c1cf0009fac77d3d04a9c004b038883446b065871bc3e831dcd098",
signTransaction: function(tx){...},
sign: function(data){...},
encrypt: function(password){...}
}
  1. 获取地址 使用APIweb3.eth.accounts.create()创建了新账户后生成了一个账户对象,在该对象中拥有addreds属性,即账户的私钥。
let account = web3.eth.accounts.create("123456")
let address = account.address
//address:0xfF0B5A0AA68249cD161b606679DB49CBD9a12cd0

3.获取私钥

使用APIweb3.eth.accounts.create()创建了新账户后生成了一个账户对象,在该对象中拥有privateKey属性,即账户的私钥。

let account = web3.eth.accounts.create()
let privateKey = account.privateKey
//privateKey:0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709

余额获取

根据地址获取以wei为单位余额

web3.eth.getBalance(address).then((ret) => {
    console.log(ret)
});

单位转换

  1. Eth 转为 wei
# Web3 或者实例后的web3对象都可以
const num = Web3.utils.toWei("0.3");
const num = web3.utils.toWei("0.3");
console.log(num)
// 300000000000000000
  1. wei 转为Eth
# Web3 或者实例后的web3对象都可以
 this.balance = Web3.utils.fromWei(ret, "ether");
 this.balance = web3.utils.fromWei(ret, "ether");

Eth转账

API

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"返回NumberObject:每次确认都会被调用,直到第12次确认。接收确认编号作为第一个参数,将数据作为第二个参数。
  • "error"返回Error:如果在发送过程中发生错误,则会触发。
  1. 构建转账参数

    区块链转账和支付宝转账类似,需要 发送方接收方金额密码

    另外需要添加部分区块链参数:矿工费gas地址转账交易次数

         // 获取账户交易次数
          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代币会用到的一个字段
          };
    
  2. 通过转账参数计算最终gas费用,并将通过私钥将转账参数进行编码加密

    ethereumjs-tx 第三方库请选择1.3.7版本

    import 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();
    
  3. 通过 sendSignedTransaction api发送转账交易,并且获取交易id

      
      web3.eth
        .sendSignedTransaction("0x" + serializedTx.toString("hex"))
        .on("transactionHash", (txid) => {
          console.log("交易成功,请在区块链浏览器查看");
          console.log("交易id", txid);
          console.log(`https://goerli.etherscan.io/tx/${txid}`);
        })
        // .on('receipt', (ret)=>{console.log('receipt')})
        // .on('confirmation', (ret)=>{console.log('confirmation')})
        .on("error", (err) => {
          console.log("error:" + err);
        });
  1. 区块链浏览器或者目标钱包产看转账结果

    goerli区块链浏览器 goerli.etherscan.io/tx/交易id

账户系统

简介

在前面的教程中我们对以太坊钱包已经有了一定的认识,上一章也重点介绍了账号地址的生成过程,在以太坊钱包中一个重点就是账户系统,在这个模块中很多初学同学不是很清楚密码、keystore、助记词与私钥它们之间的关系。下面我们来看看它们之间到底有着怎样的爱恨情仇,让大家琢磨不透。

密码

密码不是私钥,它是在创建账户时候的密码(可以修改)

密码在以下情况下会使用到:

  1. 作为转账的支付密码
  2. 用keystore导入钱包的时候需要输入的密码,用来解锁keystore的

私钥 Private Key

私钥由64位长度的十六进制的字符组成,比如:0xA4356E49C88C8B7AB370AF7D5C0C54F0261AAA006F6BDE09CD4745CF54E0115A,一个账户只有一个私钥且不能修改。 通常一个钱包中私钥和公钥是成对出现的,有了私钥,我们就可以通过一定的算法生成公钥,再通过公钥经过一定的算法生成地址,这一过程都是不可逆的。私钥一定要妥善保管,若被泄漏别人可以通过私钥解锁账号转出你的该账号的数字货币。

公钥 Public Key

公钥(Public Key)是和私钥成对出现的,和私钥一起组成一个密钥对,保存在钱包中。公钥由私钥生成,但是无法通过公钥倒推得到私钥。公钥能够通过一系列算法运算得到钱包的地址,因此可以作为拥有这个钱包地址的凭证。

Keystore

Keystore常见于以太坊钱包,它是将私钥以加密的方式保存为一份 JSON 文件,这份 JSON 文件就是 keystore,所以它就是加密后的私钥。Keystore必须配合钱包密码才能导入并使用该账号。当黑客盗取 Keystore 后,在没有密码情况下, 有可能通过暴力破解 Keystore 密码解开 Keystore,所以建议使用者在设置密码时稍微复杂些,比如带上特殊字符,至少 8 位以上,并安全存储。

助记词 Mnemonic

私钥是64位长度的十六进制的字符,不利于记录且容易记错,所以用算法将一串随机数转化为了一串12 ~ 24个容易记住的单词,方便保存记录。注意:

  1. 助记词是私钥的另一种表现形式
  2. 助记词=私钥,这是不正确的说法,通过助记词可以获取相关联的多个私钥,但是通过其中一个私钥是不能获取助记词的,因此助记词≠私钥

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个级别:

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与助记词的关系

img

钱包的核心:私钥

基于以上的分析,我们对以太坊钱包的账号系统有了一个很好的认识,那么我们在使用钱包的过程中,该如何保管自己的钱包呢?主要包含以下几种方式:

  • 私钥(Private Key)
  • Keystore+密码(Keystore+Password)
  • 助记词(Mnemonic code)

通过以上三种中的一种方式都可以解锁账号,然后掌控它,所以对于每种方式中的数据都必须妥善包括,如有泄漏,请尽快转移数字资产。

我们可以得到以下总结:

  • 通过私钥+密码可以生成keystore,即加密私钥;
  • 通过keystore+密码可以获取私钥,即解密keystore。
  • 通过助记词根据不同的路径获取不同的私钥,即使用HD钱包将助记词转化成种子来生成主私钥,然后派生海量的子私钥和地址。

可以看出这几种方式的核心其实都是为了获得私钥,然后去解锁账号,因此钱包的核心功能是私钥的创建、存储和使用。

参考资料

web3js.readthedocs.io/en/1.0/web3… github.com/bitcoin/bip… github.com/bitcoin/bip… github.com/ethereum/EI… github.com/ethereum/EI…

创建账户

从无到有创建一个新的账户

web3 直接创建账户

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

2. 通过助记词创建账号

  • 创建助记词

    // 引入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

      // 导入分层钱包模块
import { hdkey } from "ethereumjs-wallet";
      //1.将助记词转成seed
      let seed = await bip39.mnemonicToSeed("12位助记词");
      //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");
      console.log(keypair);
     

keypair 密钥对

image-20221208222354836

3. 由keypair 获取钱包地址和私钥

// 获取钱包对象
let wallet = keypair.getWallet();
// 获取钱包地址
let lowerCaseAddress = wallet.getAddressString();
// 获取钱包校验地址
let CheckSumAddress = wallet.getChecksumAddressString();
// 获取私钥
let prikey = wallet.getPrivateKey().toString("hex");
 console.log("lowerCaseAddress", lowerCaseAddress);
 console.log("CheckSumAddress", CheckSumAddress);
 console.log("prikey", prikey);
/*
lowerCaseAddress 0xd9fc0fd4412616c7075e68b151ab3a7bcb9a3f54
CheckSumAddress 0xd9fC0FD4412616c7075E68b151ab3A7bcB9A3f54
prikey 3fc11495517f1f015bbcb6c311da66e3b26b23e4c91c1285ccc4b69d9d274002
*/

导出账户

一个已经存在的账户导出 私钥 和 keystore

  1. 通过分层钱包对象 + 密码 创建keystore
  let keystore = await wallet.toV3(data.pass1); // 参数必须为 字符串
  1. 通过私钥和密码创建 keystore
const keystore = await web3.eth.accounts.encrypt("账户私钥","密码");
// 模拟keystore数据
const keystoreJsonV3 = {
        version: 3,
        id: "dbb70fb2-52ad-4e1f-9c19-0b50329f89c3",
        address: "445b469888528dacd9b87246c5ce70407adaa411",
        crypto: {
          ciphertext:
            "1e53e7e775644422600188b8134907992db40d278064ea3a966da4dcdf80db64",
          cipherparams: { iv: "f9d2b047019674eee449b316f4a21491" },
          cipher: "aes-128-ctr",
          kdf: "scrypt",
          kdfparams: {
            dklen: 32,
            salt: "153e074d78d0ba36fae3e46e582c42e53f61653cb5d4f1a3a3f68094e6ca0160",
            n: 8192,
            r: 8,
            p: 1,
          },
          mac: "e91456c59b2505c16b80c2495ab7b4633273c2ae366cb6953f27de8cfebad629",
        },
      };
 const res = web3.eth.accounts.decrypt(keystoreJsonV3, "1235");
 console.log(res);
  1. 通过keystore解密私钥
import ethwallet from "ethereumjs-wallet";  
   let pass = prompt("请输入密码");
       let wallet;
       try {
         wallet = await ethwallet.fromV3(keystore, pass);
       } catch (error) {
         alert("密码错误");
         return false;
       }
   let key = wallet.getPrivateKey().toString("hex");

导入账户

通过 私钥、助记词、keystore 导入一个已经存在的钱包账户 地址 和 私钥

  1. 通过keystore获取 私钥和地址
import ethwallet from "ethereumjs-wallet";  
   let pass = prompt("请输入密码");
       let wallet;
       try {
         wallet = await ethwallet.fromV3(keystore, pass);
       } catch (error) {
         alert("密码错误");
         return false;
       }
let key = wallet.getPrivateKey().toString("hex");
let address = wallet.getAddressString()
  1. 通过助记词 获取地址和私钥
let mnemonic=prompt("请输入助记词")
let seed = bip39.mnemonicToSeed(mnemonic)
let hdwallet = hdkey.fromMasterSeed(seed)
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");
  1. 通过私钥获取 地址

    import ethwallet from "ethereumjs-wallet";     
    let privatekey=new Buffer( prompt("请输入私钥"), 'hex' )
    let wallet = ethwallet.fromPrivateKey(privatekey)
    // 获取钱包地址
    let lowerCaseAddress = wallet.getAddressString();
    

区块链钱包项目流程

image-20221209110335777

1.项目准备

​ 直接采用随堂demo创建的项目 不需要重新创建

web3相关第三方包

npm install web3 bip39 ethereumjs-tx@1.3.7 ethereumjs-util ethereumjs-wallet

注: ethereumjs-tx 使用1.3.7 版本

node-polyfill 兼容文件配置

  1. 下载polyfill 插件

    npm install node-polyfill-webpack-plugin -D 
    
  2. 在vue.config.js 文件中配置插件

    const { defineConfig } = require('@vue/cli-service')
    // 引入插件
    ++ const NodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");
    module.exports = defineConfig({
      transpileDependencies: true,
    ++  configureWebpack: {
    ++    plugins: [
    ++      new NodePolyfillWebpackPlugin()
        ],
      },
    })
    
    

配置vant-ui ui组件库

vant-contrib.gitee.io/vant/#/zh-C…

  1. 安装
$ npm i vant
$ npm i unplugin-vue-components -D
  1. 在vue.config.js 文件中配置插件
const { defineConfig } = require("@vue/cli-service");
// 引入插件
const NodePolyfillWebpackPlugin = require("node-polyfill-webpack-plugin");
// vant
++ const { VantResolver } = require('unplugin-vue-components/resolvers');
++ const ComponentsPlugin = require('unplugin-vue-components/webpack');
module.exports = defineConfig({
  transpileDependencies: true,
  configureWebpack: {
    plugins: [
      new NodePolyfillWebpackPlugin(),
++    ComponentsPlugin({ resolvers: [VantResolver()]}),
    ],
  },
});

  1. 测试 在app.vue文件添加代码
<template>
 <van-button type="primary">test</van-button>
 <van-button type="success">test</van-button>
</template>

image-20230102202255947

通过vw配置响应式

www.cnblogs.com/hongrun/p/1…

  1. 安装
npm install postcss-px-to-viewport -D 
  1. 在根目录下创建 名为 postcss.config.js 文件
module.exports = {
  plugins: {
    "postcss-px-to-viewport": {
      unitToConvert: "px", // 要转化的单位
      viewportWidth: 375, // UI设计稿的宽度
      unitPrecision: 6, // 转换后的精度,即小数点位数
      propList: ["*"], // 指定转换的css属性的单位,*代表全部css属性的单位都进行转换
      viewportUnit: "vw", // 指定需要转换成的视窗单位,默认vw
      fontViewportUnit: "vw", // 指定字体需要转换成的视窗单位,默认vw      selectorBlackList: ["wrap"], // 指定不转换为视窗单位的类名,
      minPixelValue: 1, // 默认值1,小于或等于1px则不进行转换
      mediaQuery: true, // 是否在媒体查询的css代码中也进行转换,默认false
      replace: true, // 是否转换后直接更换属性值
      exclude: [/node_modules/], // 设置忽略文件,用正则做目录名匹配
    },
  },
};

封装缓存函数

整个项目为了保证钱包的安全性,所有账户相关的操作,不经过中心化服务器,只在缓存使用

在这里可以参考webstorage增加过期时间,cookie 等的封装

创建 src/utils/storage.js 文件

class Storage {
  setItem(key, val) {
    localStorage.setItem(key, JSON.stringify(val || ""));
  }
  getItem(key) {
    let val;
    try {
      val = JSON.parse(localStorage.getItem(key));
    } catch {
      val = null;
    }
    return val;
  }
}
export default new Storage();

app.vue 文件引入

在app.vue 引入文件并且初始化web3.js

<template>
  <div class="app">
   <h3> qf - eth -wallet</h3>
  </div>
</template>
<script setup>
// import * as util from "ethereumjs-util";
import ethwallet from "ethereumjs-wallet";  
import { hdkey } from "ethereumjs-wallet";
import * as bip39 from "bip39";
import Tx from "ethereumjs-tx";
import Web3 from "web3";
import storage from "@/utils/storage";
var web3 = new Web3(
  Web3.givenProvider ||
    "https://goerli.infura.io/v3/e4f789009c9245eeaad1d93ce9d059bb"
);

</script>

2.通过Mnemonic助记词创建钱包

判断缓存是否有 钱包对象

  1. 有钱包对象显示钱包信息 地址 私钥 余额
  2. 没有钱包对象显示创建钱包按钮
<template>
  <div class="app">
    <h3>qf - eth -wallet</h3>
    <h4>创建钱包</h4>
    <van-button type="primary" @click="createWallet"> 创建钱包 </van-button>
    <van-button type="primary" @click="importWallet">
      导入钱包-助记词
    </van-button>
    <h4>钱包信息</h4>
    {{ wallet }}
  </div>
</template>
<script setup>

// import * as util from "ethereumjs-util";
import ethwallet from "ethereumjs-wallet";
import { hdkey } from "ethereumjs-wallet";
import * as bip39 from "bip39";
import Tx from "ethereumjs-tx";
import storage from "@/utils/storage";
import Web3 from "web3";
import { reactive } from "vue";
var web3 = new Web3(
  Web3.givenProvider ||
    "https://goerli.infura.io/v3/e4f789009c9245eeaad1d93ce9d059bb"
);
const wallet = reactive(storage.getItem("wallet") || {});
console.log(wallet);
async function createWallet() {
  const pass = prompt("请输入您的钱包密码");
  if (!pass) return false;
  let mnemonic = bip39.generateMnemonic();
  alert("您的助记词为:" + mnemonic);
  const checkMnemonic = prompt("请输入您的助记词");
  if (mnemonic === checkMnemonic) {
    //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;
  } else {
    alert("助记词错误请重新输入");
  }
}

</script>

3.显示余额

const balance = ref("0");

// 获取余额
async function getBalance() {
  if(!wallet.address) return false;
  // 根据地址查询余额
  web3.eth.getBalance(wallet.address).then((ret) => {
    balance.value = web3.utils.fromWei(ret, "ether");
  });
}
getBalance()

4.转账交易

1. 获取用户输入金额 与 地址 
2. 调用send 方法

async function send() {
  const keystore = storage.getItem("wallet").keystore;
  let pass = prompt("请输入密码");
  let walletobj;
  try {
    walletobj = await ethwallet.fromV3(keystore, pass);
  } catch (error) {
    alert("密码错误");
    return false;
  }
  let key = walletobj.getPrivateKey().toString("hex");
  var privateKey = new Buffer(key, "hex");
  // console.log(privateKey);
  const fromaddress = wallet.address;
  // 获取账户交易次数
  let nonce = await web3.eth.getTransactionCount(fromaddress);
  // 获取预计转账gas费
  let gasPrice = await web3.eth.getGasPrice();
  // 转账金额以wei为单位
  console.log(number.value, typeof number.value);
  let balance = Web3.utils.toWei(number.value);
  console.log(222);
  var rawTx = {
    from: fromaddress,
    nonce: nonce,
    gasPrice: gasPrice,
    to: toaddress.value,
    value: balance,
    data: "0x00", //转Token代币会用到的一个字段
  };

  //需要将交易的数据进行预估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();

  web3.eth
    .sendSignedTransaction("0x" + serializedTx.toString("hex"))
    .on("transactionHash", (txid) => {
      console.log("交易成功,请在区块链浏览器查看");
      console.log("交易id", txid);
      console.log(`https://goerli.etherscan.io/tx/${txid}`);
    })
    // .on('receipt', (ret)=>{console.log('receipt')})
    // .on('confirmation', (ret)=>{console.log('confirmation')})
    .on("error", (err) => {
      console.log("error:" + err);
    });
}

5.导出账户信息

  • 导出私钥
async function exportKey() {
  let pass = prompt("请输入密码");
       let walletObj;
       try {
        walletObj = await ethwallet.fromV3(wallet.keystore, pass);
       } catch (error) {
         alert("密码错误");
         return false;
       }
   let key = walletObj.getPrivateKey().toString("hex");
   alert(key)
}
  • 导出keystore
缓存对象
function exportKeyStore() {
  alert(JSON.stringify(wallet.keystore))
}

6.解锁账户信息

  • 助记词解锁

    添加助记词导入钱包方法
    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;
      }
    
  • 私钥解锁

async function importByPrivateKey() { 
  const key = prompt("请输入私钥")
	let privatekey=new Buffer(key , 'hex' )
  let pass =  prompt("请输入密码");
	let wallet = ethwallet.fromPrivateKey(privatekey)
  let keystore = await wallet.toV3(pass);
  // 获取钱包地址
  let lowerCaseAddress = wallet.getAddressString();
  const walletInfo = {
      address: lowerCaseAddress,
      prikey: key,
      keystore,
      balance: 0,
    };
    console.log(walletInfo)
    storage.setItem("wallet", walletInfo);
}
  1. 未来展望
    • 通过uniapp 、rn、electron 将项目变为app和桌面端应用
    • app添加扫码转账功能
    • 增加erc20代币转账功能
    • 增加nft数字藏品商城功能
    • 增加Dao 应用

智能合约

  1. 通过智能合约文件 获取abi

    import { abi } from "@/contract/HHC.json";
    
  2. 在web3实例的基础上创建智能合约实例

const hhc = new this.web3.eth.Contract(
  abi,
  "0x18dAaC8e2E422fDB5e26715eF1EcDca11F78eDE5"
);
  1. 通过智能合约实例获取代币余额
 let num = await hhc.methods.balanceOf(address).call();
Web3.utils.fromWei(num, "ether");
  1. 智能合约交易hash 创建
 async createCoinTransationHx(contractInstance, method, to, value) {
    let pass = prompt("请输入密码");
    let wallet;
    try {
      wallet = await ethwallet.fromV3(keystore, pass);
    } catch (error) {
      alert("密码错误");
      return false;
    }
    let key = wallet.getPrivateKey().toString("hex");
    const from = this.ownerAddress;
    // 当前地址交易次数
    const nonce = await this.instance.eth.getTransactionCount(from);
    var privateKey = new Buffer(key, "hex");
    // 获取预计转账gas费
    let gasPrice = await this.instance.eth.getGasPrice();
    // 转账金额以wei为单位
    let weiValue = await Web3.utils.toWei(value);
    // 转账的记录对象
    // 代币转账
    // this.HccCont = new this.web3.eth.Contract(
    //   abi,
    //   "0x18dAaC8e2E422fDB5e26715eF1EcDca11F78eDE5"
    // );
    const contractAbi = await contractInstance.methods[method](
      to,
      weiValue
    ).encodeABI();
    // console.log(contractAbi);
    // console.log(contractInstance._address);
    // return false;
    var rawTx = {
      from: this.ownerAddress,
      nonce: nonce,
      gasPrice: gasPrice,
      to: contractInstance._address, //eth 转账 to 目标地址 ,智能合约 to 智能合约地址
      value: 0, //eth 转账 数量
      data: contractAbi, //智能合约方法的abi编码
    };
    //需要将交易的数据进行预估gas计算,然后将gas值设置到数据参数中
    let gas = await this.instance.eth.estimateGas(rawTx);
    rawTx.gas = gas;
    // 通过tx实现交易对象的加密操作
    var tx = new Tx(rawTx);
    tx.sign(privateKey);
    var serializedTx = tx.serialize();
    var transationHx = "0x" + serializedTx.toString("hex");
    return transationHx;
  }
  1. 智能合约代币转账

    this.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);
          });