本文作者: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