目录

前言

一、ethers.js术语

二、ethers.js使用

1.Provider

方法示例

监听

2.Wallet

方法示例

3.Contracts

用法示例

合约abi

监听

4.utils

部分示例

三、从0到1

需求功能点

开发功能

1.连接MetaMask

2.监听账户变化

3.provider和合约对象

4.唤起MetaMask签名

5.链上转账、余额查询

总结


前言

本文记录Vue框架前端使用ethers.js开发web3钱包相关功能。主要是前端调用ethers.js的相关用法。


一、ethers.js术语

1.Provider 是一个连接以太坊网络的抽象,用与查询以太坊网络状态或者发送更改状态的交易。

2.Wallet 类管理着一个公私钥对用于在以太坊网络上密码签名交易以及所有权证明。

3.Signer 是一个抽象类,当需要签名器Signer时就可以扩展实现它。主要用于对交易消息最新签名,发到后台需要验签。

4.Contracts 合约是在以太坊区块链上的可执行程序的抽象。合约具有代码 (称为字节代码) 以及分配的长期存储 (storage)。每个已部署的合约都有一个地址, 用它连接到合约, 可以向其发送消息来调用合约方法。

5.Utils 工具包提供了大量的通用实用函数去编写 dapps、处理用户输入和格式化数据等功能。

ProviderA Provider (in ethers) is a class which provides an abstraction for a connection to the Ethereum Network. It provides read-only access to the Blockchain and its status.
SignerA Signer is a class which (usually) in some way directly or indirectly has access to a private key, which can sign messages and transactions to authorize the network to charge your account ether to perform operations.
ContractA Contract is an abstraction which represents a connection to a specific contract on the Ethereum Network, so that applications can use it like a normal JavaScript object.

二、ethers.js使用

1.Provider

Provider主要提供读属性,取得provider后可以查询账户信息及太坊状态。

方法示例

1.连接以太坊:MetaMask//window.ethereum是一个以太坊对象,MetaMask会向网页注入一个全局的API变量window.ethereum//登录连接到MetaMask后可以获取providerconst provider = new ethers.providers.Web3Provider(window.ethereum)2.获取signerconst signer = provider.getSigner();//调取MetaMask小狐狸钱包签名let msg = address+amount...;//约定好签名规则即可const signature = await signer.signMessage(msg);3.获取账户余额provider.getBalance(address).then((balance) => {    // 余额是 BigNumber (in wei); 格式化为 ether 字符串    let etherString = ethers.utils.formatEther(balance);    console.log("Balance: " + etherString);});或者const balance = await provider.getBalance(address);// 余额是 BigNumber (in wei); 格式化为 ether 字符串,使用工具包格式化成字符串let balanceStr = ethers.utils.formatUnits(balance, 18);4.获取交易数let address = "0x02F024e0882B310c6734703AB9066EdD3a10C6e0";provider.getTransactionCount(address).then((transactionCount) => {    console.log("发送交易总数: " + transactionCount);});5.获取当前状态//当前的区块号provider.getBlockNumber().then((blockNumber) => {    console.log("Current block number: " + blockNumber);});//当前的gas费provider.getGasPrice().then((gasPrice) => {    // gasPrice is a BigNumber; convert it to a decimal string    gasPriceString = gasPrice.toString();    console.log("Current gas price: " + gasPriceString);});

监听

