21个基于ethers的Dapp常用工具函数

2022-04-08 14:16:37 浏览数 (1)

本文作者:webkubor[1]

使用 ethers.js 开发区块链 dapp

基本依赖

代码语言:javascript复制
import BN from "bn.js";
import { ethers } from "ethers";
import _ from "lodash";

交易金额处理

预处理数据金额(有 bug 例如 0.0079,精度 18 转化)

代码语言:javascript复制
/**
 * @description: 大数据处理使用BN(小数点输出值不准确), 小数点类使用 lodash
 * @param {*} amount
 * @param {*} tokenDecimals
 * @return {*}
 */
export function toAmount(amount, tokenDecimals = 18) {
  let result = 0;
  if (amount * 1 < 100) {
    result = _.multiply(amount * 1, 10 ** tokenDecimals).toString();
    console.log(result, amount, "use--safeMath");
  } else {
    let points = (10 ** tokenDecimals).toString();
    let amountStr = amount.toString();
    result = new BN(amountStr).mul(new BN(points)).toString();
    console.log(result, amount, "use--BN");
  }
  return result;
}

预处理数据金额解决方案

代码语言:javascript复制
import { ethers } from "ethers";
/**
 * @description: 大数据处理使用ethers
 * @param {*} amount
 * @param {*} tokenDecimals
 * @return {*}
 */
export function toAmount(amount, tokenDecimals = 18) {
  return ethers.utils.parseUnits(amount, tokenDecimals)
}

从合约拉取数据转化 Bignumber => 10 进制字符串(不处理精度)

代码语言:javascript复制
/**
 * @description: Bignumber => 10进制字符串
 * @param {*} object
 * @return {*}
 */
export function transBigNumber(object) {
  let isBig = ethers.BigNumber.isBigNumber(object);
  if (isBig) {
    return object.toString();
  }
  return object;
}

从合约拉取数据转化 Bignumber => 10 进制字符串(处理精度)

代码语言:javascript复制
export function receiveAmount(amount, tokenDecimals = 18) {
  let inNumber = transBigNumber(amount);
  let outAmount = ethers.utils.formatUnits(inNumber, tokenDecimals); //处理原数据
   outAmount = dealDecimals(outAmount, 10); // 截取有效数据
  return outAmount * 1;
}

错误处理

归集处理错误

代码语言:javascript复制
import { Message } from "@/hooks/useMessage";

/**
 * @description: rpc错误配置处理
 * @param {*} error
 * @return {*}
 */
export function handleRpcError(error) {
  if (!error) {
    console.error("other RpcError", "local handleRpcError");
    return;
  }

  if (error.data && error.data.message) {
    Message.error(error.data.message);
    return;
  }

  if (error.code === -32603) {
    Message.error("Internal JSON-RPC error");
    return;
  }

  if (error.code === 4001) {
    Message.error("Transaction rejected!");
    return;
  }

  if (error.message) {
    Message.error(error.message);
    return;
  }
  Message.error("Unexpected interruption of transaction");
}

调用链相关

获取块高度

代码语言:javascript复制
/**
 * @description: 获取块高度
 * @param {*}
 * @return {*}
 */
export async function getBlockNumber() {
  if (!window.ethereum) return;
  let currentBLock = await window.ethereum.request({
    method: "eth_getBlockByNumber",
    params: ["latest", false]
  });
  currentBLock = Number(currentBLock.number).toString();
  return currentBLock;
}

账号相关

校验 token Address 是否合法

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/go/doc/48378172[2]

大小写地址矫正

代码语言:javascript复制
/**
 * @description:  fix address lowercase letters
 * @param {*} address
 * @return {*}
 */
export function transLegalAddress(address) {
  const account = ethers.utils.getAddress(address);
  return account;
}

调用智能合约相关

可读可写权限-执行合约

代码语言:javascript复制
/**
 * @description: 基于ether.js返回的合约对象
 * @param {*} tokenJson ABI文件
 * @param {*} contractAddress 合约地址
 * @return {*}
 */
export async function outContract(tokenJson, contractAddress) {
  let ethersProvider = new ethers.providers.Web3Provider(
    window.ethereum,
    "any"
  );
  if (account.value) {
    const Contract = new ethers.Contract(
      contractAddress,
      tokenJson,
      ethersProvider.getSigner()
    );
    return Contract;
  } else {
    let contracts = getContractAddress();
    let ethersProvider = new ethers.providers.JsonRpcProvider(contracts.rpcURL);
    const Contract = new ethers.Contract(
      contractAddress,
      tokenJson,
      ethersProvider
    );
    return Contract;
  }
}

