• 自学Vue开发Dapp去中心化钱包(四)


    目录

    前言

    一、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. 1.连接以太坊:MetaMask
    2. //window.ethereum是一个以太坊对象,MetaMask会向网页注入一个全局的API变量window.ethereum
    3. //登录连接到MetaMask后可以获取provider
    4. const provider = new ethers.providers.Web3Provider(window.ethereum)
    5. 2.获取signer
    6. const signer = provider.getSigner();
    7. //调取MetaMask小狐狸钱包签名
    8. let msg = address+amount...;//约定好签名规则即可
    9. const signature = await signer.signMessage(msg);
    10. 3.获取账户余额
    11. provider.getBalance(address).then((balance) => {
    12. // 余额是 BigNumber (in wei); 格式化为 ether 字符串
    13. let etherString = ethers.utils.formatEther(balance);
    14. console.log("Balance: " + etherString);
    15. });
    16. 或者
    17. const balance = await provider.getBalance(address);
    18. // 余额是 BigNumber (in wei); 格式化为 ether 字符串,使用工具包格式化成字符串
    19. let balanceStr = ethers.utils.formatUnits(balance, 18);
    20. 4.获取交易数
    21. let address = "0x02F024e0882B310c6734703AB9066EdD3a10C6e0";
    22. provider.getTransactionCount(address).then((transactionCount) => {
    23. console.log("发送交易总数: " + transactionCount);
    24. });
    25. 5.获取当前状态
    26. //当前的区块号
    27. provider.getBlockNumber().then((blockNumber) => {
    28. console.log("Current block number: " + blockNumber);
    29. });
    30. //当前的gas费
    31. provider.getGasPrice().then((gasPrice) => {
    32. // gasPrice is a BigNumber; convert it to a decimal string
    33. gasPriceString = gasPrice.toString();
    34. console.log("Current gas price: " + gasPriceString);
    35. });

    监听

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

    2.Wallet

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

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

    方法示例

    1. 1.创建钱包-随机钱包
    2. //Wallet . createRandom ( [ options ] ) => Wallet
    3. //创建一个随机钱包实例。 确保钱包(私钥)存放在安全的位置,如果丢失了就没有办法找回钱包。
    4. let randomWallet = ethers.Wallet.createRandom();
    5. 2.创建钱包-加载JSON钱包文件
    6. let data = {
    7. id: "fb1280c0-d646-4e40-9550-7026b1be504a",
    8. address: "88a5c2d9919e46f883eb62f7b8dd9d0cc45bc290",
    9. Crypto: {
    10. kdfparams: {
    11. dklen: 32,
    12. p: 1,
    13. salt: "bbfa53547e3e3bfcc9786a2cbef8504a5031d82734ecef02153e29daeed658fd",
    14. r: 8,
    15. n: 262144
    16. },
    17. kdf: "scrypt",
    18. ciphertext: "10adcc8bcaf49474c6710460e0dc974331f71ee4c7baa7314b4a23d25fd6c406",
    19. mac: "1cf53b5ae8d75f8c037b453e7c3c61b010225d916768a6b145adf5cf9cb3a703",
    20. cipher: "aes-128-ctr",
    21. cipherparams: {
    22. iv: "1dcdf13e49cea706994ed38804f6d171"
    23. }
    24. },
    25. "version" : 3
    26. };
    27. let json = JSON.stringify(data);
    28. let password = "foo";
    29. ethers.Wallet.fromEncryptedJson(json, password).then(function(wallet) {
    30. console.log("Address: " + wallet.address);
    31. // "Address: 0x88a5C2d9919e46F883EB62F7b8Dd9d0CC45bc290"
    32. });
    33. 3.创建钱包-加载助记词
    34. let mnemonic = "radar blur cabbage chef fix engine embark joy scheme fiction master release";
    35. let mnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic);
    36. // Load the second account from a mnemonic
    37. let path = "m/44'/60'/1'/0/0";
    38. let secondMnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic, path);
    39. // Load using a non-english locale wordlist (the path "null" will use the default)
    40. let secondMnemonicWallet = ethers.Wallet.fromMnemonic(mnemonic, null, ethers.wordlists.ko);
    41. 4.从已有实例创建新的Wallet实例
    42. //privateKey是小狐狸钱包账户的私钥,可以在钱包处看到
    43. let privateKey = "0x0123456789012345678901234567890123456789012345678901234567890123";
    44. let wallet = new ethers.Wallet(privateKey);
    45. // Connect a wallet to mainnet
    46. let provider = ethers.getDefaultProvider();
    47. let walletWithProvider = new ethers.Wallet(privateKey, provider);
    48. 5.余额和交易数
    49. let balancePromise = wallet.getBalance();
    50. balancePromise.then((balance) => {
    51. console.log(balance);
    52. });
    53. let transactionCountPromise = wallet.getTransactionCount();
    54. transactionCountPromise.then((transactionCount) => {
    55. console.log(transactionCount);
    56. });

    3.Contracts

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

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

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

    用法示例

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

    合约abi

    1. //contract-abi.json
    2. [
    3. {
    4. "inputs": [
    5. {
    6. "internalType": "address",
    7. "name": "to",
    8. "type": "address"
    9. },
    10. {
    11. "internalType": "uint256",
    12. "name": "amount",
    13. "type": "uint256"
    14. }
    15. ],
    16. "name": "transfer",
    17. "outputs": [
    18. {
    19. "internalType": "bool",
    20. "name": "",
    21. "type": "bool"
    22. }
    23. ],
    24. "stateMutability": "nonpayable",
    25. "type": "function"
    26. },
    27. {
    28. "inputs": [
    29. {
    30. "internalType": "address",
    31. "name": "account",
    32. "type": "address"
    33. }
    34. ],
    35. "name": "balanceOf",
    36. "outputs": [
    37. {
    38. "internalType": "uint256",
    39. "name": "",
    40. "type": "uint256"
    41. }
    42. ],
    43. "stateMutability": "view",
    44. "type": "function"
    45. },
    46. {
    47. "anonymous": false,
    48. "inputs": [
    49. {
    50. "indexed": false,
    51. "internalType": "address",
    52. "name": "from",
    53. "type": "address"
    54. },
    55. {
    56. "indexed": false,
    57. "internalType": "uint256",
    58. "name": "fromBalance",
    59. "type": "uint256"
    60. },
    61. {
    62. "indexed": false,
    63. "internalType": "address",
    64. "name": "to",
    65. "type": "address"
    66. },
    67. {
    68. "indexed": false,
    69. "internalType": "uint256",
    70. "name": "toBalance",
    71. "type": "uint256"
    72. },
    73. {
    74. "indexed": false,
    75. "internalType": "uint256",
    76. "name": "amount",
    77. "type": "uint256"
    78. }
    79. ],
    80. "name": "TransferNew",
    81. "type": "event"
    82. }
    83. ]

    监听

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

    4.utils

     部分示例

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

     三、从0到1

    需求功能点

    1.连接MetaMask小狐狸钱包;

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

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

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

    开发功能

    1.连接MetaMask

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

     具体代码

    1. //写在了store的action里
    2. //连接小狐狸钱包
    3. export const connectWallet= async ({ commit }) => {
    4. let web3Provider;
    5. if (window.ethereum) {
    6. web3Provider = window.ethereum;
    7. try {
    8. //通过
    9. const addressArray = await web3Provider.request({
    10. method: "eth_requestAccounts",
    11. });
    12. let address = addressArray[0];
    13. const obj = {
    14. status: "👆🏽 Write a message in the text-field above.",
    15. address: address,
    16. };
    17. setProvider({commit},address);
    18. addWalletListener({commit});
    19. return obj;
    20. } catch (err) {
    21. return {
    22. address: "",
    23. status: "😥 " + err.message,
    24. };
    25. }
    26. } else {
    27. return {
    28. address: "",
    29. status: (
    30. <span>
    31. <p>
    32. {" "}
    33. 🦊{" "}
    34. <a target="_blank" href={`https://metamask.io/download.html`}>
    35. You must install Metamask, a virtual Ethereum wallet, in your
    36. browser.
    37. a>
    38. p>
    39. span>
    40. ),
    41. };
    42. }
    43. };
    44. //获得当前连接的账户
    45. export const getCurrentWalletConnected= async ({ commit }) => {
    46. let web3Provider;
    47. if (window.ethereum) {
    48. web3Provider = window.ethereum;
    49. try {
    50. const addressArray = await web3Provider.request({
    51. method: "eth_accounts",
    52. });
    53. if (addressArray.length > 0) {
    54. let address = addressArray[0];
    55. setProvider({commit},address);
    56. addWalletListener({commit});
    57. return {
    58. address: addressArray[0],
    59. status: "👆🏽 Write a message in the text-field above.",
    60. };
    61. } else {
    62. return {
    63. address: "",
    64. status: "🦊 Connect to Metamask using the top right button.",
    65. };
    66. }
    67. } catch (err) {
    68. return {
    69. address: "",
    70. status: "😥 " + err.message,
    71. };
    72. }
    73. } else {
    74. return {
    75. address: "",
    76. status: (
    77. <span>
    78. <p>
    79. {" "}
    80. 🦊{" "}
    81. <a target="_blank" href={`https://metamask.io/download.html`}>
    82. You must install Metamask, a virtual Ethereum wallet, in your
    83. browser.
    84. a>
    85. p>
    86. span>
    87. ),
    88. };
    89. }
    90. };

    2.监听账户变化

    1. //使用方法web3Provider.on('accountsChanged', accounts => {})
    2. export const addWalletListener = ({commit}) => {
    3. let web3Provider;
    4. if (window.ethereum) {
    5. web3Provider = window.ethereum;
    6. web3Provider.on('accountsChanged', accounts => {
    7. SET_ACCOUNT({commit},accounts[0]);
    8. //断开链接后,初始化一些值
    9. if(accounts.length===0){
    10. //使用store的commit改变数据状态
    11. SET_PROVIDER({commit},{});
    12. SET_CONTRACTS({commit},{});
    13. SET_IS_CONNECT_WALLET({commit},false);
    14. SET_SIGNER({commit},{});
    15. SET_BALANCE({commit},'0.0');
    16. }
    17. })
    18. }
    19. };

    3.provider和合约对象

    1. //获得provider,contract,signer对象,改变store的数据状态,全局使用
    2. export const setProvider = ({commit},address) => {
    3. let web3Provider;
    4. if (window.ethereum) {
    5. web3Provider = window.ethereum;
    6. const provider = new ethers.providers.Web3Provider(web3Provider);
    7. const signer = provider.getSigner();
    8. const contractABI = require("../config/constants/contract-abi.json");
    9. const wethAddress = getWethAddress();
    10. const daiContract = new ethers.Contract(wethAddress, contractABI, provider);
    11. //commit('saveAccountStore', address);//另外一种方式
    12. SET_ACCOUNT({commit},address);
    13. SET_PROVIDER({commit},provider);
    14. SET_CONTRACTS({commit},daiContract);
    15. SET_IS_CONNECT_WALLET({commit},true);
    16. SET_SIGNER({commit},signer);
    17. }
    18. };

     4.唤起MetaMask签名

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

      

    5.链上转账、余额查询

    1. 1.转账
    2. export const sendTransfer = async (store,amount,to) => {
    3. let isConnectWallet = store.getters.isConnectWallet;
    4. if(isConnectWallet){
    5. let daiContract = store.getters.contracts;
    6. let signer = store.getters.signer;
    7. const daiWithSigner = daiContract.connect(signer);
    8. const dai = ethers.utils.parseUnits(amount.toString(), 18);
    9. //方式一,传参传一个promise 一个方法
    10. // daiWithSigner.transfer(to, dai).then((resp) => {}).catch((error) => {
    11. // errHandler(error);
    12. // });
    13. //方式二,直接返回这个promise
    14. return daiWithSigner.transfer(to, dai);
    15. }
    16. };
    17. 2.余额查询
    18. export const getBalance = async (store) => {
    19. let balanceStr = '0.0';
    20. let isConnectWallet = store.getters.isConnectWallet;
    21. if(isConnectWallet){
    22. let address = store.getters.account;
    23. let daiContract = store.getters.contracts;
    24. if(daiContract.balanceOf!==undefined){
    25. const balance = await daiContract.balanceOf(address);
    26. let balanceStr = ethers.utils.formatUnits(balance, 18);
    27. store.dispatch('SET_BALANCE', balanceStr);
    28. }
    29. }
    30. return {balance: balanceStr,};
    31. };


    总结

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

  • 相关阅读:
    算法D38 | 动态规划1 | 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯
    365天深度学习 | 第7周:咖啡豆识别
    微软外服工作札记③——窗口函数的介绍
    PlayBook 详解
    实战:typora里面如何快捷改变字体颜色(博客分享-完美)-2022.6.25(已解决)
    maven resources与配置分离
    C# Winform+Halcon结合标准视觉工具
    SpringBoot项目结合mybatis generator自动生成代码
    PDF提取 PDF截取
    交换奇偶位
  • 原文地址:https://blog.csdn.net/xieedeni/article/details/126342914