1.监听事件,监听小狐狸钱包余额变化let address = '';this.provider.on(address, (bal) => {});2.监听区块 provider.on("block", (blockNumber) => {    // Emitted on every block change    console.log("blockNumber: " + blockNumber); })

2.Wallet

Wallet实现了Signer,所以交易时使用Wallet就行。

本人目前没有使用到wallet对象,目前使用的是后端开发的合约方式

方法示例

1.创建钱包-随机钱包//Wallet . createRandom ( [ options ] )   =>   Wallet//创建一个随机钱包实例。 确保钱包(私钥)存放在安全的位置,如果丢失了就没有办法找回钱包。let randomWallet = ethers.Wallet.createRandom();2.创建钱包-加载JSON钱包文件let data = {    id: "fb1280c0-d646-4e40-9550-7026b1be504a",    address: "88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290",    Crypto: {        kdfparams: {            dklen: 32,            p: 1,            salt: "bbfa53547e3e3bfcc9786a2cbef8504a5031d82734ecef02153e29daeed658fd",            r: 8,            n: 262144        },        kdf: "scrypt",        ciphertext: "10adcc8bcaf49474c6710460e0dc974331f71ee4c7baa7314b4a23d25fd6c406",        mac: "1cf53b5ae8d75f8c037b453e7c3c61b010225d916768a6b145adf5cf9cb3a703",        cipher: "aes-128-ctr",        cipherparams: {            iv: "1dcdf13e49cea706994ed38804f6d171"         }    },    "version" : 3};let json = JSON.stringify(data);let password = "foo";ethers.Wallet.fromEncryptedJson(json, password).then(function(wallet) {    console.log("Address: " + wallet.address);    // "Address: 0x88a5C2d9919e46F883EB62F7b8Dd9d0CC45bc290"});3.创建钱包-加载助记词let mnemonic = "radar blur cabbage chef fix engine embark joy scheme fiction master release";let mnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic);// Load the second account from a mnemoniclet path = "m/44'/60'/1'/0/0";let secondMnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic, path);// Load using a non-english locale wordlist (the path "null" will use the default)let secondMnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic, null, ethers.wordlists.ko);4.从已有实例创建新的Wallet实例//privateKey是小狐狸钱包账户的私钥,可以在钱包处看到let privateKey = "0x0123456789012345678901234567890123456789012345678901234567890123";let wallet = new ethers.Wallet(privateKey);// Connect a wallet to mainnetlet provider = ethers.getDefaultProvider();let walletWithProvider = new ethers.Wallet(privateKey, provider);5.余额和交易数let balancePromise = wallet.getBalance();balancePromise.then((balance) => {    console.log(balance);});let transactionCountPromise = wallet.getTransactionCount();transactionCountPromise.then((transactionCount) => {    console.log(transactionCount);});

3.Contracts

合约Contract对象是一个元类,它是一个在运行时定义类的类。 可以提供合约定义(称为应用程序二进制接口或ABI)以及可用的方法和事件可以动态添加到对象中。

创建和部署合约这块我前端没涉及,以后明白了再补偿。

我这里使用到连接已有合约并执行转账等操作。

用法示例

1.连接已有合约// The Contract interfacelet abi = [    "event ValueChanged(address indexed author, string oldValue, string newValue)",    "constructor(string value)",    "function getValue() view returns (string value)",    "function setValue(string value)"];//const abi= require("../config/constants/contract-abi.json");//将abi单独存放到json文件中// Connect to the network,查看provider获取连接provider对象let provider = new ethers.providers.Web3Provider(window.ethereum)// 地址来自上面部署的合约let contractAddress = "0x2bD9aAa2953F988153c8629926D22A6a5F69b14E";// 使用Provider 连接合约,将只有对合约的可读权限let daiContract = new ethers.Contract(contractAddress, abi, provider);2.合约代币转账//合约使用signer签名,查询provider的用法let signer = provider.getSigner();const daiWithSigner = daiContract.connect(signer);const dai = ethers.utils.parseUnits(amount.toString(), 18);//执行转账动作,这里的transfer是部署的合约abi定义的转账方法daiWithSigner.transfer(to, dai).then((resp) => {}).catch((err) => {});3.代币余额查询const balance = await daiContract.balanceOf(address);let balanceStr = ethers.utils.formatUnits(balance, 18);

合约abi

//contract-abi.json[  {    "inputs": [      {        "internalType": "address",        "name": "to",        "type": "address"      },      {        "internalType": "uint256",        "name": "amount",        "type": "uint256"      }    ],    "name": "transfer",    "outputs": [      {        "internalType": "bool",        "name": "",        "type": "bool"      }    ],    "stateMutability": "nonpayable",    "type": "function"  },  {    "inputs": [      {        "internalType": "address",        "name": "account",        "type": "address"      }    ],    "name": "balanceOf",    "outputs": [      {        "internalType": "uint256",        "name": "",        "type": "uint256"      }    ],    "stateMutability": "view",    "type": "function"  },  {  "anonymous": false,  "inputs": [      {        "indexed": false,        "internalType": "address",        "name": "from",        "type": "address"      },      {        "indexed": false,        "internalType": "uint256",        "name": "fromBalance",        "type": "uint256"      },      {        "indexed": false,        "internalType": "address",        "name": "to",        "type": "address"      },      {        "indexed": false,        "internalType": "uint256",        "name": "toBalance",        "type": "uint256"      },      {        "indexed": false,        "internalType": "uint256",        "name": "amount",        "type": "uint256"      }    ],    "name": "TransferNew",    "type": "event"  }]

监听

daiContract.on("TransferNew", (from, fromBalance,to,toBalance, amount, event) => {    // let balanceStr = ethers.utils.formatUnits(fromBalance, 18);    // console.log("fromBalance:::"+fromBalance);    // this.$store.dispatch('SET_BALANCE', balanceStr);  });

4.utils

部分示例

import * as ethers from 'ethers';1.BigNumber类型转成可读的字符串let balanceStr = ethers.utils.formatUnits(balance, 18);2.字符串转成BigNumberlet amount = 1000;let amountBig = ethers.utils.parseUnits(amount.toString(), 18);3.校验是否为以太坊账户地址//返回true或者falselet isAddress = ethers.utils.isAddress(address);4.随机数let randomNumber = utils.bigNumberify(utils.randomBytes(32));// BigNumber { _hex: 0x617542634156966e0bbb6c673bf88015f542c96eb115186fd93881518f05f7ff }

三、从0到1

需求功能点

1.连接MetaMask小狐狸钱包;

2.监听账户变化,即时更新页面信息;

2.链上转账(代币合约转账),唤起小狐狸钱包签名对转账消息签名;

3.代币余额查询,账户切换时即时刷新;

开发功能

1.连接MetaMask

//我们前面provider提到,MetaMask在安装后会发布一个全局的对象window.ethereum//参照小狐狸钱包的官方API:https://docs.metamask.io/guide/getting-started.html#basic-considerations//通过eth_requestAccounts获取连接的账户,未连接时弹出小狐狸钱包的连接页面const addressArray = await web3Provider.request({        method: "eth_requestAccounts",});//通过eth_accounts获取当前连接的账户const addressArray = await web3Provider.request({        method: "eth_accounts",      });

具体代码

//写在了store的action里//连接小狐狸钱包export const connectWallet= async ({ commit }) => {  let web3Provider;  if (window.ethereum) {    web3Provider = window.ethereum;    try {      //通过      const addressArray = await web3Provider.request({        method: "eth_requestAccounts",      });      let address = addressArray[0];      const obj = {        status: " Write a message in the text-field above.",        address: address,      };      setProvider({commit},address);      addWalletListener({commit});      return obj;    } catch (err) {      return {        address: "",        status: " " + err.message,      };    }  } else {    return {      address: "",      status: (                  

{" "} {" "} You must install Metamask, a virtual Ethereum wallet, in your browser.

), }; }};//获得当前连接的账户export const getCurrentWalletConnected= async ({ commit }) => { let web3Provider; if (window.ethereum) { web3Provider = window.ethereum; try { const addressArray = await web3Provider.request({ method: "eth_accounts", }); if (addressArray.length > 0) { let address = addressArray[0]; setProvider({commit},address); addWalletListener({commit}); return { address: addressArray[0], status: " Write a message in the text-field above.", }; } else { return { address: "", status: " Connect to Metamask using the top right button.", }; } } catch (err) { return { address: "", status: " " + err.message, }; } } else { return { address: "", status: (