// 使用实例-读
export async function getPoolRewards(account) {
  const contract = await outContract(tokenJson, contractAddress.PoolGuardian);
  let getPoolRewards = await contract.getPoolRewards(account);
  return {
    createRewards: Transfer.receiveAmount(getPoolRewards._createReward, 18),
    createPools: Transfer.transBigNumber(getPoolRewards._createPools),
    stakingRewards: Transfer.receiveAmount(getPoolRewards._stakingReward, 18),
    stakingPools: Transfer.transBigNumber(getPoolRewards._stakingPools)
  };
}

// 使用实例-写
export async function deposit(poolId, tokenAddress, value) {
  const contract = await await outContract(tokenJson, contractAddress.PoolGuardian);
  let _decimals = await decimals(tokenAddress);
  let Amount = Transfer.toAmount(value, _decimals);
  const tx = await contract.deposit(poolId, Amount);
  let deposit = await tx.wait();
  return deposit;
}

并发调用

需要在目标链上部署好并发调用的工具合约

bsc 测试链:

multicall: "0xc8aeA8381c6679Ac49E7e7ff638aEe10c6Ff3122",

代码语言:javascript复制
import { Provider, setMulticallAddress } from "ethers-multicall";


/**
 * @description: 根据链ID直接获取合约配置
 * @param {*}
 * @return {*}
 */
export function getContractAddress() {
  let chainId = chain.id ? chain.id : wallet.defaultChainId;
  const contractAddress = contractMap[chainId];
  if (contractAddress?.support) {
    return contractAddress;
  } else {
    return null;
  }
}

export async function getProvider() {
  let contractAddress = getContractAddress();
  if (!contractAddress.rpcURL) {
    console.log("contractAddress.rpcURL is null");
    return;
  }
  if (!contractAddress.multicall) {
    console.log("contractAddress.multicall address is null");
    return;
  }
  if (!contractAddress.chainId) {
    console.log("contractAddress.chainId is null");
    return;
  }
  const provider = new ethers.providers.JsonRpcProvider(contractAddress.rpcURL);
  const ethcallProvider = new Provider(provider);
  setMulticallAddress(contractAddress.chainId, contractAddress.multicall);
  await ethcallProvider.init();
  return ethcallProvider;
}

//使用实例



/**
 * @description: 获取所有的在线的池子信息
 * @param {*}
 * @return {*}
 */
export async function fetchOnlinePools() {
  let contractAddress = getContractAddress();
  let ids = await getOnlinePoolIds();
  let ethcallProvider = await getProvider();
  const targetContract = new Contract(contractAddress.PoolGuardian, tokenJson);
  const poolInfoCalls = ids.map(poolId => targetContract.getPoolInfo(poolId));
  const poolInfoList = await ethcallProvider.all(poolInfoCalls);
  let resultList = poolInfoList.map(poolInfo => {
    return {
      id: Transfer.transBigNumber(poolInfo._id),
      tokenAddress: poolInfo._stakedTokenAddr,
      creator: poolInfo._creator,
      tokenName: poolInfo._name,
      decimals: poolInfo._decimal,
      durationDays: Transfer.transBigNumber(poolInfo._durationDays),
      endBlock: Transfer.transBigNumber(poolInfo._endBlock),
      startBlock: Transfer.transBigNumber(poolInfo._endBlock),
      maxLeverage: Transfer.transBigNumber(poolInfo._leverage),
      stateFlag: Transfer.transBigNumber(poolInfo._stateFlag)
    };
  });
  return resultList;
}

根据块节点获取历史日志

此处为语雀内容卡片,点击链接查看:https://www.yuque.com/webkubor/blockchain/xnunwx[3]

钱包工具相关

metamask

简介

Metamask[4] 是 Chrome 和 Firefox 的浏览器扩展, 它能让用户安全地维护他们的以太坊账户和私钥 , 并用他们的账户和使用 Web3.js 的网站互动(如果你还没用过它,你肯定会想去安装的——这样你的浏览器就能使用 Web3.js 了,然后你就可以和任何与以太坊区块链通信的网站交互了)

作为开发者,如果你想让用户从他们的浏览器里通过网站和你的 DApp 交互(就像我们在 CryptoZombies 游戏里一样),你肯定会想要兼容 Metamask 的。

注意: Metamask 默认使用 Infura 的服务器做为 web3 提供者。 就像我们上面做的那样。不过它还为用户提供了选择他们自己 Web3 提供者的选项。所以使用 Metamask 的 web3 提供者,你就给了用户选择权,而自己无需操心这一块。

小狐狸显示状态-连接钱包的状态

连接钱包的地址:

https://app.uniswap.org/#/swap[5]

是否安装 metamsk
代码语言:javascript复制
/**
 * @description: check metamsk
 * @param {*}
 * @return {bool}
 */
