Skip to content

Commit

Permalink
2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
willpote committed Nov 5, 2021
1 parent c455384 commit 19c5611
Show file tree
Hide file tree
Showing 37 changed files with 5,437 additions and 1,731 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This repository contains routing logic for the Uniswap V3 protocol.
It searches for the most efficient way to swap token A for token B, considering splitting swaps across multiple routes and gas costs.

## Testing

### Unit Tests

```
Expand All @@ -24,7 +25,7 @@ JSON_RPC_PROVIDER = '<JSON_RPC_PROVIDER>'
Then from the root directory you can execute the CLI.

```
./bin/cli quote --tokenIn 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --tokenOut 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --amount 1000 --exactIn --recipient 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B
./bin/cli quote --tokenIn 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 --tokenOut 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --amount 1000 --exactIn --recipient 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B --protocols v2,v3
Best Route:
100.00% = USDC -- 0.3% --> UNI
Expand Down
97 changes: 46 additions & 51 deletions cli/base-command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,39 @@
import { Command, flags } from '@oclif/command';
import { ParserOutput } from '@oclif/parser/lib/parse';
import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list';
import { MethodParameters, Pool } from '@uniswap/v3-sdk';
import { BigNumber, ethers } from 'ethers';
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,
CachingPoolProvider,
CachingSubgraphProvider,
CachingTokenListProvider,
CachingTokenProviderWithFallback,
ChainId,
CHAIN_IDS_LIST,
EIP1559GasPriceProvider,
HeuristicGasModelFactory,
GasPrice,
ID_TO_CHAIN_ID,
ID_TO_NETWORK_NAME,
IPoolProvider,
IRouter,
IRouteWithValidQuote,
ISwapToRatio,
ITokenProvider,
IV3PoolProvider,
LegacyRouter,
MetricLogger,
PoolProvider,
QuoteProvider,
NodeJSCache,
routeAmountsToString,
RouteWithValidQuote,
setGlobalLogger,
setGlobalMetric,
TokenProvider,
CachingTokenListProvider,
UniswapMulticallProvider,
NodeJSCache,
SubgraphPool,
GasPrice,
CachingTokenProviderWithFallback,
URISubgraphProvider,
V2StaticSubgraphProvider,
V3PoolProvider,
V3QuoteProvider,
} from '../src';