{" "} {" "} You must install Metamask, a virtual Ethereum wallet, in your browser.

), }; }};

2.监听账户变化

//使用方法web3Provider.on('accountsChanged', accounts => {})export const addWalletListener = ({commit}) => {  let web3Provider;  if (window.ethereum) {    web3Provider = window.ethereum;    web3Provider.on('accountsChanged', accounts => {      SET_ACCOUNT({commit},accounts[0]);      //断开链接后,初始化一些值      if(accounts.length===0){        //使用store的commit改变数据状态        SET_PROVIDER({commit},{});        SET_CONTRACTS({commit},{});        SET_IS_CONNECT_WALLET({commit},false);        SET_SIGNER({commit},{});        SET_BALANCE({commit},'0.0');      }    })  }};

3.provider和合约对象

//获得provider,contract,signer对象,改变store的数据状态,全局使用export const setProvider = ({commit},address) => {  let web3Provider;  if (window.ethereum) {    web3Provider = window.ethereum;    const provider = new ethers.providers.Web3Provider(web3Provider);    const signer = provider.getSigner();    const contractABI = require("../config/constants/contract-abi.json");    const wethAddress = getWethAddress();    const daiContract = new ethers.Contract(wethAddress, contractABI, provider);    //commit('saveAccountStore', address);//另外一种方式    SET_ACCOUNT({commit},address);    SET_PROVIDER({commit},provider);    SET_CONTRACTS({commit},daiContract);    SET_IS_CONNECT_WALLET({commit},true);    SET_SIGNER({commit},signer);  }};

4.唤起MetaMask签名

1.获取signer,并对数据消息签名//调取MetaMask小狐狸钱包签名let signer = this.$store.getters.signer;let address = this.$store.getters.account;let msg = address+amount...;//约定好签名规则即可const signature = await signer.signMessage(msg.toLowerCase());

5.链上转账、余额查询

1.转账export const sendTransfer = async (store,amount,to) => {  let isConnectWallet = store.getters.isConnectWallet;  if(isConnectWallet){    let daiContract = store.getters.contracts;    let signer = store.getters.signer;    const daiWithSigner = daiContract.connect(signer);    const dai = ethers.utils.parseUnits(amount.toString(), 18);    //方式一,传参传一个promise  一个方法    // daiWithSigner.transfer(to, dai).then((resp) => {}).catch((error) => {    //   errHandler(error);    // });    //方式二,直接返回这个promise    return daiWithSigner.transfer(to, dai);  }};2.余额查询export const getBalance = async (store) => {  let balanceStr = '0.0';  let isConnectWallet = store.getters.isConnectWallet;  if(isConnectWallet){    let address = store.getters.account;    let daiContract = store.getters.contracts;    if(daiContract.balanceOf!==undefined){      const balance = await daiContract.balanceOf(address);      let balanceStr = ethers.utils.formatUnits(balance, 18);      store.dispatch('SET_BALANCE', balanceStr);    }  }  return {balance: balanceStr,};};

总结

参与web3开发让我感触颇多,磕磕绊绊,从小白总算是跨出了第一步,算是入了Vue和web3的坑了。本人是那种属于要做就做好的人,看不惯随便做做交代任务完事的研发,所以看到不合适的代码总想换掉,就会去查资料查百度,去找一些合理的写法。对与web3钱包前端门户的开发目前总结到此,待学习后续。