export function isMetaMask() {
  const { ethereum } = window;
  return Boolean(ethereum && ethereum.isMetaMask);
}
获取 chainid
代码语言:javascript复制
/**
 * @description: 获取chainid
 * @param {*}
 * @return {*}
 */
async function getChainId() {
  const { ethereum } = window;
  try {
    const chainId = await ethereum.request({
      method: "eth_chainId"
    });
    handleNewChain(chainId);
  } catch (err) {
    console.error(err);
  }
}
主动切换到以太坊网络
代码语言:javascript复制
/**
 * @description: Switch to the primary network (may be deprecated in the future)
 * @param {*}
 * @return {*}
 */
async function switchToEthereum() {
  try {
    await window.ethereum.request({
      method: "wallet_switchEthereumChain",
      params: [
        {
          chainId: "0x1"
        }
      ]
    });
  } catch (error) {
    console.log(error);
  }
}
主动切换到其余链配置
代码语言:javascript复制
/**
 * @description: Switch to the rest of the network (mainly used)
 * @param {*} findChain
 * @return {*}
 */
async function switchToOtherNetwork(findChain) {
  const data = [];
  data.push(findChain);
  console.log(findChain, "switchNetwork");
  try {
    await window.ethereum.request({
      method: "wallet_addEthereumChain",
      params: data // [{XXXXX}]  is Array
    });
  } catch (error) {
    console.log(error);
  }
}
监听链上钱包配置
代码语言:javascript复制
/**
 * @description: handelConnectInfo
 * @param {*} info
 * @return {*}
 */
function handelConnectInfo(info) {
  console.log(info, "handelConnectInfo");
}

/**
 * @description: handleDisConnect
 * @param {*} disconnect
 * @return {*}
 */
function handleDisConnect(disconnect) {
  console.log(disconnect, "handleDisConnect");
}

/**
 * @description: handleNewAccount
 * @param {*} account
 * @return {*}
 */
function handleNewAccount(account) {
  updateAccount(account[0]);
}

/**
 * @description: handelNewMessage
 * @param {*} msg
 * @return {*}
 */
function handelNewMessage(msg) {
  console.log(msg, "handelNewMessage");
}

/**
 * @description: monitor metamsk
 * @param {*}
 * @return {*}
 */
function _listeningMetamsk() {
  const { ethereum } = window;

  ethereum.on("chainChanged", handleNewChain);

  ethereum.on("accountsChanged", handleNewAccount);

  ethereum.on("message", handelNewMessage);

  ethereum.on("connect", throttle(handelConnectInfo, 1000));

  ethereum.on("disconnect", throttle(handleDisConnect, 1000));
}
添加自定义代币到 metamask
代码语言:javascript复制
// metmask提供的可以直接在memask中添加自定义代币的方法
async function addToken() {
      let contractAddress = getContractAddress();
      window.ethereum
        .request({
          method: "wallet_watchAsset",
          params: {
            type: "ERC20",
            options: {
              address: contractAddress.IPISTR,
              symbol: "IPISTR",
              decimals: 18,
              image: "https://img.yuanmabao.com/zijie/pic/2022/04/08/pwpen2e2nzt"
            }
          }
        })
        .then(success => {
          if (success) {
            Message.success("IPISTR Token added successfully!");
          } else {
            Message.error("Something went wrong.");
          }
        })
        .catch(console.error);
    }
综合实例-连接钱包
代码语言:javascript复制
export async function getMetamskConnect() {
  if (!isMetaMask()) {
    openUrl("https://metamask.io/", "install metamsk");
  }
  if (window.ethereum) {
    window.provider = window.ethereum;
    try {
      let accounts = await window.ethereum.request({
        method: "eth_requestAccounts"
      });
      await updateAccount(accounts[0]);
    } catch (error) {
      console.warn("Please authorize to access tour account");
    }
  }
  await getChainId(); //获取chanid参数
  _listeningMetamsk();
}
综合实例-切换网络

参考文献

https://ethereum-magicians.org/t/eip-3326-wallet-switchethereumchain/5471/3[6]

解决方案

https://web03.cn/blog/257[7]

代码语言:javascript复制
/**
 * @description: switch or add rpcNetwork
 * @param {*} id chan_id(16)
 * @return {*}
 */
export async function switchNetwork(id) {
  let findChain = nativeMetamaskMap.find(v => v.chainId === id);
  if (!window.ethereum) {
    Message.warning("Please install metamsk");
    return;
  }
  if (!findChain) {
    Message.warning("The current website does not support the chain");
    return;
  }
  if (id === "0x1") {
    switchToEthereum();
  } else {
    switchToOtherNetwork(findChain);
  }
}


/**
 * @description: matemask config
 * @param {*}
 * @return {*}
 */
export const nativeMetamaskMap = [
  {
    chainId: "0x1",
    chainName: "Ethereum Mainnet",
    nativeCurrency: {
      name: "Ether",
      symbol: "ETH",
      decimals: 18
    },
    rpcUrls: ["https://mainnet.infura.io/v3/"],
    blockExplorerUrls: ["https://etherscan.io/"]
  },
  {
    chainId: "0x80",
    chainName: "huobi Network",
    nativeCurrency: {
      name: "HT",
      symbol: "HT",
      decimals: 18
    },
    rpcUrls: ["https://http-mainnet-node.huobichain.com"],
    blockExplorerUrls: ["https://hecoinfo.com"]
  },
  {
    chainId: "0x38",
    chainName: "Binance Smart Chain",
    nativeCurrency: {
      name: "BNB",
      symbol: "BNB",
      decimals: 18
    },
    rpcUrls: ["https://bsc-dataseed.binance.org/"],
    blockExplorerUrls: ["https://bscscan.com/"]
  },
  {
    chainId: "0x61",
    chainName: "BSC-Test-Network",
    nativeCurrency: {
      name: "BNB",
      symbol: "BNB",
      decimals: 18
    },
    rpcUrls: ["https://data-seed-prebsc-2-s3.binance.org:8545"],
    blockExplorerUrls: ["https://testnet.bscscan.com"]
  },
  {
    chainId: "0x539",
    chainName: "Local-Test-Network",
    nativeCurrency: {
      name: "Ether",
      symbol: "ETH",
      decimals: 18
    },
    rpcUrls: ["https://mainnet.infura.io/v3/"],
    blockExplorerUrls: ["https://etherscan.io/"]
  }
];

walletconnect

WalletConnect 由同名非盈利组织 WalletConnect 基金会支持开发的一套开源协议。WalletConnect 的开发团队实力雄厚,团队负责人 Pedro Gomes 曾经是http://Balance.io[8](ETH DeFi 入口级产品)的网页端全栈工程师 ,在 2018 年的时候,他离开http://Balance.io,全职投入 WalletConnect 的开发,才有了如今的 WalletConnect 开源协议。WalletConnect 开源协议主要用于端到端的加密,提高数字钱包的易用性,给用户更加轻松、安全的体验感。

基本依赖
代码语言:javascript复制
import WalletConnect from "@walletconnect/client";
import QRCodeModal from "@walletconnect/qrcode-modal";
激活主动连接-弹出二维码
代码语言:javascript复制
const getWallectConnect = async () => {
    // bridge url
    const bridge = "https://bridge.walletconnect.org";

    // create new connector
    const connector = new WalletConnect({ bridge, qrcodeModal: QRCodeModal });

    walletconnect.connector = connector;
    // check if already connected
    if (!connector.connected) {
      // create new session
      await connector.createSession();

      if (localStorage.metamskconnect) delete localStorage.metamskconnect;
    }

    // subscribe to events
    await subscribeToEvents();
  };
断开链接
代码语言:javascript复制
const onDisconnect = () => {
    walletconnect.walletconnect = null;
  };
事件监听
代码语言:javascript复制
const subscribeToEvents = () => {
    const { connector } = walletconnect;

    if (!connector) {
      return;
    }

    connector.on("session_update", async (error, payload) => {
      console.log(`connector.on("session_update")`, payload);

      if (error) {
        throw error;
      }

      const { chainId, accounts } = payload.params[0];
      onSessionUpdate(accounts, chainId);
    });

    connector.on("connect", (error, payload) => {
      console.log(`connector.on("connect")`);

      if (error) {
        throw error;
      }

      onConnect(payload);
    });

    connector.on("disconnect", (error, payload) => {
      console.log("%c%s", "color: #ffa640", payload);
      Message.warning("wallectconnect disconnect");

      if (error) {
        throw error;
      }

      onDisconnect();
    });

    if (connector.connected) {
      const { chainId, accounts } = connector;
      onSessionUpdate(accounts, chainId);
    }
    walletconnect.connector = connector;
  };

参考资料

[1]

webkubor: https://learnblockchain.cn/people/2871

[2]

https://www.yuque.com/go/doc/48378172: https://www.yuque.com/go/doc/48378172

[3]

https://www.yuque.com/webkubor/blockchain/xnunwx: https://www.yuque.com/webkubor/blockchain/xnunwx

[4]

Metamask: https://metamask.io/

[5]

https://app.uniswap.org/#/swap: https://app.uniswap.org/#/swap

[6]

https://ethereum-magicians.org/t/eip-3326-wallet-switchethereumchain/5471/3: https://ethereum-magicians.org/t/eip-3326-wallet-switchethereumchain/5471/3

[7]

https://web03.cn/blog/257: https://web03.cn/blog/257

[8]

http://Balance.io: http://Balance.io

0 人点赞