@sky-mavis/smart-order-router
Version:
Ronin Swap Smart Order Router
138 lines • 16 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.V2PoolProvider = void 0;
const katana_core_1 = require("@sky-mavis/katana-core");
const sdk_core_1 = require("@uniswap/sdk-core");
const async_retry_1 = __importDefault(require("async-retry"));
const lodash_1 = __importDefault(require("lodash"));
const IUniswapV2Pair__factory_1 = require("../../types/v2/factories/IUniswapV2Pair__factory");
const util_1 = require("../../util");
const log_1 = require("../../util/log");
const routes_1 = require("../../util/routes");
const token_validator_provider_1 = require("../token-validator-provider");
class V2PoolProvider {
/**
* Creates an instance of V2PoolProvider.
* @param chainId The chain id to use.
* @param multicall2Provider The multicall provider to use to get the pools.
* @param tokenPropertiesProvider The token properties provider to use to get token properties.
* @param retryOptions The retry options for each call to the multicall.
*/
constructor(chainId, multicall2Provider, tokenPropertiesProvider, retryOptions = {
retries: 2,
minTimeout: 50,
maxTimeout: 500,
}) {
this.chainId = chainId;
this.multicall2Provider = multicall2Provider;
this.tokenPropertiesProvider = tokenPropertiesProvider;
this.retryOptions = retryOptions;
// Computing pool addresses is slow as it requires hashing, encoding etc.
// Addresses never change so can always be cached.
this.POOL_ADDRESS_CACHE = {};
}
async getPools(tokenPairs, providerConfig) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
const poolAddressSet = new Set();
const sortedTokenPairs = [];
const sortedPoolAddresses = [];
for (const tokenPair of tokenPairs) {
const [tokenA, tokenB] = tokenPair;
const { poolAddress, token0, token1 } = this.getPoolAddress(tokenA, tokenB);
if (poolAddressSet.has(poolAddress)) {
continue;
}
poolAddressSet.add(poolAddress);
sortedTokenPairs.push([token0, token1]);
sortedPoolAddresses.push(poolAddress);
}
log_1.log.debug(`getPools called with ${tokenPairs.length} token pairs. Deduped down to ${poolAddressSet.size}`);
util_1.metric.putMetric('V2_RPC_POOL_RPC_CALL', 1, util_1.MetricLoggerUnit.None);
util_1.metric.putMetric('V2GetReservesBatchSize', sortedPoolAddresses.length, util_1.MetricLoggerUnit.Count);
util_1.metric.putMetric(`V2GetReservesBatchSize_${(0, util_1.ID_TO_NETWORK_NAME)(this.chainId)}`, sortedPoolAddresses.length, util_1.MetricLoggerUnit.Count);
const [reservesResults, tokenPropertiesMap] = await Promise.all([
this.getPoolsData(sortedPoolAddresses, 'getReserves', providerConfig),
this.tokenPropertiesProvider.getTokensProperties(this.flatten(tokenPairs), providerConfig),
]);
log_1.log.info(`Got reserves for ${poolAddressSet.size} pools ${(providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.blockNumber) ? `as of block: ${await (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.blockNumber)}.` : ``}`);
const poolAddressToPool = {};
const invalidPools = [];
for (let i = 0; i < sortedPoolAddresses.length; i++) {
const reservesResult = reservesResults[i];
if (!(reservesResult === null || reservesResult === void 0 ? void 0 : reservesResult.success)) {
const [token0, token1] = sortedTokenPairs[i];
invalidPools.push([token0, token1]);
continue;
}
let [token0, token1] = sortedTokenPairs[i];
if (((_a = tokenPropertiesMap[token0.address.toLowerCase()]) === null || _a === void 0 ? void 0 : _a.tokenValidationResult) === token_validator_provider_1.TokenValidationResult.FOT) {
token0 = new sdk_core_1.Token(token0.chainId, token0.address, token0.decimals, token0.symbol, token0.name, true, // at this point we know it's valid token address
(_c = (_b = tokenPropertiesMap[token0.address.toLowerCase()]) === null || _b === void 0 ? void 0 : _b.tokenFeeResult) === null || _c === void 0 ? void 0 : _c.buyFeeBps, (_e = (_d = tokenPropertiesMap[token0.address.toLowerCase()]) === null || _d === void 0 ? void 0 : _d.tokenFeeResult) === null || _e === void 0 ? void 0 : _e.sellFeeBps);
}
if (((_f = tokenPropertiesMap[token1.address.toLowerCase()]) === null || _f === void 0 ? void 0 : _f.tokenValidationResult) === token_validator_provider_1.TokenValidationResult.FOT) {
token1 = new sdk_core_1.Token(token1.chainId, token1.address, token1.decimals, token1.symbol, token1.name, true, // at this point we know it's valid token address
(_h = (_g = tokenPropertiesMap[token1.address.toLowerCase()]) === null || _g === void 0 ? void 0 : _g.tokenFeeResult) === null || _h === void 0 ? void 0 : _h.buyFeeBps, (_k = (_j = tokenPropertiesMap[token1.address.toLowerCase()]) === null || _j === void 0 ? void 0 : _j.tokenFeeResult) === null || _k === void 0 ? void 0 : _k.sellFeeBps);
}
const { reserve0, reserve1 } = reservesResult.result;
const pool = new katana_core_1.CustomPair(util_1.CurrencyAmount.fromRawAmount(token0, reserve0.toString()), util_1.CurrencyAmount.fromRawAmount(token1, reserve1.toString()));
const poolAddress = sortedPoolAddresses[i];
poolAddressToPool[poolAddress] = pool;
}
if (invalidPools.length > 0) {
log_1.log.info({
invalidPools: lodash_1.default.map(invalidPools, ([token0, token1]) => `${token0.symbol}/${token1.symbol}`),
}, `${invalidPools.length} pools invalid after checking their slot0 and liquidity results. Dropping.`);
}
const poolStrs = lodash_1.default.map(Object.values(poolAddressToPool), routes_1.poolToString);
log_1.log.debug({ poolStrs }, `Found ${poolStrs.length} valid pools`);
return {
getPool: (tokenA, tokenB) => {
const { poolAddress } = this.getPoolAddress(tokenA, tokenB);
return poolAddressToPool[poolAddress];
},
getPoolByAddress: (address) => poolAddressToPool[address],
getAllPools: () => Object.values(poolAddressToPool),
};
}
getPoolAddress(tokenA, tokenB) {
const [token0, token1] = tokenA.sortsBefore(tokenB) ? [tokenA, tokenB] : [tokenB, tokenA];
const cacheKey = `${this.chainId}/${token0.address}/${token1.address}`;
const cachedAddress = this.POOL_ADDRESS_CACHE[cacheKey];
if (cachedAddress) {
return { poolAddress: cachedAddress, token0, token1 };
}
const poolAddress = (0, katana_core_1.computePairAddress)({
tokenA: token0,
tokenB: token1,
chainId: this.chainId,
});
this.POOL_ADDRESS_CACHE[cacheKey] = poolAddress;
return { poolAddress, token0, token1 };
}
async getPoolsData(poolAddresses, functionName, providerConfig) {
const { results, blockNumber } = await (0, async_retry_1.default)(async () => {
return this.multicall2Provider.callSameFunctionOnMultipleContracts({
addresses: poolAddresses,
contractInterface: IUniswapV2Pair__factory_1.IUniswapV2Pair__factory.createInterface(),
functionName: functionName,
providerConfig,
});
}, this.retryOptions);
log_1.log.debug(`Pool data fetched as of block ${blockNumber}`);
return results;
}
// We are using ES2017. ES2019 has native flatMap support
flatten(tokenPairs) {
const tokens = new Array();
for (const [tokenA, tokenB] of tokenPairs) {
tokens.push(tokenA);
tokens.push(tokenB);
}
return tokens;
}
}
exports.V2PoolProvider = V2PoolProvider;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbC1wcm92aWRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9wcm92aWRlcnMvdjIvcG9vbC1wcm92aWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7QUFBQSx3REFBaUY7QUFFakYsZ0RBQTBDO0FBRTFDLDhEQUE2RDtBQUM3RCxvREFBdUI7QUFFdkIsOEZBQTJGO0FBQzNGLHFDQUEwRjtBQUMxRix3Q0FBcUM7QUFDckMsOENBQWlEO0FBSWpELDBFQUFvRTtBQTBDcEUsTUFBYSxjQUFjO0lBS3pCOzs7Ozs7T0FNRztJQUNILFlBQ1ksT0FBZ0IsRUFDaEIsa0JBQXNDLEVBQ3RDLHVCQUFpRCxFQUNqRCxlQUFtQztRQUMzQyxPQUFPLEVBQUUsQ0FBQztRQUNWLFVBQVUsRUFBRSxFQUFFO1FBQ2QsVUFBVSxFQUFFLEdBQUc7S0FDaEI7UUFQUyxZQUFPLEdBQVAsT0FBTyxDQUFTO1FBQ2hCLHVCQUFrQixHQUFsQixrQkFBa0IsQ0FBb0I7UUFDdEMsNEJBQXVCLEdBQXZCLHVCQUF1QixDQUEwQjtRQUNqRCxpQkFBWSxHQUFaLFlBQVksQ0FJckI7UUFuQkgseUVBQXlFO1FBQ3pFLGtEQUFrRDtRQUMxQyx1QkFBa0IsR0FBOEIsRUFBRSxDQUFDO0lBa0J4RCxDQUFDO0lBRUcsS0FBSyxDQUFDLFFBQVEsQ0FBQyxVQUE0QixFQUFFLGNBQStCOztRQUNqRixNQUFNLGNBQWMsR0FBZ0IsSUFBSSxHQUFHLEVBQVUsQ0FBQztRQUN0RCxNQUFNLGdCQUFnQixHQUEwQixFQUFFLENBQUM7UUFDbkQsTUFBTSxtQkFBbUIsR0FBYSxFQUFFLENBQUM7UUFDekMsS0FBSyxNQUFNLFNBQVMsSUFBSSxVQUFVLEVBQUU7WUFDbEMsTUFBTSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsR0FBRyxTQUFTLENBQUM7WUFFbkMsTUFBTSxFQUFFLFdBQVcsRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLEdBQUcsSUFBSSxDQUFDLGNB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