export abstract class BaseCommand extends Command {
Expand Down Expand Up @@ -67,6 +63,10 @@ export abstract class BaseCommand extends Command {
required: false,
default: false,
}),
topNDirectSwaps: flags.integer({
required: false,
default: 2,
}),
maxSwapsPerPath: flags.integer({
required: false,
default: 3,
Expand Down Expand Up @@ -105,8 +105,9 @@ export abstract class BaseCommand extends Command {
private _router: IRouter<any> | null = null;
private _swapToRatioRouter: ISwapToRatio<any, any> | null = null;
private _tokenProvider: ITokenProvider | null = null;
private _poolProvider: IPoolProvider | null = null;
private _poolProvider: IV3PoolProvider | null = null;
private _blockNumber: number | null = null;
private _multicall2Provider: UniswapMulticallProvider | null = null;

get logger() {
return this._log
Expand Down Expand Up @@ -156,6 +157,14 @@ export abstract class BaseCommand extends Command {
}
}

get multicall2Provider() {
if (this._multicall2Provider) {
return this._multicall2Provider;
} else {
throw 'multicall2 not initialized';
}
}

async init() {
const query: ParserOutput<any, any> = this.parse();
const {
Expand Down Expand Up @@ -201,13 +210,17 @@ export abstract class BaseCommand extends Command {
const chainName = ID_TO_NETWORK_NAME(chainIdNumb);

const provider = new ethers.providers.JsonRpcProvider(
chainId == ChainId.MAINNET ? process.env.JSON_RPC_PROVIDER! : process.env.JSON_RPC_PROVIDER_RINKEBY!,
chainId == ChainId.MAINNET
? process.env.JSON_RPC_PROVIDER!
: process.env.JSON_RPC_PROVIDER_RINKEBY!,
chainName
);

this._blockNumber = await provider.getBlockNumber()
this._blockNumber = await provider.getBlockNumber();

const tokenCache = new NodeJSCache<Token>(new NodeCache({ stdTTL: 3600, useClones: false }));
const tokenCache = new NodeJSCache<Token>(
new NodeCache({ stdTTL: 3600, useClones: false })
);

let tokenListProvider: CachingTokenListProvider;
if (tokenListURI) {
Expand All @@ -225,7 +238,8 @@ export abstract class BaseCommand extends Command {
}

const multicall2Provider = new UniswapMulticallProvider(chainId, provider);
this._poolProvider = new PoolProvider(chainId, multicall2Provider);
this._multicall2Provider = multicall2Provider;
this._poolProvider = new V3PoolProvider(chainId, multicall2Provider);

// initialize tokenProvider
const tokenProviderOnChain = new TokenProvider(chainId, multicall2Provider);
Expand All @@ -240,48 +254,29 @@ export abstract class BaseCommand extends Command {
this._router = new LegacyRouter({
chainId,
multicall2Provider,
poolProvider: new PoolProvider(chainId, multicall2Provider),
quoteProvider: new QuoteProvider(chainId, provider, multicall2Provider),
poolProvider: new V3PoolProvider(chainId, multicall2Provider),
quoteProvider: new V3QuoteProvider(
chainId,
provider,
multicall2Provider
),
tokenProvider: this.tokenProvider,
});
} else {
const subgraphCache = new NodeJSCache<SubgraphPool[]>(new NodeCache({ stdTTL: 900, useClones: true }));
const poolCache = new NodeJSCache<Pool>(new NodeCache({ stdTTL: 900, useClones: false }));
const gasPriceCache = new NodeJSCache<GasPrice>(new NodeCache({ stdTTL: 15, useClones: true }));
const gasPriceCache = new NodeJSCache<GasPrice>(
new NodeCache({ stdTTL: 15, useClones: true })
);

const router = new AlphaRouter({
provider,
chainId,
subgraphProvider: new CachingSubgraphProvider(chainId,
new URISubgraphProvider(chainId, 'https://ipfs.io/ipfs/QmfArMYESGVJpPALh4eQXnjF8HProSF1ky3v8RmuYLJZT4'),
subgraphCache
),
multicall2Provider: multicall2Provider,
poolProvider: new CachingPoolProvider(chainId,
new PoolProvider(chainId, multicall2Provider),
poolCache
),
quoteProvider: new QuoteProvider(
gasPriceProvider: new CachingGasStationProvider(
chainId,
provider,
multicall2Provider,
{
retries: 2,
minTimeout: 25,
maxTimeout: 250,
},
{
multicallChunk: 200,
gasLimitPerCall: 725_000,
quoteMinSuccessRate: 0.7,
}
),
gasPriceProvider: new CachingGasStationProvider(chainId,
new EIP1559GasPriceProvider(provider),
gasPriceCache
),
gasModelFactory: new HeuristicGasModelFactory(),
tokenProvider: this.tokenProvider,
v2SubgraphProvider: new V2StaticSubgraphProvider(),
});

this._swapToRatioRouter = router;
Expand All @@ -290,7 +285,7 @@ export abstract class BaseCommand extends Command {
}

logSwapResults(
routeAmounts: RouteWithValidQuote[],
routeAmounts: IRouteWithValidQuote[],
quote: CurrencyAmount<Currency>,
quoteGasAdjusted: CurrencyAmount<Currency>,
estimatedGasUsedQuoteToken: CurrencyAmount<Currency>,
Expand Down
9 changes: 3 additions & 6 deletions cli/commands/quote-to-ratio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,7 @@ export class QuoteToRatio extends BaseCommand {
const router = this.swapToRatioRouter;
const tokenProvider = this.tokenProvider;

const tokenAccessor = await tokenProvider.getTokens([
token0Str,
token1Str,
]);
const tokenAccessor = await tokenProvider.getTokens([token0Str, token1Str]);

const chainId = ID_TO_CHAIN_ID(chainIdNumb);
const tokenIn: Currency =
Expand Down Expand Up @@ -101,14 +98,14 @@ export class QuoteToRatio extends BaseCommand {
liquidity: 1,
});

let swapRoutes: SwapRoute<any> | null;
let swapRoutes: SwapRoute | null;
swapRoutes = await router.routeToRatio(
tokenInBalance,
tokenOutBalance,
position,
{
errorTolerance: new Fraction(1, 100),
maxIterations: 6
maxIterations: 6,
},
{
deadline: 100,
Expand Down
72 changes: 52 additions & 20 deletions cli/commands/quote.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { flags } from '@oclif/command';
import { Currency, Ether, Percent } from '@uniswap/sdk-core';
import { Currency, Ether, Percent, TradeType } from '@uniswap/sdk-core';
import dotenv from 'dotenv';
import { ethers } from 'ethers';
import _ from 'lodash';
import { ID_TO_CHAIN_ID, parseAmount, SwapRoute } from '../../src';
import { Protocol, TO_PROTOCOL } from '../../src/util/protocols';
import { BaseCommand } from '../base-command';

dotenv.config();
Expand All @@ -23,6 +25,8 @@ export class Quote extends BaseCommand {
amount: flags.string({ char: 'a', required: true }),
exactIn: flags.boolean({ required: false }),
exactOut: flags.boolean({ required: false }),
protocols: flags.string({ required: false }),
forceCrossProtocol: flags.boolean({ required: false, default: false }),
};

async run() {
Expand All @@ -41,17 +45,33 @@ export class Quote extends BaseCommand {
topNWithEachBaseToken,
topNWithBaseToken,
topNWithBaseTokenInSet,
topNDirectSwaps,
maxSwapsPerPath,
minSplits,
maxSplits,
distributionPercent,
chainId: chainIdNumb,
protocols: protocolsStr,
forceCrossProtocol,
} = flags;

if ((exactIn && exactOut) || (!exactIn && !exactOut)) {
throw new Error('Must set either --exactIn or --exactOut.');
}

let protocols: Protocol[] = [];
if (protocolsStr) {
try {
protocols = _.map(protocolsStr.split(','), (protocolStr) =>
TO_PROTOCOL(protocolStr)
);
} catch (err) {
throw new Error(
`Protocols invalid. Valid options: ${Object.values(Protocol)}`
);
}
}

const chainId = ID_TO_CHAIN_ID(chainIdNumb);

const log = this.logger;
Expand All @@ -72,53 +92,65 @@ export class Quote extends BaseCommand {
? Ether.onChain(chainId)
: tokenAccessor.getTokenByAddress(tokenOutStr)!;

let swapRoutes: SwapRoute<any> | null;
let swapRoutes: SwapRoute | null;
if (exactIn) {
const amountIn = parseAmount(amountStr, tokenIn);
swapRoutes = await router.routeExactIn(
tokenIn,
tokenOut,
swapRoutes = await router.route(
amountIn,
tokenOut,
TradeType.EXACT_INPUT,
{
deadline: 100,
recipient,
slippageTolerance: new Percent(5, 10_000),
},
{
topN,
topNTokenInOut,
topNSecondHop,
topNWithEachBaseToken,
topNWithBaseToken,
topNWithBaseTokenInSet,
blockNumber: this.blockNumber - 10,
v3PoolSelection: {
topN,
topNTokenInOut,
topNSecondHop,
topNWithEachBaseToken,
topNWithBaseToken,
topNWithBaseTokenInSet,
topNDirectSwaps,
},
maxSwapsPerPath,
minSplits,
maxSplits,
distributionPercent,
protocols,
forceCrossProtocol,
}
);
} else {
const amountOut = parseAmount(amountStr, tokenOut);
swapRoutes = await router.routeExactOut(
tokenIn,
tokenOut,
swapRoutes = await router.route(
amountOut,
tokenIn,
TradeType.EXACT_OUTPUT,
{
deadline: 100,
recipient,
slippageTolerance: new Percent(5, 10_000),
},
{
topN,
topNTokenInOut,
topNSecondHop,
topNWithEachBaseToken,
topNWithBaseToken,
topNWithBaseTokenInSet,
blockNumber: this.blockNumber - 10,
v3PoolSelection: {
topN,
topNTokenInOut,
topNSecondHop,
topNWithEachBaseToken,
topNWithBaseToken,
topNWithBaseTokenInSet,
topNDirectSwaps,
},
maxSwapsPerPath,
minSplits,
maxSplits,
distributionPercent,
protocols,
forceCrossProtocol,
}
);
}
Expand Down
Loading

0 comments on commit 19c5611

Please sign in to comment.