Skip to content

Instantly share code, notes, and snippets.

@zsytssk
Created June 18, 2021 10:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zsytssk/8fbe6309149f39e1e3aee7440a19a763 to your computer and use it in GitHub Desktop.
Save zsytssk/8fbe6309149f39e1e3aee7440a19a763 to your computer and use it in GitHub Desktop.

因为公司的项目接入了几个钱包项目,现在来总结下; 接入的钱包有:MetaMask,Binance, WalletConnect; 钱包的基本功能有登录、获取余额、转账,为此我写了一个钱包类型实现这些功能,当时是想如果后面有新的钱包接入只要实现这些接口就可以了,后面接入新的钱包真的是很方便,只需实现这些接口,基本不用改动页面的其他地方。以下是钱包的接口:

/** 智能合约钱包账户信息 */
type WalletAccountInfo = {
  name: string;
  address: string;
  balance: number;
  currency: string;
  icon: string;
};

/** 智能合约钱包接口 */
type Wallet = {
  supportCurrencies: string[];
  isSupport: () => Promise<[boolean, WalletError?]>;
  login: () => Promise<[boolean, WalletError?]>;
  getCurrentCurrency: () => Promise<string>;
  getAccountInfo: () => Promise<WalletAccountInfo>;
  sendTransaction: (
    toAddress: string,
    amount: number,
    data?: string
  ) => Promise<boolean>;
  getLink: (address: string) => Promise<[string, string]>;
  getTip: (lang: string) => React.ReactNode;
  logout?: () => Promise<void>;
  getWeb3?: () => any;
};

其实只用关注logingetAccountInfosendTransaction,这些是钱包的核心功能,其他的是我为了方便项目上的某些功能而实现的,比方getCurrentCurrency是通过链 id 判断当前是什么货币,这些功能不需要太关注。

接下来是钱包的实现

Metamask

Metamask 钱包给页面注入了ethereum变量, 用这个来调用钱包的功能;比方说登录就是如此

ethereum
  .request({ method: "eth_requestAccounts" })
  .then((data: any) => {
    ...
  })
  .catch(() => {
    ...
  });

钱包的所有功能都有类似的方法调用,具体可以查看 官方文档; 但是我为了统一调用方法方便并没有使用这些方法而是使用web3, web3统一了所有钱包的调用方式,每个类 ETH 钱包都有自身的实现;比方 Metamask 的创建 web3 就是如此:

import Web3 from "web3";
...
const web3 = new Web3(ethereum);

获取帐户

const address = await web3.eth.getAccounts().then((e: string[]) => e[0]);

获取余额

web3?.eth.getBalance(address, (err: any, balance: any) => {
  if (err) {
    return reject(err);
  }
  resolve(web3.utils.fromWei(balance, "ether"));
});

转账

web3.eth.sendTransaction(
  {
    to: contractAddress, // 目标帐户地址
    from: ethereum.selectedAddress, // 转账帐户
    value: web3.utils.toWei(amount + ""), // 转账金额
    data, // 一些数据,在后面有用法
  },
  (err: any, data: any) => {
    ...
  }
);

以上就是钱包的核心功能,我在做项目的时候发现有些其他必要的功能需要说明下:

监听帐户切换和链切换刷新页面或者其他的处理(这些事件有时候不起作用,这应该是官方插件的 bug)

ethereum.on("chainChanged", () => {
  ...
});
ethereum.on("disconnect", () => {
  ...
});
ethereum.on("accountsChanged", (accounts: any) => {
  ...
});

让钱包切换当前链,如果没有就添加这条链, 比方添加 BSC 链,手动添加 BSC 的方法参考文档

ethereum.request({
  method: "wallet_addEthereumChain",
  params: [
    {
      chainId: "0x38",
      chainName: "Binance Smart Chain Mainnet",
      nativeCurrency: { name: "BNB", symbol: "bnb", decimals: 18 },
      rpcUrls: [
        "https://bsc-dataseed1.ninicoin.io",
        "https://bsc-dataseed1.defibit.io",
        "https://bsc-dataseed.binance.org",
      ],
      blockExplorerUrls: ["https://bscscan.com/"],
    },
  ],
});

Binance 钱包

Binance 钱包给页面注入BinanceChain对象,它的调用方法和 Metamask 类似;

登录

BinanceChain.request({ method: "eth_requestAccounts" })
  .then((data: string) => {
    return ...
  })
  .catch((err: any) => {
    return ...
  });

初始化 web3

import Web3 from "web3";
...
const bsc = new BscConnector({
  supportedChainIds: [56, 97],
});
const { provider } = await bsc.activate();
const web3 = new Web3(provider);

它的获取帐户地址、余额以及转账的方法使用 web3 和 metamask 一样,这里就不写了; 同样 Binance 钱包也有自身的调用方式,可以参考官方文档

监听链切换和帐户切换

BinanceChain.on("chainChanged", () => {
  ...
});
BinanceChain.on("accountsChanged", () => {
  ...
});
BinanceChain.on("disconnect", () => {
  ...
});

让钱包切换当前链这个功能我在 Binance 钱包没有找到,也许它没有实现这个。

