因为公司的项目接入了几个钱包项目,现在来总结下; 接入的钱包有: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;
};
其实只用关注login
、getAccountInfo
和sendTransaction
,这些是钱包的核心功能,其他的是我为了方便项目上的某些功能而实现的,比方getCurrentCurrency
是通过链 id 判断当前是什么货币,这些功能不需要太关注。
接下来是钱包的实现
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 钱包给页面注入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 钱包没有找到,也许它没有实现这个。
以上两个钱包都是 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.sendTransaction
的 data
参数吗,把你生成的字符串作为 data
参数, 那么在转账成功之后就会执行调用someFun(param1, param2)
, 这真的是太神奇了。