/// import { Command, flags } from '@oclif/command'; import { ParserOutput } from '@oclif/parser/lib/parse'; import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'; import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'; import { MethodParameters } from '@uniswap/v3-sdk'; import { default as bunyan, default as Logger } from 'bunyan'; import bunyanDebugStream from 'bunyan-debug-stream'; import { BigNumber, ethers } from 'ethers'; import NodeCache from 'node-cache'; import { AlphaRouter, CachingGasStationProvider, CachingTokenListProvider, CachingTokenProviderWithFallback, ChainId, CHAIN_IDS_LIST, EIP1559GasPriceProvider, GasPrice, ID_TO_CHAIN_ID, ID_TO_PROVIDER, IRouter, ISwapToRatio, ITokenProvider, IV3PoolProvider, LegacyRouter, MetricLogger, NodeJSCache, routeAmountsToString, RouteWithValidQuote, setGlobalLogger, setGlobalMetric, TokenProvider, UniswapMulticallProvider, V3PoolProvider, V3QuoteProvider, } from '../src'; import { LegacyGasPriceProvider } from '../src/providers/legacy-gas-price-provider'; import { OnChainGasPriceProvider } from '../src/providers/on-chain-gas-price-provider'; export abstract class BaseCommand extends Command { static flags = { topN: flags.integer({ required: false, default: 3, }), topNTokenInOut: flags.integer({ required: false, default: 2, }), topNSecondHop: flags.integer({ required: false, default: 0, }), topNWithEachBaseToken: flags.integer({ required: false, default: 2, }), topNWithBaseToken: flags.integer({ required: false, default: 6, }), topNWithBaseTokenInSet: flags.boolean({ required: false, default: false, }), topNDirectSwaps: flags.integer({ required: false, default: 2, }), maxSwapsPerPath: flags.integer({ required: false, default: 3, }), minSplits: flags.integer({ required: false, default: 1, }), maxSplits: flags.integer({ required: false, default: 3, }), distributionPercent: flags.integer({ required: false, default: 5, }), chainId: flags.integer({ char: 'c', required: false, default: ChainId.MAINNET, options: CHAIN_IDS_LIST, }), tokenListURI: flags.string({ required: false, }), router: flags.string({ char: 's', required: false, default: 'alpha', }), debug: flags.boolean(), debugJSON: flags.boolean(), }; private _log: Logger | null = null; private _router: IRouter | null = null; private _swapToRatioRouter: ISwapToRatio | null = null; private _tokenProvider: ITokenProvider | null = null; private _poolProvider: IV3PoolProvider | null = null; private _blockNumber: number | null = null; private _multicall2Provider: UniswapMulticallProvider | null = null; get logger() { return this._log ? this._log : bunyan.createLogger({ name: 'Default Logger', }); } get router() { if (this._router) { return this._router; } else { throw 'router not initialized'; } } get swapToRatioRouter() { if (this._swapToRatioRouter) { return this._swapToRatioRouter; } else { throw 'swapToRatioRouter not initialized'; } } get tokenProvider() { if (this._tokenProvider) { return this._tokenProvider; } else { throw 'tokenProvider not initialized'; } } get poolProvider() { if (this._poolProvider) { return this._poolProvider; } else { throw 'poolProvider not initialized'; } } get blockNumber() { if (this._blockNumber) { return this._blockNumber; } else { throw 'blockNumber not initialized'; } } get multicall2Provider() { if (this._multicall2Provider) { return this._multicall2Provider; } else { throw 'multicall2 not initialized'; } } async init() { const query: ParserOutput = this.parse(); const { chainId: chainIdNumb, router: routerStr, debug, debugJSON, tokenListURI, } = query.flags; // initialize logger const logLevel = debug || debugJSON ? bunyan.DEBUG : bunyan.INFO; this._log = bunyan.createLogger({ name: 'Uniswap Smart Order Router', serializers: bunyan.stdSerializers, level: logLevel, streams: debugJSON ? undefined : [ { level: logLevel, type: 'stream', stream: bunyanDebugStream({ basepath: __dirname, forceColor: false, showDate: false, showPid: false, showLoggerName: false, showLevel: !!debug, }), }, ], }); if (debug || debugJSON) { setGlobalLogger(this.logger); } const metricLogger: MetricLogger = new MetricLogger(); setGlobalMetric(metricLogger); const chainId = ID_TO_CHAIN_ID(chainIdNumb); const chainProvider = ID_TO_PROVIDER(chainId); const provider = new ethers.providers.JsonRpcProvider( chainProvider, chainId ); this._blockNumber = await provider.getBlockNumber(); const tokenCache = new NodeJSCache( new NodeCache({ stdTTL: 3600, useClones: false }) ); let tokenListProvider: CachingTokenListProvider; if (tokenListURI) { tokenListProvider = await CachingTokenListProvider.fromTokenListURI( chainId, tokenListURI, tokenCache ); } else { tokenListProvider = await CachingTokenListProvider.fromTokenList( chainId, DEFAULT_TOKEN_LIST, tokenCache ); } const multicall2Provider = new UniswapMulticallProvider(chainId, provider); this._multicall2Provider = multicall2Provider; this._poolProvider = new V3PoolProvider(chainId, multicall2Provider); // initialize tokenProvider const tokenProviderOnChain = new TokenProvider(chainId, multicall2Provider); this._tokenProvider = new CachingTokenProviderWithFallback( chainId, tokenCache, tokenListProvider, tokenProviderOnChain ); if (routerStr == 'legacy') { this._router = new LegacyRouter({ chainId, multicall2Provider, poolProvider: new V3PoolProvider(chainId, multicall2Provider), quoteProvider: new V3QuoteProvider( chainId, provider, multicall2Provider ), tokenProvider: this.tokenProvider, }); } else { const gasPriceCache = new NodeJSCache( new NodeCache({ stdTTL: 15, useClones: true }) ); // const useDefaultQuoteProvider = // chainId != ChainId.ARBITRUM_ONE && chainId != ChainId.ARBITRUM_RINKEBY; const router = new AlphaRouter({ provider, chainId, multicall2Provider: multicall2Provider, gasPriceProvider: new CachingGasStationProvider( chainId, new OnChainGasPriceProvider( chainId, new EIP1559GasPriceProvider(provider), new LegacyGasPriceProvider(provider) ), gasPriceCache ), }); this._swapToRatioRouter = router; this._router = router; } } logSwapResults( routeAmounts: RouteWithValidQuote[], quote: CurrencyAmount, quoteGasAdjusted: CurrencyAmount, estimatedGasUsedQuoteToken: CurrencyAmount, estimatedGasUsedUSD: CurrencyAmount, methodParameters: MethodParameters | undefined, blockNumber: BigNumber, estimatedGasUsed: BigNumber, gasPriceWei: BigNumber ) { this.logger.info(`Best Route:`); this.logger.info(`${routeAmountsToString(routeAmounts)}`); this.logger.info(`\tRaw Quote Exact In:`); this.logger.info( `\t\t${quote.toFixed(Math.min(quote.currency.decimals, 2))}` ); this.logger.info(`\tGas Adjusted Quote In:`); this.logger.info( `\t\t${quoteGasAdjusted.toFixed( Math.min(quoteGasAdjusted.currency.decimals, 2) )}` ); this.logger.info(``); this.logger.info( `Gas Used Quote Token: ${estimatedGasUsedQuoteToken.toFixed( Math.min(estimatedGasUsedQuoteToken.currency.decimals, 6) )}` ); this.logger.info( `Gas Used USD: ${estimatedGasUsedUSD.toFixed( Math.min(estimatedGasUsedUSD.currency.decimals, 6) )}` ); this.logger.info(`Calldata: ${methodParameters?.calldata}`); this.logger.info(`Value: ${methodParameters?.value}`); this.logger.info({ blockNumber: blockNumber.toString(), estimatedGasUsed: estimatedGasUsed.toString(), gasPriceWei: gasPriceWei.toString(), }); } }