WalletConnect

以上两个钱包都是 pc 端的浏览器插件,如果是移动端就没有办法了,移动端有一个 walletConnect 协议实现了这个功能;walletConnect 支持 pc 和移动端,pc 是打开一个二维码,然后让支持 walletConnect 钱包扫描下然后钱包和网页就绑定上了,移动端会直接打开相关的软件;你必须先下载支持的钱包 app,我用的是 Metamask;我用 metamask 使用 walletConnect 发现这个链接并不稳定,有时候可以连上有时候不行,这可能和 walletConnect 现在在 Metamask 中是实验功能有关;也有可能是 walletConnect 要和那些链通信,一旦网络不稳定就会出现问题。

我是使用 @walletconnect/web3-provider调用 walletConnect 功能,在调用之前必须先创建ethereum对象;创建方法:

import WalletConnectProvider from "@walletconnect/web3-provider";
...
const rpc = getRpc(id);
const ethereum = new WalletConnectProvider({
  bridge: `//${bridgeUrl}`,
  chainId: id,
  rpc: {
    [id]: rpc,
  },
  qrcodeModalOptions: {
    mobileLinks: [
      "rainbow",
      "metamask",
      "argent",
      "trust",
      "imtoken",
      "pillar",
    ],
  },
});

id是指定的链的 id,pc 浏览器插件可以指定当前钱包连接是哪一个链,walletConnect 在连接时必须指定要连接的链,getRpc是获取官方提供的 Rpc 地址;

function getRpc(id: number) {
  const map = {
    // eth主链的id和rpc地址
    1: "https://mainnet.infura.io/v3/9f80438472d14c53ac6cf342caf8d016",
    // eth测试链rinkeby的id和rpc地址
    4: "https://rinkeby.infura.io/v3/9f80438472d14c53ac6cf342caf8d016",
    // eth测试链kovan的id和rpc地址
    42: "https://kovan.infura.io/v3/9f80438472d14c53ac6cf342caf8d016",
    // BSC主链的id和rpc地址
    "56": [
      "https://bsc-dataseed.binance.org/",
      "https://bsc-dataseed1.defibit.io/",
      "https://bsc-dataseed1.ninicoin.io/",
      "https://bsc-dataseed2.defibit.io/",
      "https://bsc-dataseed3.defibit.io/",
      "https://bsc-dataseed4.defibit.io/",
      "https://bsc-dataseed2.ninicoin.io/",
      "https://bsc-dataseed3.ninicoin.io/",
      "https://bsc-dataseed4.ninicoin.io/",
      "https://bsc-dataseed1.binance.org/",
      "https://bsc-dataseed2.binance.org/",
      "https://bsc-dataseed3.binance.org/",
      "https://bsc-dataseed4.binance.org/",
    ],
    // BSC测试链的id和rpc地址
    "97": [
      "https://data-seed-prebsc-1-s1.binance.org:8545/",
      "https://data-seed-prebsc-2-s1.binance.org:8545/",
      "https://data-seed-prebsc-1-s2.binance.org:8545/",
      "https://data-seed-prebsc-2-s2.binance.org:8545/",
      "https://data-seed-prebsc-1-s3.binance.org:8545/",
      "https://data-seed-prebsc-2-s3.binance.org:8545/",
    ],
  };
  const item = map[id + ""];
  if (typeof item === "string") {
    return item;
  }
  return item[Math.floor(Math.random() * item.length)];
}

bridgeUrl是来沟通钱包和网页的桥接地址,详细可以参看官方说明; 这个 bridge 需要服务端去搭建一个 socket 服务,官方也提供了 bridge 的代码

创建ethereum之后调用就和浏览器插件类似了

创建 web3

import Web3 from "web3";
...
const web3 = new Web3(ethereum as any);

登录获、取帐户余额、转账这个要看你连的是什么钱包,使用方法和钱包的一摸一样,我还是用web3调用用的

高级玩法

调用合约方法

流动性挖矿是区块链的一个新的玩法,公司需要建立一个新的合约,同时合约上必须实现某些方法,这样前端才可以调用。前端在使用的时候必须先创建一个合约对象

const contract = new web3.eth.Contract(minABI, lpAddress);

其中lpAddress是合约地址,minABI是前端要调用的合约方法,这必须链上要先实现了这些方法,前端才可以调用。例如下面, 声明someFun方法 :

const minABI = [
  {
    inputs: [
      { internalType: "string", name: "gID1", type: "string" },
      { internalType: "string", name: "oID1", type: "string" },
    ],
    name: "someFun",
    outputs: [],
    stateMutability: "payable",
    type: "function",
  },
];

他的调用方式如下

contract.methods
  .someFun(param1, param2) // address 是用户的地址
  .call()
  .then((data: number) => {
    ...
  });

还可以将方法转义成字符串

const data = contract.methods.someFun(param1, param2).encodeABI();

还记得前面web3.eth.sendTransactiondata 参数吗,把你生成的字符串作为 data参数, 那么在转账成功之后就会执行调用someFun(param1, param2), 这真的是太神奇了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment