草庐IT

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

全能打工人 2023-05-11 原文

目录

前言

一、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后可以获取provider
const provider = new ethers.providers.Web3Provider(window.ethereum)

2.获取signer
const 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 mnemonic
let 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 mainnet
let 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 interface
let 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.字符串转成BigNumber
let amount = 1000;
let amountBig = ethers.utils.parseUnits(amount.toString(), 18);

3.校验是否为以太坊账户地址
//返回true或者false
let 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: (
        <span>
          <p>
            {" "}
            🦊{" "}
            <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
      ),
    };
  }
};

//获得当前连接的账户
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: (
        <span>
          <p>
            {" "}
            🦊{" "}
            <a target="_blank" href={`https://metamask.io/download.html`}>
              You must install Metamask, a virtual Ethereum wallet, in your
              browser.
            </a>
          </p>
        </span>
      ),
    };
  }
};

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钱包前端门户的开发目前总结到此,待学习后续。

有关自学Vue开发Dapp去中心化钱包(四)的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  5. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. 计算机毕业设计ssm+vue基本微信小程序的小学生兴趣延时班预约小程序 - 2

    项目介绍随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱小学生兴趣延时班预约小程序的设计与开发被用户普遍使用,为方便用户能够可以随时进行小学生兴趣延时班预约小程序的设计与开发的数据信息管理,特开发了小程序的设计与开发的管理系统。小学生兴趣延时班预约小程序的设计与开发的开发利用现有的成熟技术参考,以源代码为模板,分析功能调整与小学生兴趣延时班预约小程序的设计与开发的实际需求相结合,讨论了小学生兴趣延时班预约小程序的设计与开发的使用。开发环境开发说明:前端使用微信微信小程序开发工具:后端使用ssm:VU

  9. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

  10. ruby-on-rails - environment.rb 中设置的常量在开发模式中消失 - 2

    了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl

随机推荐