UNPKG

@onekeyfe/blockchain-libs

Version:
169 lines 7.98 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UniswapV3 = exports.UniswapV2 = void 0; const console = __importStar(require("console")); const abi_1 = require("@ethersproject/abi"); const bytes_1 = require("@ethersproject/bytes"); const bignumber_js_1 = __importDefault(require("bignumber.js")); const array_plus_1 = require("../../basic/array-plus"); const bignumber_plus_1 = require("../../basic/bignumber-plus"); const eth_1 = require("../../provider/chains/eth"); const BASE_TOKEN_DECIMALS_MULTIPLY = new bignumber_js_1.default(1).shiftedBy(18); class ABCUniswap { constructor(config, providerController) { this.config = config; this.providerController = providerController; } async pricing(coins) { const prices = []; for (const [chainCode, partCoins] of Object.entries((0, array_plus_1.groupBy)(coins, (i) => i.chainCode))) { if (typeof this.config[chainCode] !== 'object') { continue; } const configPerChain = this.config[chainCode]; const wbtcConfig = configPerChain['wbtc']; if (typeof wbtcConfig === 'string' && wbtcConfig.includes('@')) { const [wbtcTokenAddress, wbtcDecimals] = wbtcConfig.split('@'); partCoins.push({ code: 'btc', chainCode: chainCode, decimals: parseInt(wbtcDecimals), tokenAddress: wbtcTokenAddress, }); } const baseTokenAddress = configPerChain['base_token_address']; // also called wrapped token address, like WETH, WBNB etc. const mediaTokenAddresses = configPerChain['media_token_addresses']; // like WBTC, USDT, BUSD etc. const quotePaths = []; const refCoins = []; for (const coin of partCoins) { if (!coin.tokenAddress) { continue; } else if (coin.tokenAddress === baseTokenAddress) { prices.push({ coin: coin.code, value: new bignumber_js_1.default(1), unit: chainCode, }); continue; } if (mediaTokenAddresses.includes(coin.tokenAddress)) { quotePaths.push([coin.tokenAddress, baseTokenAddress]); refCoins.push({ coin, quoteCount: 1 }); } else { const paths = [ [coin.tokenAddress, baseTokenAddress], ...mediaTokenAddresses.map((media) => [ coin.tokenAddress, media, baseTokenAddress, ]), ]; quotePaths.push(...paths); refCoins.push({ coin, quoteCount: paths.length }); } } const calls = []; const batchRefCoins = []; const lastIndex = refCoins.length - 1; const quoteContract = this.getContract(configPerChain); for (const [index, ref] of Object.entries(refCoins)) { calls.push(...quotePaths .splice(0, ref.quoteCount) .map((path) => this.encodeCallData(ref.coin, path))); batchRefCoins.push(ref); if (parseInt(index) < lastIndex && calls.length < ABCUniswap.BATCH_SIZE) { continue; } try { const priceBNs = await this.callContract(chainCode, quoteContract, calls); for (const i of batchRefCoins) { const bns = priceBNs .splice(0, i.quoteCount) .filter((bn) => typeof bn !== 'undefined' && bn.isFinite() && bn.gt(0)) .map((bn) => bn); const priceBN = bignumber_js_1.default.max(new bignumber_js_1.default(0), ...bns); prices.push({ coin: i.coin.code, unit: i.coin.chainCode, value: priceBN, }); } } catch (e) { console.error(`Error in batching quote prices. coins: ${batchRefCoins.map((i) => i.coin.code)}`, e); } finally { calls.splice(0, calls.length); batchRefCoins.splice(0, batchRefCoins.length); } } } return prices; } async callContract(chainCode, contract, calls) { const client = (await this.providerController.getClient(chainCode, (i) => i instanceof eth_1.Geth)); const resp = await client.batchEthCall(calls.map((i) => ({ to: contract, data: i }))); return resp.map((i) => { if (typeof i === 'undefined') { return undefined; } const [value] = abi_1.defaultAbiCoder.decode(['uint256'], '0x' + i.slice(-64)); const bn = new bignumber_js_1.default(value.toString()); return bn.isFinite() ? bn.div(BASE_TOKEN_DECIMALS_MULTIPLY) : undefined; }); } } ABCUniswap.BATCH_SIZE = 50; class UniswapV2 extends ABCUniswap { getContract(configPerChain) { return configPerChain['router_address']; } encodeCallData(coin, path) { // https://docs.uniswap.org/protocol/V2/reference/smart-contracts/library#getamountsout const methodID = '0xd06ca61f'; // keccak256(Buffer.from("getAmountsOut(uint256,address[])")).slice(0, 10) const amountIn = (0, bignumber_plus_1.toBigIntHex)(new bignumber_js_1.default(1).shiftedBy(coin.decimals)); const encodedParams = abi_1.defaultAbiCoder.encode(['uint256', 'address[]'], [amountIn, path]); return (0, bytes_1.hexConcat)([methodID, encodedParams]); } } exports.UniswapV2 = UniswapV2; class UniswapV3 extends ABCUniswap { getContract(configPerChain) { return configPerChain['quoter_address']; } encodeCallData(coin, path) { // https://docs.uniswap.org/protocol/reference/periphery/interfaces/IQuoter#quoteexactinput const methodID = '0xcdca1753'; // keccak256(Buffer.from("quoteExactInput(bytes,uint256)")).slice(0, 10) const amountIn = (0, bignumber_plus_1.toBigIntHex)(new bignumber_js_1.default(1).shiftedBy(coin.decimals)); const pathHex = '0x' + path.map((i) => i.slice(2)).join('000bb8'); const encodedParams = abi_1.defaultAbiCoder.encode(['bytes', 'uint256'], [pathHex, amountIn]); return (0, bytes_1.hexConcat)([methodID, encodedParams]); } } exports.UniswapV3 = UniswapV3; //# sourceMappingURL=uniswap.js.map