UNPKG

@evolutionland/evolution-js

Version:

evolution evolution-js evolutionland evolution-js-sdk evolution-land metaverse

554 lines (458 loc) 24.6 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>uniswap/index.ts - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc.css"> <script src="scripts/nav.js" defer></script> <meta name="viewport" content="width=device-width, initial-scale=1.0"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav > <h2><a href="index.html">Home</a></h2><h3>Global</h3><ul><li><a href="global.html#apostleAllowanceToClockAuction">apostleAllowanceToClockAuction</a></li><li><a href="global.html#apostleAskWithToken">apostleAskWithToken</a></li><li><a href="global.html#apostleBidWithToken">apostleBidWithToken</a></li><li><a href="global.html#apostleBorn">apostleBorn</a></li><li><a href="global.html#apostleBornAndEnhance">apostleBornAndEnhance</a></li><li><a href="global.html#apostleBreed">apostleBreed</a></li><li><a href="global.html#apostleBreedAsk">apostleBreedAsk</a></li><li><a href="global.html#apostleBreedBid">apostleBreedBid</a></li><li><a href="global.html#apostleCancelAskWithToken">apostleCancelAskWithToken</a></li><li><a href="global.html#apostleCancelBreedAsk">apostleCancelBreedAsk</a></li><li><a href="global.html#apostleCancelHire">apostleCancelHire</a></li><li><a href="global.html#apostleClaim">apostleClaim</a></li><li><a href="global.html#apostleClaimHire">apostleClaimHire</a></li><li><a href="global.html#apostleGetAutoBreedFee">apostleGetAutoBreedFee</a></li><li><a href="global.html#apostleGetCurrentPriceByTokenId">apostleGetCurrentPriceByTokenId</a></li><li><a href="global.html#apostleGetCurrentSiringPriceByTokenId">apostleGetCurrentSiringPriceByTokenId</a></li><li><a href="global.html#apostleHire">apostleHire</a></li><li><a href="global.html#apostleHireBid">apostleHireBid</a></li><li><a href="global.html#apostleStopWork">apostleStopWork</a></li><li><a href="global.html#apostleTransfer">apostleTransfer</a></li><li><a href="global.html#apostleWork">apostleWork</a></li><li><a href="global.html#drillStopWork">drillStopWork</a></li><li><a href="global.html#drillTransfer">drillTransfer</a></li><li><a href="global.html#drillWork">drillWork</a></li><li><a href="global.html#equipmentTransfer">equipmentTransfer</a></li><li><a href="global.html#equipmentUnequip">equipmentUnequip</a></li><li><a href="global.html#erc20Allowance">erc20Allowance</a></li><li><a href="global.html#erc20AllowanceByContractAddress">erc20AllowanceByContractAddress</a></li><li><a href="global.html#erc20Approve">erc20Approve</a></li><li><a href="global.html#erc20ApproveByContractAddress">erc20ApproveByContractAddress</a></li><li><a href="global.html#erc20BalanceOf">erc20BalanceOf</a></li><li><a href="global.html#erc20TotalSupply">erc20TotalSupply</a></li><li><a href="global.html#erc20TransferByContractAddress">erc20TransferByContractAddress</a></li><li><a href="global.html#erc721IsApprovedForAll">erc721IsApprovedForAll</a></li><li><a href="global.html#erc721IsApprovedOrOwner">erc721IsApprovedOrOwner</a></li><li><a href="global.html#erc721OwnerOf">erc721OwnerOf</a></li><li><a href="global.html#erc721SetApprovalForAll">erc721SetApprovalForAll</a></li><li><a href="global.html#erc1155BalanceOf">erc1155BalanceOf</a></li><li><a href="global.html#erc1155BalanceOfBatch">erc1155BalanceOfBatch</a></li><li><a href="global.html#erc1155IsApprovedForAll">erc1155IsApprovedForAll</a></li><li><a href="global.html#erc1155SafeBatchTransferFrom">erc1155SafeBatchTransferFrom</a></li><li><a href="global.html#erc1155SetApprovalForAll">erc1155SetApprovalForAll</a></li><li><a href="global.html#farmGetStakerAddress">farmGetStakerAddress</a></li><li><a href="global.html#farmGetStakerBalanceOf">farmGetStakerBalanceOf</a></li><li><a href="global.html#farmGetStakerEarned">farmGetStakerEarned</a></li><li><a href="global.html#farmGetStakerExit">farmGetStakerExit</a></li><li><a href="global.html#farmGetStakerGetReward">farmGetStakerGetReward</a></li><li><a href="global.html#farmGetStakerStakeToken">farmGetStakerStakeToken</a></li><li><a href="global.html#farmGetStakerTotalSupply">farmGetStakerTotalSupply</a></li><li><a href="global.html#farmStakerWithdraw">farmStakerWithdraw</a></li><li><a href="global.html#furnaceBuyTreasure">furnaceBuyTreasure</a></li><li><a href="global.html#furnaceDisenchantProps">furnaceDisenchantProps</a></li><li><a href="global.html#furnaceDismantleEquipment">furnaceDismantleEquipment</a></li><li><a href="global.html#furnaceEnchantEquipment">furnaceEnchantEquipment</a></li><li><a href="global.html#furnaceEnchantProps">furnaceEnchantProps</a></li><li><a href="global.html#furnaceGetTreasurePrice">furnaceGetTreasurePrice</a></li><li><a href="global.html#furnaceGetTreasureStatus">furnaceGetTreasureStatus</a></li><li><a href="global.html#furnaceOpenTreasure">furnaceOpenTreasure</a></li><li><a href="global.html#goldRushRaffleChangeAmount">goldRushRaffleChangeAmount</a></li><li><a href="global.html#goldRushRaffleChangeInfo">goldRushRaffleChangeInfo</a></li><li><a href="global.html#goldRushRaffleChangeSubAddr">goldRushRaffleChangeSubAddr</a></li><li><a href="global.html#goldRushRaffleDraw">goldRushRaffleDraw</a></li><li><a href="global.html#goldRushRaffleExit">goldRushRaffleExit</a></li><li><a href="global.html#goldRushRaffleGetHistory">goldRushRaffleGetHistory</a></li><li><a href="global.html#goldRushRaffleJoin">goldRushRaffleJoin</a></li><li><a href="global.html#landAskWithToken">landAskWithToken</a></li><li><a href="global.html#landBatchClaimItemResource">landBatchClaimItemResource</a></li><li><a href="global.html#landBatchClaimLandResource">landBatchClaimLandResource</a></li><li><a href="global.html#landBidWithToken">landBidWithToken</a></li><li><a href="global.html#landCancelAskWithToken">landCancelAskWithToken</a></li><li><a href="global.html#landClaimItemResource">landClaimItemResource</a></li><li><a href="global.html#landClaimLand">landClaimLand</a></li><li><a href="global.html#landGetAvailableItemResources">landGetAvailableItemResources</a></li><li><a href="global.html#landGetAvailableResources">landGetAvailableResources</a></li><li><a href="global.html#landGetCurrentPriceByTokenId">landGetCurrentPriceByTokenId</a></li><li><a href="global.html#landTransfer">landTransfer</a></li><li><a href="global.html#lotteryGetPointsBalance">lotteryGetPointsBalance</a></li><li><a href="global.html#lotteryGetTotalRewardInPool">lotteryGetTotalRewardInPool</a></li><li><a href="global.html#lotteryPlayWithTicket">lotteryPlayWithTicket</a></li><li><a href="global.html#materialBalanceOfBatch">materialBalanceOfBatch</a></li><li><a href="global.html#materialBatchTransfer">materialBatchTransfer</a></li><li><a href="global.html#materialIdEncode">materialIdEncode</a></li><li><a href="global.html#pveTeamAt">pveTeamAt</a></li><li><a href="global.html#pveTeamEvict">pveTeamEvict</a></li><li><a href="global.html#pveTeamExist">pveTeamExist</a></li><li><a href="global.html#pveTeamExit">pveTeamExit</a></li><li><a href="global.html#pveTeamExits">pveTeamExits</a></li><li><a href="global.html#pveTeamJoin">pveTeamJoin</a></li><li><a href="global.html#pveTeamJoins">pveTeamJoins</a></li><li><a href="global.html#pveTeamSwap">pveTeamSwap</a></li><li><a href="global.html#uniswapBuyRING">uniswapBuyRING</a></li></ul> </nav> <div id="main"> <h1 class="page-title">uniswap/index.ts</h1> <section> <article> <pre class="prettyprint source linenums"><code>/* eslint-disable @typescript-eslint/ban-ts-comment */ import { ethers, providers } from "ethers"; import type { CallbackType } from "@evo/utils/ethers/contractHelper"; import { Token, TokenAmount, Percent, Route, TradeType, Trade, JSBI, CurrencyAmount } from "@evo/libs/uniswap"; import { Pair } from "@evo/utils/uniswap/uniswapPair"; import { getAddressByName } from "../../../utils/ethers/addressHelper"; import { Fetcher } from "@evo/utils/uniswap/uniswapFetcher"; import { calculateSlippageAmount } from "@evo/utils/uniswap/uniswap"; import { triggerContractByContractName } from "@evo/utils/ethers/contractHelper"; import BigNumber from "bignumber.js"; import { CompactToken, CompactTokenWithAmount, DerivedMintInfo, DerivedBurnInfo } from "../types"; import { erc20TotalSupply, erc20BalanceOf } from "../erc20"; import { LandId } from "@evo/config/constants"; import { TransactionResponse } from "@ethersproject/providers"; export const uniswapGetWETH = (landId: LandId, chainId: number, symbol = "WETH", name = "Wrapped ether"): Token => { const WETHAddress = getAddressByName(landId, "TOKEN_WETH"); return new Token(chainId, WETHAddress, 18, symbol, name); }; /** * Swap Ether to Ring token - Powered by uniswap. * @param {string} value - amount for Ring, unit of measurement(wei) * @returns {Promise&lt;PromiEvent&lt;any>>} */ export const uniswapBuyRING = async ( landId: LandId, signer: ethers.Signer, value: string, callback?: CallbackType ): Promise&lt;TransactionResponse> => { const network = await signer.provider?.getNetwork(); if (!network) { throw Error("no network"); } const RINGAddress = getAddressByName(landId, "TOKEN_RING"); const RING = new Token(network?.chainId, RINGAddress, 18, "RING", "Darwinia Network Native Token"); const WETH = uniswapGetWETH(landId, network?.chainId); const pair = await Fetcher.fetchPairData(WETH, RING, signer.provider); // @ts-ignore const route = new Route([pair], WETH); const amountIn = value; const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_OUTPUT); const slippageTolerance = new Percent("30", "10000"); // 30 bips, or 0.30% const amountInMax = trade.maximumAmountIn(slippageTolerance).raw; // needs to be converted to e.g. hex const path = [WETH.address, RING.address]; const to = await signer.getAddress(); // should be a checksummed recipient address const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time const outputAmount = trade.outputAmount.raw; // // needs to be converted to e.g. hex return triggerContractByContractName( landId, signer, "uniswapExchange", "swapETHForExactTokens", [outputAmount.toString(10), path, to, deadline], callback, { value: ethers.BigNumber.from(amountInMax.toString()), } ); }; export const uniswapSellRING = async ( landId: LandId, signer: ethers.Signer, value: string, callback?: CallbackType ): Promise&lt;TransactionResponse> => { const network = await signer.provider?.getNetwork(); if (!network) { throw Error("no network"); } const RINGAddress = getAddressByName(landId, "TOKEN_RING"); const RING = new Token(network?.chainId, RINGAddress, 18, "RING", "Darwinia Network Native Token"); const WETH = uniswapGetWETH(landId, network?.chainId); const pair = await Fetcher.fetchPairData(RING, WETH, signer); // @ts-ignore const route = new Route([pair], RING); const amountIn = value; const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_INPUT); const slippageTolerance = new Percent("30", "10000"); // 30 bips, or 0.30% const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex const path = [RING.address, WETH.address]; const to = await signer.getAddress(); // should be a checksummed recipient address const deadline = Math.floor(Date.now() / 1000) + 60 * 20; // 20 minutes from the current Unix time const inputAmount = trade.inputAmount.raw; // // needs to be converted to e.g. hex return triggerContractByContractName( landId, signer, "uniswapExchange", "swapExactTokensForETH", [inputAmount.toString(10), amountOutMin.toString(10), path, to, deadline], callback, { value: ethers.BigNumber.from(0), } ); }; export const uniswapGetDerivedPairInfo = async ( provider: ethers.providers.Provider, compactTokenA: CompactToken, compactTokenB: CompactToken ): Promise&lt;Pair> => { const network = await provider?.getNetwork(); // decimals 18 just a random value,is useless, not used in the process of obtaining pair information const tokenA = new Token( network?.chainId, compactTokenA.address, compactTokenA.decimals, compactTokenA.symbol || "TokenA", compactTokenA.name || "TokenA" ); const tokenB = new Token( network?.chainId, compactTokenB.address, compactTokenB.decimals, compactTokenB.symbol || "TokenB", compactTokenB.name || "TokenB" ); const pair = await Fetcher.fetchPairData(tokenA, tokenB, provider); return pair; }; export const uniswapEthToTokenOutputPrice = async ( landId: LandId, provider: ethers.providers.Provider, token: CompactToken, tokenBought: string ): Promise&lt;string[]> => { const network = await provider?.getNetwork(); const RING = new Token(network?.chainId, token.address, token.decimals, token.symbol, token.name); const WETH = uniswapGetWETH(landId, network?.chainId); const pair = await Fetcher.fetchPairData(WETH, RING, provider); // @ts-ignore const route = new Route([pair], WETH); const amountIn = tokenBought; const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_OUTPUT); const slippageTolerance = new Percent("30", "10000"); const amountInMax = trade.maximumAmountIn(slippageTolerance).raw; return [ new BigNumber(amountInMax.toString(10)).times("1000000000000000000").div(tokenBought).toFixed(0), amountInMax.toString(10), ]; }; export const uniswapTokenToEthInputPrice = async ( landId: LandId, provider: ethers.providers.Provider, token: CompactToken, tokenBought: string ): Promise&lt;string[]> => { const network = await provider?.getNetwork(); const RING = new Token(network?.chainId, token.address, token.decimals, token.symbol, token.name); const WETH = uniswapGetWETH(landId, network?.chainId); const pair = await Fetcher.fetchPairData(RING, WETH, provider); // @ts-ignore const route = new Route([pair], RING); const amountIn = tokenBought; // 1 WETH const trade = new Trade(route, new TokenAmount(RING, amountIn), TradeType.EXACT_INPUT); const slippageTolerance = new Percent("30", "10000"); // 30 bips, or 0.30% const amountOutMin = trade.minimumAmountOut(slippageTolerance).raw; // needs to be converted to e.g. hex return [ new BigNumber(amountOutMin.toString(10)).times("1000000000000000000").div(tokenBought).toFixed(0), amountOutMin.toString(10), ]; }; export const uniswapAddETHLiquidity = async ( landId: LandId, signer: ethers.Signer, tokenA: CompactTokenWithAmount, tokenB: CompactTokenWithAmount, to: string, slippage = 100, callback?: CallbackType ): Promise&lt;TransactionResponse> => { const network = await signer.provider?.getNetwork(); if (!network || !signer.provider) { throw Error("no network"); } const WETH = uniswapGetWETH(landId, network?.chainId); const { pair, parsedAmounts } = await uniswapGetDerivedMintInfo(signer.provider, tokenA, tokenB); if (!pair || !pair.token0.address || !pair.token1.address) { throw Error("no pair"); } const amountsMin: { [x: string]: JSBI } = { [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0], [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0], }; const erc20Token = pair.token0.address.toLowerCase() === WETH.address?.toLowerCase() ? pair.token1 : pair.token0; const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time return triggerContractByContractName( landId, signer, "uniswapExchange", "addLiquidityETH", [ erc20Token.address, parsedAmounts[erc20Token.address].raw.toString(), amountsMin[erc20Token.address].toString(), amountsMin[WETH.address].toString(), to, deadline, ], callback, { value: ethers.BigNumber.from(parsedAmounts[WETH.address].raw.toString()), } ); }; export const uniswapAddLiquidity = async ( landId: LandId, signer: ethers.Signer, tokenA: CompactTokenWithAmount, tokenB: CompactTokenWithAmount, to: string, slippage = 100, callback?: CallbackType ): Promise&lt;TransactionResponse> => { const network = await signer.provider?.getNetwork(); if (!network || !signer.provider) { throw Error("no network"); } const { pair, parsedAmounts } = await uniswapGetDerivedMintInfo(signer.provider, tokenA, tokenB); if (!pair || !pair.token0.address || !pair.token1.address) { throw Error("no pair"); } const amountsMin: { [x: string]: JSBI } = { [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0], [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0], }; const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time return triggerContractByContractName( landId, signer, "uniswapExchange", "addLiquidity", [ pair.token0.address, pair.token1.address, parsedAmounts[pair.token0.address].raw.toString(), parsedAmounts[pair.token1.address].raw.toString(), amountsMin[pair.token0.address].toString(), amountsMin[pair.token1.address].toString(), to, deadline, ], callback ); }; export const uniswapRemoveLiquidity = async ( landId: LandId, signer: ethers.Signer, tokenA: CompactToken, tokenB: CompactToken, liquidityValue: string, to: string, slippage = 100, callback?: CallbackType ): Promise&lt;TransactionResponse> => { const network = await signer.provider?.getNetwork(); if (!network || !signer.provider) { throw Error("no network"); } const { pair, parsedAmounts } = await uniswapGetDerivedBurnInfo( signer.provider, tokenA, tokenB, ethers.BigNumber.from(liquidityValue), to ); if (!pair || !pair.token0.address || !pair.token1.address) { throw Error("no pair"); } const amountsMin: { [x: string]: JSBI } = { [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0], [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0], }; const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time return triggerContractByContractName( landId, signer, "uniswapExchange", "removeLiquidity", [ pair.token0.address, pair.token1.address, parsedAmounts[pair.liquidityToken.address].raw.toString(), amountsMin[pair.token0.address].toString(), amountsMin[pair.token1.address].toString(), to, deadline, ], callback ); }; export const uniswapRemoveETHLiquidity = async ( landId: LandId, signer: ethers.Signer, tokenA: CompactToken, tokenB: CompactToken, liquidityValue: string, to: string, slippage = 100, callback?: CallbackType ): Promise&lt;TransactionResponse> => { const network = await signer.provider?.getNetwork(); if (!network || !signer.provider) { throw Error("no network"); } const { pair, parsedAmounts } = await uniswapGetDerivedBurnInfo( signer.provider, tokenA, tokenB, ethers.BigNumber.from(liquidityValue), to ); const WETH = uniswapGetWETH(landId, network?.chainId); if (!pair || !pair.token0.address || !pair.token1.address) { throw Error("no pair"); } const amountsMin: { [x: string]: JSBI } = { [pair.token0.address]: calculateSlippageAmount(parsedAmounts[pair.token0.address].raw, slippage)[0], [pair.token1.address]: calculateSlippageAmount(parsedAmounts[pair.token1.address].raw, slippage)[0], }; const erc20Token = pair.token0.address === WETH ? pair.token1 : pair.token0; const deadline = Math.floor(Date.now() / 1000) + 60 * 120; // 120 minutes from the current Unix time return triggerContractByContractName( landId, signer, "uniswapExchange", "removeLiquidityETH", [ erc20Token.address, parsedAmounts[pair.liquidityToken.address].raw.toString(), amountsMin[erc20Token.address].toString(), amountsMin[WETH.address].toString(), to, deadline, ], callback ); }; export const uniswapGetDerivedMintInfo = async ( provider: ethers.providers.Provider, tokenA: CompactTokenWithAmount, tokenB: CompactTokenWithAmount ): Promise&lt;DerivedMintInfo> => { const network = await provider?.getNetwork(); const pair = await uniswapGetDerivedPairInfo(provider, tokenA, tokenB); const totalSupply = new TokenAmount( pair.liquidityToken, await erc20TotalSupply(provider, pair.liquidityToken.address) ); const independentToken = tokenA.amount.isZero() ? { token: new Token(network?.chainId, tokenB.address, tokenB.decimals, tokenB.symbol, tokenB.name), amount: tokenB.amount, } : { token: new Token(network?.chainId, tokenA.address, tokenA.decimals, tokenA.symbol, tokenA.name), amount: tokenA.amount, }; const parsedAmounts = { [pair.liquidityToken.address]: totalSupply, [pair.token0.address]: new TokenAmount( pair.token0, independentToken.token.equals(pair.token0) ? JSBI.BigInt(independentToken.amount) : pair .priceOf(independentToken.token) // @ts-ignore .quote(new CurrencyAmount(independentToken.token, JSBI.BigInt(independentToken.amount))).raw ), [pair.token1.address]: new TokenAmount( pair.token1, independentToken.token.equals(pair.token1) ? JSBI.BigInt(independentToken.amount) : pair .priceOf(independentToken.token) // @ts-ignore .quote(new CurrencyAmount(independentToken.token, JSBI.BigInt(independentToken.amount))).raw ), }; return { pair, parsedAmounts }; }; export const uniswapGetDerivedBurnInfo = async ( provider: ethers.providers.Provider, tokenA: CompactToken, tokenB: CompactToken, liquidityValue: ethers.BigNumber, to: string ): Promise&lt;DerivedBurnInfo> => { const pair = await uniswapGetDerivedPairInfo(provider, tokenA, tokenB); const lpBalanceStr = await erc20BalanceOf(provider, pair.liquidityToken.address, to); const userLiquidity = new TokenAmount(pair.liquidityToken, JSBI.BigInt(lpBalanceStr)); const totalSupply = new TokenAmount( pair.liquidityToken, await erc20TotalSupply(provider, pair.liquidityToken.address) ); const liquidityValueA = pair &amp;&amp; totalSupply &amp;&amp; userLiquidity &amp;&amp; pair.token0 &amp;&amp; new TokenAmount(pair.token0, pair.getLiquidityValue(pair.token0, totalSupply, userLiquidity, false).raw); const liquidityValueB = pair &amp;&amp; totalSupply &amp;&amp; userLiquidity &amp;&amp; pair.token1 &amp;&amp; new TokenAmount(pair.token1, pair.getLiquidityValue(pair.token1, totalSupply, userLiquidity, false).raw); const percentToRemove = new Percent(JSBI.BigInt(liquidityValue), userLiquidity.raw); const parsedAmounts = { [pair.liquidityToken.address]: new TokenAmount( userLiquidity.token, percentToRemove.multiply(userLiquidity.raw).quotient ), [pair.token0.address]: new TokenAmount(pair.token0, percentToRemove.multiply(liquidityValueA.raw).quotient), [pair.token1.address]: new TokenAmount(pair.token1, percentToRemove.multiply(liquidityValueB.raw).quotient), }; return { pair, parsedAmounts, liquidityPercent: percentToRemove }; }; </code></pre> </article> </section> </div> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.6.11</a> on Mon Aug 15 2022 13:54:57 GMT+0800 (China Standard Time) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme. </footer> <script>prettyPrint();</script> <script src="scripts/polyfill.js"></script> <script src="scripts/linenumber.js"></script> </body> </html>