Compare commits
7 Commits
@0x/contra
...
protocol@9
Author | SHA1 | Date | |
---|---|---|---|
|
9ce090c8cd | ||
|
980d60deb8 | ||
|
d6d79e51e7 | ||
|
3ef5de93bb | ||
|
ab7dc33ca4 | ||
|
14dcee5bb6 | ||
|
9856e78609 |
@@ -1,4 +1,14 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"version": "16.65.0",
|
||||||
|
"changes": [
|
||||||
|
{
|
||||||
|
"note": "Use 0x gas api instead of eth gas station api",
|
||||||
|
"pr": 532
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": 1659391840
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"version": "16.64.0",
|
"version": "16.64.0",
|
||||||
"changes": [
|
"changes": [
|
||||||
|
@@ -5,6 +5,10 @@ Edit the package's CHANGELOG.json file only.
|
|||||||
|
|
||||||
CHANGELOG
|
CHANGELOG
|
||||||
|
|
||||||
|
## v16.65.0 - _August 1, 2022_
|
||||||
|
|
||||||
|
* Use 0x gas api instead of eth gas station api (#532)
|
||||||
|
|
||||||
## v16.64.0 - _July 27, 2022_
|
## v16.64.0 - _July 27, 2022_
|
||||||
|
|
||||||
* Refactor `TokenAdjacency` and `TokenAdjacencyBuilder` (#517)
|
* Refactor `TokenAdjacency` and `TokenAdjacencyBuilder` (#517)
|
||||||
|
@@ -1897,7 +1897,7 @@ ___
|
|||||||
|
|
||||||
# Interface: SwapQuoteRequestOpts
|
# Interface: SwapQuoteRequestOpts
|
||||||
|
|
||||||
slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.2 (20%).
|
slippagePercentage: The percentage buffer to add to account for slippage. Affects max ETH price estimates. Defaults to 0.01 (1%).
|
||||||
gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
|
gasPrice: gas price to determine protocolFee amount, default to ethGasStation fast amount
|
||||||
|
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@0x/asset-swapper",
|
"name": "@0x/asset-swapper",
|
||||||
"version": "16.64.0",
|
"version": "16.65.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.12"
|
"node": ">=6.12"
|
||||||
},
|
},
|
||||||
@@ -109,6 +109,7 @@
|
|||||||
"dirty-chai": "^2.0.1",
|
"dirty-chai": "^2.0.1",
|
||||||
"gitpkg": "https://github.com/0xProject/gitpkg.git",
|
"gitpkg": "https://github.com/0xProject/gitpkg.git",
|
||||||
"mocha": "^6.2.0",
|
"mocha": "^6.2.0",
|
||||||
|
"msw": "^0.44.2",
|
||||||
"npm-run-all": "^4.1.2",
|
"npm-run-all": "^4.1.2",
|
||||||
"nyc": "^11.0.1",
|
"nyc": "^11.0.1",
|
||||||
"shx": "^0.2.2",
|
"shx": "^0.2.2",
|
||||||
|
@@ -19,7 +19,7 @@ import {
|
|||||||
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
DEFAULT_TOKEN_ADJACENCY_GRAPH_BY_CHAIN_ID,
|
||||||
} from './utils/market_operation_utils/constants';
|
} from './utils/market_operation_utils/constants';
|
||||||
|
|
||||||
const ETH_GAS_STATION_API_URL = 'https://ethgasstation.info/api/ethgasAPI.json';
|
const ZERO_EX_GAS_API_URL = 'https://gas.api.0x.org/source/median';
|
||||||
const NULL_BYTES = '0x';
|
const NULL_BYTES = '0x';
|
||||||
const NULL_ERC20_ASSET_DATA = '0xf47261b00000000000000000000000000000000000000000000000000000000000000000';
|
const NULL_ERC20_ASSET_DATA = '0xf47261b00000000000000000000000000000000000000000000000000000000000000000';
|
||||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||||
@@ -48,7 +48,7 @@ const DEFAULT_SWAP_QUOTER_OPTS: SwapQuoterOpts = {
|
|||||||
orderRefreshIntervalMs: 10000, // 10 seconds
|
orderRefreshIntervalMs: 10000, // 10 seconds
|
||||||
...DEFAULT_ORDER_PRUNER_OPTS,
|
...DEFAULT_ORDER_PRUNER_OPTS,
|
||||||
samplerGasLimit: 500e6,
|
samplerGasLimit: 500e6,
|
||||||
ethGasStationUrl: ETH_GAS_STATION_API_URL,
|
zeroExGasApiUrl: ZERO_EX_GAS_API_URL,
|
||||||
rfqt: {
|
rfqt: {
|
||||||
integratorsWhitelist: [],
|
integratorsWhitelist: [],
|
||||||
makerAssetOfferings: {},
|
makerAssetOfferings: {},
|
||||||
@@ -99,7 +99,7 @@ export const POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS = new BigNumber(30000);
|
|||||||
export const KEEP_ALIVE_TTL = 5 * 60 * ONE_SECOND_MS;
|
export const KEEP_ALIVE_TTL = 5 * 60 * ONE_SECOND_MS;
|
||||||
|
|
||||||
export const constants = {
|
export const constants = {
|
||||||
ETH_GAS_STATION_API_URL,
|
ZERO_EX_GAS_API_URL,
|
||||||
PROTOCOL_FEE_MULTIPLIER,
|
PROTOCOL_FEE_MULTIPLIER,
|
||||||
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS,
|
POSITIVE_SLIPPAGE_FEE_TRANSFORMER_GAS,
|
||||||
NULL_BYTES,
|
NULL_BYTES,
|
||||||
|
@@ -157,8 +157,6 @@ export {
|
|||||||
GetMarketOrdersRfqOpts,
|
GetMarketOrdersRfqOpts,
|
||||||
LiquidityProviderFillData,
|
LiquidityProviderFillData,
|
||||||
LiquidityProviderRegistry,
|
LiquidityProviderRegistry,
|
||||||
MarketDepth,
|
|
||||||
MarketDepthSide,
|
|
||||||
MooniswapFillData,
|
MooniswapFillData,
|
||||||
MultiHopFillData,
|
MultiHopFillData,
|
||||||
NativeRfqOrderFillData,
|
NativeRfqOrderFillData,
|
||||||
|
@@ -36,9 +36,6 @@ import {
|
|||||||
FillData,
|
FillData,
|
||||||
GasSchedule,
|
GasSchedule,
|
||||||
GetMarketOrdersOpts,
|
GetMarketOrdersOpts,
|
||||||
MarketDepth,
|
|
||||||
MarketDepthSide,
|
|
||||||
MarketSideLiquidity,
|
|
||||||
OptimizedMarketOrder,
|
OptimizedMarketOrder,
|
||||||
OptimizerResultWithReport,
|
OptimizerResultWithReport,
|
||||||
} from './utils/market_operation_utils/types';
|
} from './utils/market_operation_utils/types';
|
||||||
@@ -112,7 +109,7 @@ export class SwapQuoter {
|
|||||||
};
|
};
|
||||||
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
this._protocolFeeUtils = ProtocolFeeUtils.getInstance(
|
||||||
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
constants.PROTOCOL_FEE_UTILS_POLLING_INTERVAL_IN_MS,
|
||||||
options.ethGasStationUrl,
|
options.zeroExGasApiUrl,
|
||||||
);
|
);
|
||||||
// Allow the sampler bytecode to be overwritten using geths override functionality
|
// Allow the sampler bytecode to be overwritten using geths override functionality
|
||||||
const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
||||||
@@ -228,67 +225,6 @@ export class SwapQuoter {
|
|||||||
return batchSwapQuotes.filter(x => x !== undefined) as MarketBuySwapQuote[];
|
return batchSwapQuotes.filter(x => x !== undefined) as MarketBuySwapQuote[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the bids and asks liquidity for the entire market.
|
|
||||||
* For certain sources (like AMM's) it is recommended to provide a practical maximum takerAssetAmount.
|
|
||||||
* @param makerTokenAddress The address of the maker asset
|
|
||||||
* @param takerTokenAddress The address of the taker asset
|
|
||||||
* @param takerAssetAmount The amount to sell and buy for the bids and asks.
|
|
||||||
*
|
|
||||||
* @return An object that conforms to MarketDepth that contains all of the samples and liquidity
|
|
||||||
* information for the source.
|
|
||||||
*/
|
|
||||||
public async getBidAskLiquidityForMakerTakerAssetPairAsync(
|
|
||||||
makerToken: string,
|
|
||||||
takerToken: string,
|
|
||||||
takerAssetAmount: BigNumber,
|
|
||||||
options: Partial<SwapQuoteRequestOpts> = {},
|
|
||||||
): Promise<MarketDepth> {
|
|
||||||
assert.isString('makerToken', makerToken);
|
|
||||||
assert.isString('takerToken', takerToken);
|
|
||||||
const sourceFilters = new SourceFilters([], options.excludedSources, options.includedSources);
|
|
||||||
|
|
||||||
let [sellOrders, buyOrders] = !sourceFilters.isAllowed(ERC20BridgeSource.Native)
|
|
||||||
? [[], []]
|
|
||||||
: await Promise.all([
|
|
||||||
this.orderbook.getOrdersAsync(makerToken, takerToken),
|
|
||||||
this.orderbook.getOrdersAsync(takerToken, makerToken),
|
|
||||||
]);
|
|
||||||
if (!sellOrders || sellOrders.length === 0) {
|
|
||||||
sellOrders = [createDummyOrder(makerToken, takerToken)];
|
|
||||||
}
|
|
||||||
if (!buyOrders || buyOrders.length === 0) {
|
|
||||||
buyOrders = [createDummyOrder(takerToken, makerToken)];
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMarketDepthSide = (marketSideLiquidity: MarketSideLiquidity): MarketDepthSide => {
|
|
||||||
const { dexQuotes, nativeOrders } = marketSideLiquidity.quotes;
|
|
||||||
const { side } = marketSideLiquidity;
|
|
||||||
|
|
||||||
return [
|
|
||||||
...dexQuotes,
|
|
||||||
nativeOrders.map(o => {
|
|
||||||
return {
|
|
||||||
input: side === MarketOperation.Sell ? o.fillableTakerAmount : o.fillableMakerAmount,
|
|
||||||
output: side === MarketOperation.Sell ? o.fillableMakerAmount : o.fillableTakerAmount,
|
|
||||||
fillData: o,
|
|
||||||
source: ERC20BridgeSource.Native,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
const [bids, asks] = await Promise.all([
|
|
||||||
this._marketOperationUtils.getMarketBuyLiquidityAsync(buyOrders, takerAssetAmount, options),
|
|
||||||
this._marketOperationUtils.getMarketSellLiquidityAsync(sellOrders, takerAssetAmount, options),
|
|
||||||
]);
|
|
||||||
return {
|
|
||||||
bids: getMarketDepthSide(bids),
|
|
||||||
asks: getMarketDepthSide(asks),
|
|
||||||
makerTokenDecimals: asks.makerTokenDecimals,
|
|
||||||
takerTokenDecimals: asks.takerTokenDecimals,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the recommended gas price for a fast transaction
|
* Returns the recommended gas price for a fast transaction
|
||||||
*/
|
*/
|
||||||
|
@@ -337,7 +337,7 @@ export interface SwapQuoterOpts extends OrderPrunerOpts {
|
|||||||
contractAddresses?: AssetSwapperContractAddresses;
|
contractAddresses?: AssetSwapperContractAddresses;
|
||||||
samplerGasLimit?: number;
|
samplerGasLimit?: number;
|
||||||
multiBridgeAddress?: string;
|
multiBridgeAddress?: string;
|
||||||
ethGasStationUrl?: string;
|
zeroExGasApiUrl?: string;
|
||||||
rfqt?: SwapQuoterRfqOpts;
|
rfqt?: SwapQuoterRfqOpts;
|
||||||
samplerOverrides?: SamplerOverrides;
|
samplerOverrides?: SamplerOverrides;
|
||||||
tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
tokenAdjacencyGraph?: TokenAdjacencyGraph;
|
||||||
|
@@ -2176,7 +2176,6 @@ export const LIDO_INFO_BY_CHAIN = valueByChainId<LidoInfo>(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
|
|
||||||
export const BALANCER_TOP_POOLS_FETCHED = 250;
|
export const BALANCER_TOP_POOLS_FETCHED = 250;
|
||||||
export const BALANCER_MAX_POOLS_FETCHED = 3;
|
export const BALANCER_MAX_POOLS_FETCHED = 3;
|
||||||
|
|
||||||
|
@@ -862,14 +862,9 @@ export class MarketOperationUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
||||||
void Promise.all(
|
_.values(this._sampler.poolsCaches)
|
||||||
Object.values(this._sampler.poolsCaches).map(async cache => {
|
.filter(cache => cache !== undefined && !cache.isFresh(takerToken, makerToken))
|
||||||
if (!cache || cache.isFresh(takerToken, makerToken)) {
|
.forEach(cache => cache?.getFreshPoolsForPairAsync(takerToken, makerToken));
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
return cache.getFreshPoolsForPairAsync(takerToken, makerToken);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,16 +1,21 @@
|
|||||||
|
import { ChainId } from '@0x/contract-addresses';
|
||||||
import { getPoolsWithTokens, parsePoolData } from 'balancer-labs-sor-v1';
|
import { getPoolsWithTokens, parsePoolData } from 'balancer-labs-sor-v1';
|
||||||
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
||||||
import { gql, request } from 'graphql-request';
|
import { gql, request } from 'graphql-request';
|
||||||
|
|
||||||
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
||||||
import { LogFunction } from '../../../types';
|
import { LogFunction } from '../../../types';
|
||||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_SUBGRAPH_URL, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
||||||
|
|
||||||
import { CacheValue, PoolsCache } from './pools_cache';
|
import { NoOpPoolsCache } from './no_op_pools_cache';
|
||||||
|
import { AbstractPoolsCache, CacheValue, PoolsCache } from './pools_cache';
|
||||||
|
|
||||||
// tslint:disable:custom-no-magic-numbers
|
// tslint:disable:custom-no-magic-numbers
|
||||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||||
// tslint:enable:custom-no-magic-numbers
|
// tslint:enable:custom-no-magic-numbers
|
||||||
|
// tslint:disable: member-ordering
|
||||||
|
|
||||||
|
const BALANCER_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name/balancer-labs/balancer';
|
||||||
|
|
||||||
interface BalancerPoolResponse {
|
interface BalancerPoolResponse {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,10 +25,18 @@ interface BalancerPoolResponse {
|
|||||||
totalWeight: string;
|
totalWeight: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BalancerPoolsCache extends PoolsCache {
|
export class BalancerPoolsCache extends AbstractPoolsCache {
|
||||||
constructor(
|
public static create(chainId: ChainId): PoolsCache {
|
||||||
|
if (chainId !== ChainId.Mainnet) {
|
||||||
|
return new NoOpPoolsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new BalancerPoolsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(
|
||||||
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
private readonly _subgraphUrl: string = BALANCER_SUBGRAPH_URL,
|
||||||
cache: { [key: string]: CacheValue } = {},
|
cache: Map<string, CacheValue> = new Map(),
|
||||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||||
|
@@ -9,7 +9,10 @@ import { LogFunction } from '../../../types';
|
|||||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
||||||
|
|
||||||
import { parsePoolData } from './balancer_sor_v2';
|
import { parsePoolData } from './balancer_sor_v2';
|
||||||
import { CacheValue, PoolsCache } from './pools_cache';
|
import { NoOpPoolsCache } from './no_op_pools_cache';
|
||||||
|
import { AbstractPoolsCache, CacheValue, PoolsCache } from './pools_cache';
|
||||||
|
|
||||||
|
// tslint:disable: member-ordering
|
||||||
|
|
||||||
const BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN = new Map<ChainId, string>([
|
const BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN = new Map<ChainId, string>([
|
||||||
[ChainId.Fantom, 'https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx'],
|
[ChainId.Fantom, 'https://api.thegraph.com/subgraphs/name/beethovenxfi/beethovenx'],
|
||||||
@@ -28,11 +31,11 @@ interface BalancerPoolResponse {
|
|||||||
amp: string | null;
|
amp: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BalancerV2PoolsCache extends PoolsCache {
|
export class BalancerV2PoolsCache extends AbstractPoolsCache {
|
||||||
public static createBeethovenXPoolCache(chainId: ChainId): BalancerV2PoolsCache | undefined {
|
public static createBeethovenXPoolCache(chainId: ChainId): PoolsCache {
|
||||||
const subgraphUrl = BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN.get(chainId);
|
const subgraphUrl = BEETHOVEN_X_SUBGRAPH_URL_BY_CHAIN.get(chainId);
|
||||||
if (subgraphUrl === undefined) {
|
if (subgraphUrl === undefined) {
|
||||||
return undefined;
|
return new NoOpPoolsCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new BalancerV2PoolsCache(subgraphUrl);
|
return new BalancerV2PoolsCache(subgraphUrl);
|
||||||
@@ -58,12 +61,12 @@ export class BalancerV2PoolsCache extends PoolsCache {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
private constructor(
|
||||||
private readonly subgraphUrl: string,
|
private readonly subgraphUrl: string,
|
||||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||||
cache: { [key: string]: CacheValue } = {},
|
cache: Map<string, CacheValue> = new Map(),
|
||||||
) {
|
) {
|
||||||
super(cache);
|
super(cache);
|
||||||
void this._loadTopPoolsAsync();
|
void this._loadTopPoolsAsync();
|
||||||
|
@@ -1,18 +1,20 @@
|
|||||||
|
import { ChainId } from '@0x/contract-addresses';
|
||||||
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
import { Pool } from 'balancer-labs-sor-v1/dist/types';
|
||||||
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
|
import { getPoolsWithTokens, parsePoolData } from 'cream-sor';
|
||||||
|
|
||||||
import { BALANCER_MAX_POOLS_FETCHED } from '../constants';
|
import { BALANCER_MAX_POOLS_FETCHED } from '../constants';
|
||||||
|
|
||||||
import { CacheValue, PoolsCache } from './pools_cache';
|
import { NoOpPoolsCache } from './no_op_pools_cache';
|
||||||
|
import { AbstractPoolsCache, CacheValue, PoolsCache } from './pools_cache';
|
||||||
|
|
||||||
export class CreamPoolsCache extends PoolsCache {
|
export class CreamPoolsCache extends AbstractPoolsCache {
|
||||||
constructor(
|
public static create(chainId: ChainId): PoolsCache {
|
||||||
_cache: { [key: string]: CacheValue } = {},
|
if (chainId !== ChainId.Mainnet) {
|
||||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
return new NoOpPoolsCache();
|
||||||
) {
|
}
|
||||||
super(_cache);
|
|
||||||
|
return new CreamPoolsCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||||
try {
|
try {
|
||||||
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
const poolData = (await getPoolsWithTokens(takerToken, makerToken)).pools;
|
||||||
@@ -25,4 +27,10 @@ export class CreamPoolsCache extends PoolsCache {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private constructor(
|
||||||
|
_cache: Map<string, CacheValue> = new Map(),
|
||||||
|
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||||
|
) {
|
||||||
|
super(_cache);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
export { BalancerPoolsCache } from './balancer_utils';
|
export { BalancerPoolsCache } from './balancer_utils';
|
||||||
export { BalancerV2PoolsCache } from './balancer_v2_utils';
|
export { BalancerV2PoolsCache } from './balancer_v2_utils';
|
||||||
export { CreamPoolsCache } from './cream_utils';
|
export { CreamPoolsCache } from './cream_utils';
|
||||||
export { PoolsCache } from './pools_cache';
|
export { AbstractPoolsCache, PoolsCache } from './pools_cache';
|
||||||
|
@@ -0,0 +1,21 @@
|
|||||||
|
import { Pool, PoolsCache } from './pools_cache';
|
||||||
|
|
||||||
|
// tslint:disable:prefer-function-over-method
|
||||||
|
|
||||||
|
export class NoOpPoolsCache implements PoolsCache {
|
||||||
|
public async getFreshPoolsForPairAsync(
|
||||||
|
_takerToken: string,
|
||||||
|
_makerToken: string,
|
||||||
|
_timeoutMs?: number | undefined,
|
||||||
|
): Promise<Pool[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getPoolAddressesForPair(_takerToken: string, _makerToken: string): string[] {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public isFresh(_takerToken: string, _makerToken: string): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@@ -10,15 +10,29 @@ export interface CacheValue {
|
|||||||
// tslint:disable:custom-no-magic-numbers
|
// tslint:disable:custom-no-magic-numbers
|
||||||
// Cache results for 30mins
|
// Cache results for 30mins
|
||||||
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
|
const DEFAULT_CACHE_TIME_MS = (ONE_HOUR_IN_SECONDS / 2) * ONE_SECOND_MS;
|
||||||
const DEFAULT_TIMEOUT_MS = 1000;
|
const DEFAULT_TIMEOUT_MS = 3000;
|
||||||
// tslint:enable:custom-no-magic-numbers
|
// tslint:enable:custom-no-magic-numbers
|
||||||
|
|
||||||
export abstract class PoolsCache {
|
export interface PoolsCache {
|
||||||
protected static _isExpired(value: CacheValue): boolean {
|
getFreshPoolsForPairAsync(takerToken: string, makerToken: string, timeoutMs?: number): Promise<Pool[]>;
|
||||||
|
getPoolAddressesForPair(takerToken: string, makerToken: string): string[];
|
||||||
|
isFresh(takerToken: string, makerToken: string): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class AbstractPoolsCache implements PoolsCache {
|
||||||
|
protected static _getKey(takerToken: string, makerToken: string): string {
|
||||||
|
return `${takerToken}-${makerToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static _isExpired(value: CacheValue | undefined): boolean {
|
||||||
|
if (value === undefined) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return Date.now() >= value.expiresAt;
|
return Date.now() >= value.expiresAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly _cache: { [key: string]: CacheValue },
|
protected readonly _cache: Map<string, CacheValue>,
|
||||||
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
|
protected readonly _cacheTimeMs: number = DEFAULT_CACHE_TIME_MS,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@@ -31,47 +45,42 @@ export abstract class PoolsCache {
|
|||||||
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
|
return Promise.race([this._getAndSaveFreshPoolsForPairAsync(takerToken, makerToken), timeout]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCachedPoolAddressesForPair(
|
/**
|
||||||
takerToken: string,
|
* Returns pool addresses (can be stale) for a pair.
|
||||||
makerToken: string,
|
*
|
||||||
ignoreExpired: boolean = true,
|
* An empty array will be returned if cache does not exist.
|
||||||
): string[] | undefined {
|
*/
|
||||||
const key = JSON.stringify([takerToken, makerToken]);
|
public getPoolAddressesForPair(takerToken: string, makerToken: string): string[] {
|
||||||
const value = this._cache[key];
|
const value = this._getValue(takerToken, makerToken);
|
||||||
if (ignoreExpired) {
|
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
||||||
return value === undefined ? [] : value.pools.map(pool => pool.id);
|
|
||||||
}
|
|
||||||
if (!value) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (PoolsCache._isExpired(value)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
return (value || []).pools.map(pool => pool.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public isFresh(takerToken: string, makerToken: string): boolean {
|
public isFresh(takerToken: string, makerToken: string): boolean {
|
||||||
const cached = this.getCachedPoolAddressesForPair(takerToken, makerToken, false);
|
const value = this._getValue(takerToken, makerToken);
|
||||||
return cached !== undefined;
|
return !AbstractPoolsCache._isExpired(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _getValue(takerToken: string, makerToken: string): CacheValue | undefined {
|
||||||
|
const key = AbstractPoolsCache._getKey(takerToken, makerToken);
|
||||||
|
return this._cache.get(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async _getAndSaveFreshPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
protected async _getAndSaveFreshPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||||
const key = JSON.stringify([takerToken, makerToken]);
|
const key = AbstractPoolsCache._getKey(takerToken, makerToken);
|
||||||
const value = this._cache[key];
|
const value = this._cache.get(key);
|
||||||
if (value === undefined || value.expiresAt >= Date.now()) {
|
if (!AbstractPoolsCache._isExpired(value)) {
|
||||||
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
return value!.pools;
|
||||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
|
||||||
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
|
|
||||||
}
|
}
|
||||||
return this._cache[key].pools;
|
|
||||||
|
const pools = await this._fetchPoolsForPairAsync(takerToken, makerToken);
|
||||||
|
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||||
|
this._cachePoolsForPair(takerToken, makerToken, pools, expiresAt);
|
||||||
|
return pools;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[], expiresAt: number): void {
|
protected _cachePoolsForPair(takerToken: string, makerToken: string, pools: Pool[], expiresAt: number): void {
|
||||||
const key = JSON.stringify([takerToken, makerToken]);
|
const key = AbstractPoolsCache._getKey(takerToken, makerToken);
|
||||||
this._cache[key] = {
|
this._cache.set(key, { pools, expiresAt });
|
||||||
pools,
|
|
||||||
expiresAt,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
|
protected abstract _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]>;
|
||||||
|
@@ -56,7 +56,7 @@ import {
|
|||||||
} from './constants';
|
} from './constants';
|
||||||
import { getGeistInfoForPair } from './geist_utils';
|
import { getGeistInfoForPair } from './geist_utils';
|
||||||
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
|
import { getLiquidityProvidersForPair } from './liquidity_provider_utils';
|
||||||
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache } from './pools_cache';
|
import { BalancerPoolsCache, BalancerV2PoolsCache, CreamPoolsCache, PoolsCache } from './pools_cache';
|
||||||
import { BalancerV2SwapInfoCache } from './pools_cache/balancer_v2_utils_new';
|
import { BalancerV2SwapInfoCache } from './pools_cache/balancer_v2_utils_new';
|
||||||
import { SamplerContractOperation } from './sampler_contract_operation';
|
import { SamplerContractOperation } from './sampler_contract_operation';
|
||||||
import { SamplerNoOperation } from './sampler_no_operation';
|
import { SamplerNoOperation } from './sampler_no_operation';
|
||||||
@@ -114,10 +114,10 @@ export const TWO_HOP_SOURCE_FILTERS = SourceFilters.all().exclude([
|
|||||||
export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]);
|
export const BATCH_SOURCE_FILTERS = SourceFilters.all().exclude([ERC20BridgeSource.MultiHop, ERC20BridgeSource.Native]);
|
||||||
|
|
||||||
export interface PoolsCacheMap {
|
export interface PoolsCacheMap {
|
||||||
[ERC20BridgeSource.Balancer]: BalancerPoolsCache;
|
[ERC20BridgeSource.Balancer]: PoolsCache;
|
||||||
[ERC20BridgeSource.BalancerV2]: BalancerV2SwapInfoCache | undefined;
|
[ERC20BridgeSource.BalancerV2]: BalancerV2SwapInfoCache | undefined;
|
||||||
[ERC20BridgeSource.Beethovenx]: BalancerV2PoolsCache | undefined;
|
[ERC20BridgeSource.Beethovenx]: PoolsCache;
|
||||||
[ERC20BridgeSource.Cream]: CreamPoolsCache;
|
[ERC20BridgeSource.Cream]: PoolsCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable:no-inferred-empty-object-type no-unbound-method
|
// tslint:disable:no-inferred-empty-object-type no-unbound-method
|
||||||
@@ -156,8 +156,8 @@ export class SamplerOperations {
|
|||||||
? poolsCaches
|
? poolsCaches
|
||||||
: {
|
: {
|
||||||
[ERC20BridgeSource.Beethovenx]: BalancerV2PoolsCache.createBeethovenXPoolCache(chainId),
|
[ERC20BridgeSource.Beethovenx]: BalancerV2PoolsCache.createBeethovenXPoolCache(chainId),
|
||||||
[ERC20BridgeSource.Balancer]: new BalancerPoolsCache(),
|
[ERC20BridgeSource.Balancer]: BalancerPoolsCache.create(chainId),
|
||||||
[ERC20BridgeSource.Cream]: new CreamPoolsCache(),
|
[ERC20BridgeSource.Cream]: CreamPoolsCache.create(chainId),
|
||||||
[ERC20BridgeSource.BalancerV2]:
|
[ERC20BridgeSource.BalancerV2]:
|
||||||
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[chainId] === NULL_ADDRESS
|
BALANCER_V2_VAULT_ADDRESS_BY_CHAIN[chainId] === NULL_ADDRESS
|
||||||
? undefined
|
? undefined
|
||||||
@@ -1592,20 +1592,17 @@ export class SamplerOperations {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
case ERC20BridgeSource.Balancer:
|
case ERC20BridgeSource.Balancer:
|
||||||
return (
|
return this.poolsCaches[ERC20BridgeSource.Balancer]
|
||||||
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
.getPoolAddressesForPair(takerToken, makerToken)
|
||||||
takerToken,
|
.map(balancerPool =>
|
||||||
makerToken,
|
this.getBalancerSellQuotes(
|
||||||
) || []
|
balancerPool,
|
||||||
).map(balancerPool =>
|
makerToken,
|
||||||
this.getBalancerSellQuotes(
|
takerToken,
|
||||||
balancerPool,
|
takerFillAmounts,
|
||||||
makerToken,
|
ERC20BridgeSource.Balancer,
|
||||||
takerToken,
|
),
|
||||||
takerFillAmounts,
|
);
|
||||||
ERC20BridgeSource.Balancer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ERC20BridgeSource.BalancerV2: {
|
case ERC20BridgeSource.BalancerV2: {
|
||||||
const cache = this.poolsCaches[source];
|
const cache = this.poolsCaches[source];
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
@@ -1624,18 +1621,14 @@ export class SamplerOperations {
|
|||||||
}
|
}
|
||||||
case ERC20BridgeSource.Beethovenx: {
|
case ERC20BridgeSource.Beethovenx: {
|
||||||
const cache = this.poolsCaches[source];
|
const cache = this.poolsCaches[source];
|
||||||
if (cache === undefined) {
|
const poolAddresses = cache.getPoolAddressesForPair(takerToken, makerToken);
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const poolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken) || [];
|
|
||||||
|
|
||||||
const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||||
if (vault === NULL_ADDRESS) {
|
if (vault === NULL_ADDRESS) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
return poolIds.map(poolId =>
|
return poolAddresses.map(poolAddress =>
|
||||||
this.getBalancerV2SellQuotes(
|
this.getBalancerV2SellQuotes(
|
||||||
{ poolId, vault },
|
{ poolId: poolAddress, vault },
|
||||||
makerToken,
|
makerToken,
|
||||||
takerToken,
|
takerToken,
|
||||||
takerFillAmounts,
|
takerFillAmounts,
|
||||||
@@ -1644,20 +1637,17 @@ export class SamplerOperations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
case ERC20BridgeSource.Cream:
|
case ERC20BridgeSource.Cream:
|
||||||
return (
|
return this.poolsCaches[ERC20BridgeSource.Cream]
|
||||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
.getPoolAddressesForPair(takerToken, makerToken)
|
||||||
takerToken,
|
.map(creamPool =>
|
||||||
makerToken,
|
this.getBalancerSellQuotes(
|
||||||
) || []
|
creamPool,
|
||||||
).map(creamPool =>
|
makerToken,
|
||||||
this.getBalancerSellQuotes(
|
takerToken,
|
||||||
creamPool,
|
takerFillAmounts,
|
||||||
makerToken,
|
ERC20BridgeSource.Cream,
|
||||||
takerToken,
|
),
|
||||||
takerFillAmounts,
|
);
|
||||||
ERC20BridgeSource.Cream,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ERC20BridgeSource.Dodo:
|
case ERC20BridgeSource.Dodo:
|
||||||
if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) {
|
if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) {
|
||||||
return [];
|
return [];
|
||||||
@@ -1948,20 +1938,17 @@ export class SamplerOperations {
|
|||||||
),
|
),
|
||||||
];
|
];
|
||||||
case ERC20BridgeSource.Balancer:
|
case ERC20BridgeSource.Balancer:
|
||||||
return (
|
return this.poolsCaches[ERC20BridgeSource.Balancer]
|
||||||
this.poolsCaches[ERC20BridgeSource.Balancer].getCachedPoolAddressesForPair(
|
.getPoolAddressesForPair(takerToken, makerToken)
|
||||||
takerToken,
|
.map(poolAddress =>
|
||||||
makerToken,
|
this.getBalancerBuyQuotes(
|
||||||
) || []
|
poolAddress,
|
||||||
).map(poolAddress =>
|
makerToken,
|
||||||
this.getBalancerBuyQuotes(
|
takerToken,
|
||||||
poolAddress,
|
makerFillAmounts,
|
||||||
makerToken,
|
ERC20BridgeSource.Balancer,
|
||||||
takerToken,
|
),
|
||||||
makerFillAmounts,
|
);
|
||||||
ERC20BridgeSource.Balancer,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ERC20BridgeSource.BalancerV2: {
|
case ERC20BridgeSource.BalancerV2: {
|
||||||
const cache = this.poolsCaches[source];
|
const cache = this.poolsCaches[source];
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
@@ -1986,11 +1973,7 @@ export class SamplerOperations {
|
|||||||
}
|
}
|
||||||
case ERC20BridgeSource.Beethovenx: {
|
case ERC20BridgeSource.Beethovenx: {
|
||||||
const cache = this.poolsCaches[source];
|
const cache = this.poolsCaches[source];
|
||||||
if (cache === undefined) {
|
const poolIds = cache.getPoolAddressesForPair(takerToken, makerToken) || [];
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const poolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken) || [];
|
|
||||||
|
|
||||||
const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
const vault = BEETHOVEN_X_VAULT_ADDRESS_BY_CHAIN[this.chainId];
|
||||||
if (vault === NULL_ADDRESS) {
|
if (vault === NULL_ADDRESS) {
|
||||||
return [];
|
return [];
|
||||||
@@ -2006,20 +1989,17 @@ export class SamplerOperations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
case ERC20BridgeSource.Cream:
|
case ERC20BridgeSource.Cream:
|
||||||
return (
|
return this.poolsCaches[ERC20BridgeSource.Cream]
|
||||||
this.poolsCaches[ERC20BridgeSource.Cream].getCachedPoolAddressesForPair(
|
.getPoolAddressesForPair(takerToken, makerToken)
|
||||||
takerToken,
|
.map(poolAddress =>
|
||||||
makerToken,
|
this.getBalancerBuyQuotes(
|
||||||
) || []
|
poolAddress,
|
||||||
).map(poolAddress =>
|
makerToken,
|
||||||
this.getBalancerBuyQuotes(
|
takerToken,
|
||||||
poolAddress,
|
makerFillAmounts,
|
||||||
makerToken,
|
ERC20BridgeSource.Cream,
|
||||||
takerToken,
|
),
|
||||||
makerFillAmounts,
|
);
|
||||||
ERC20BridgeSource.Cream,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case ERC20BridgeSource.Dodo:
|
case ERC20BridgeSource.Dodo:
|
||||||
if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) {
|
if (!isValidAddress(DODOV1_CONFIG_BY_CHAIN_ID[this.chainId].registry)) {
|
||||||
return [];
|
return [];
|
||||||
|
@@ -617,15 +617,6 @@ export interface OptimizerResultWithReport extends OptimizerResult {
|
|||||||
priceComparisonsReport?: PriceComparisonsReport;
|
priceComparisonsReport?: PriceComparisonsReport;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MarketDepthSide = Array<Array<DexSample<FillData>>>;
|
|
||||||
|
|
||||||
export interface MarketDepth {
|
|
||||||
bids: MarketDepthSide;
|
|
||||||
asks: MarketDepthSide;
|
|
||||||
makerTokenDecimals: number;
|
|
||||||
takerTokenDecimals: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MarketSideLiquidity {
|
export interface MarketSideLiquidity {
|
||||||
side: MarketOperation;
|
side: MarketOperation;
|
||||||
inputAmount: BigNumber;
|
inputAmount: BigNumber;
|
||||||
|
@@ -6,28 +6,36 @@ import { SwapQuoterError } from '../types';
|
|||||||
|
|
||||||
const MAX_ERROR_COUNT = 5;
|
const MAX_ERROR_COUNT = 5;
|
||||||
|
|
||||||
|
interface GasOracleResponse {
|
||||||
|
result: {
|
||||||
|
// gas price in wei
|
||||||
|
fast: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class ProtocolFeeUtils {
|
export class ProtocolFeeUtils {
|
||||||
private static _instance: ProtocolFeeUtils;
|
private static _instance: ProtocolFeeUtils;
|
||||||
private readonly _ethGasStationUrl!: string;
|
private readonly _zeroExGasApiUrl: string;
|
||||||
private readonly _gasPriceHeart: any;
|
private readonly _gasPriceHeart: any;
|
||||||
private _gasPriceEstimation: BigNumber = constants.ZERO_AMOUNT;
|
private _gasPriceEstimation: BigNumber = constants.ZERO_AMOUNT;
|
||||||
private _errorCount: number = 0;
|
private _errorCount: number = 0;
|
||||||
|
|
||||||
public static getInstance(
|
public static getInstance(
|
||||||
gasPricePollingIntervalInMs: number,
|
gasPricePollingIntervalInMs: number,
|
||||||
ethGasStationUrl: string = constants.ETH_GAS_STATION_API_URL,
|
zeroExGasApiUrl: string = constants.ZERO_EX_GAS_API_URL,
|
||||||
initialGasPrice: BigNumber = constants.ZERO_AMOUNT,
|
initialGasPrice: BigNumber = constants.ZERO_AMOUNT,
|
||||||
): ProtocolFeeUtils {
|
): ProtocolFeeUtils {
|
||||||
if (!ProtocolFeeUtils._instance) {
|
if (!ProtocolFeeUtils._instance) {
|
||||||
ProtocolFeeUtils._instance = new ProtocolFeeUtils(
|
ProtocolFeeUtils._instance = new ProtocolFeeUtils(
|
||||||
gasPricePollingIntervalInMs,
|
gasPricePollingIntervalInMs,
|
||||||
ethGasStationUrl,
|
zeroExGasApiUrl,
|
||||||
initialGasPrice,
|
initialGasPrice,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return ProtocolFeeUtils._instance;
|
return ProtocolFeeUtils._instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns gas price (in wei) */
|
||||||
public async getGasPriceEstimationOrThrowAsync(shouldHardRefresh?: boolean): Promise<BigNumber> {
|
public async getGasPriceEstimationOrThrowAsync(shouldHardRefresh?: boolean): Promise<BigNumber> {
|
||||||
if (this._gasPriceEstimation.eq(constants.ZERO_AMOUNT)) {
|
if (this._gasPriceEstimation.eq(constants.ZERO_AMOUNT)) {
|
||||||
return this._getGasPriceFromGasStationOrThrowAsync();
|
return this._getGasPriceFromGasStationOrThrowAsync();
|
||||||
@@ -48,27 +56,21 @@ export class ProtocolFeeUtils {
|
|||||||
|
|
||||||
private constructor(
|
private constructor(
|
||||||
gasPricePollingIntervalInMs: number,
|
gasPricePollingIntervalInMs: number,
|
||||||
ethGasStationUrl: string = constants.ETH_GAS_STATION_API_URL,
|
zeroExGasApiUrl: string = constants.ZERO_EX_GAS_API_URL,
|
||||||
initialGasPrice: BigNumber = constants.ZERO_AMOUNT,
|
initialGasPrice: BigNumber = constants.ZERO_AMOUNT,
|
||||||
) {
|
) {
|
||||||
this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs);
|
this._gasPriceHeart = heartbeats.createHeart(gasPricePollingIntervalInMs);
|
||||||
this._gasPriceEstimation = initialGasPrice;
|
this._gasPriceEstimation = initialGasPrice;
|
||||||
this._ethGasStationUrl = ethGasStationUrl;
|
this._zeroExGasApiUrl = zeroExGasApiUrl;
|
||||||
this._initializeHeartBeat();
|
this._initializeHeartBeat();
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line: prefer-function-over-method
|
// tslint:disable-next-line: prefer-function-over-method
|
||||||
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
|
private async _getGasPriceFromGasStationOrThrowAsync(): Promise<BigNumber> {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(this._ethGasStationUrl);
|
const res = await fetch(this._zeroExGasApiUrl);
|
||||||
const gasInfo = await res.json();
|
const gasInfo: GasOracleResponse = await res.json();
|
||||||
// Eth Gas Station result is gwei * 10
|
const gasPriceWei = new BigNumber(gasInfo.result.fast);
|
||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
|
||||||
const BASE_TEN = 10;
|
|
||||||
const gasPriceGwei = new BigNumber(gasInfo.fast / BASE_TEN);
|
|
||||||
// tslint:disable-next-line:custom-no-magic-numbers
|
|
||||||
const unit = new BigNumber(BASE_TEN).pow(9);
|
|
||||||
const gasPriceWei = unit.times(gasPriceGwei);
|
|
||||||
// Reset the error count to 0 once we have a successful response
|
// Reset the error count to 0 once we have a successful response
|
||||||
this._errorCount = 0;
|
this._errorCount = 0;
|
||||||
return gasPriceWei;
|
return gasPriceWei;
|
||||||
|
@@ -24,7 +24,7 @@ import {
|
|||||||
SOURCE_FLAGS,
|
SOURCE_FLAGS,
|
||||||
ZERO_AMOUNT,
|
ZERO_AMOUNT,
|
||||||
} from '../src/utils/market_operation_utils/constants';
|
} from '../src/utils/market_operation_utils/constants';
|
||||||
import { PoolsCache } from '../src/utils/market_operation_utils/pools_cache';
|
import { AbstractPoolsCache } from '../src/utils/market_operation_utils/pools_cache';
|
||||||
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
import { DexOrderSampler } from '../src/utils/market_operation_utils/sampler';
|
||||||
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
import { BATCH_SOURCE_FILTERS } from '../src/utils/market_operation_utils/sampler_operations';
|
||||||
import { SourceFilters } from '../src/utils/market_operation_utils/source_filters';
|
import { SourceFilters } from '../src/utils/market_operation_utils/source_filters';
|
||||||
@@ -98,9 +98,9 @@ async function getMarketBuyOrdersAsync(
|
|||||||
return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts);
|
return utils.getOptimizerResultAsync(nativeOrders, makerAmount, MarketOperation.Buy, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockPoolsCache extends PoolsCache {
|
class MockPoolsCache extends AbstractPoolsCache {
|
||||||
constructor(private readonly _handler: (takerToken: string, makerToken: string) => Pool[]) {
|
constructor(private readonly _handler: (takerToken: string, makerToken: string) => Pool[]) {
|
||||||
super({});
|
super(new Map());
|
||||||
}
|
}
|
||||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||||
return this._handler(takerToken, makerToken);
|
return this._handler(takerToken, makerToken);
|
||||||
|
@@ -28,12 +28,12 @@ describe('Pools Caches for Balancer-based sampling', () => {
|
|||||||
expect(pools.length).greaterThan(0, `Failed to find any pools for ${takerToken} and ${makerToken}`);
|
expect(pools.length).greaterThan(0, `Failed to find any pools for ${takerToken} and ${makerToken}`);
|
||||||
expect(pools[0]).not.undefined();
|
expect(pools[0]).not.undefined();
|
||||||
expect(Object.keys(pools[0])).to.include.members(poolKeys);
|
expect(Object.keys(pools[0])).to.include.members(poolKeys);
|
||||||
const cachedPoolIds = cache.getCachedPoolAddressesForPair(takerToken, makerToken);
|
const cachedPoolIds = cache.getPoolAddressesForPair(takerToken, makerToken);
|
||||||
expect(cachedPoolIds).to.deep.equal(pools.map(p => p.id));
|
expect(cachedPoolIds).to.deep.equal(pools.map(p => p.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('BalancerPoolsCache', () => {
|
describe('BalancerPoolsCache', () => {
|
||||||
const cache = new BalancerPoolsCache();
|
const cache = BalancerPoolsCache.create(ChainId.Mainnet);
|
||||||
it('fetches pools', async () => {
|
it('fetches pools', async () => {
|
||||||
const pairs = [
|
const pairs = [
|
||||||
[usdcAddress, daiAddress],
|
[usdcAddress, daiAddress],
|
||||||
@@ -58,15 +58,14 @@ describe('Pools Caches for Balancer-based sampling', () => {
|
|||||||
[wftmAddress, fantomWethAddress],
|
[wftmAddress, fantomWethAddress],
|
||||||
];
|
];
|
||||||
|
|
||||||
expect(cache).not.null();
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
pairs.map(async ([takerToken, makerToken]) => fetchAndAssertPoolsAsync(cache!, takerToken, makerToken)),
|
pairs.map(async ([takerToken, makerToken]) => fetchAndAssertPoolsAsync(cache, takerToken, makerToken)),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('CreamPoolsCache', () => {
|
describe('CreamPoolsCache', () => {
|
||||||
const cache = new CreamPoolsCache();
|
const cache = CreamPoolsCache.create(ChainId.Mainnet);
|
||||||
it('fetches pools', async () => {
|
it('fetches pools', async () => {
|
||||||
const pairs = [
|
const pairs = [
|
||||||
[usdcAddress, creamAddress],
|
[usdcAddress, creamAddress],
|
||||||
|
45
packages/asset-swapper/test/protocol_fee_utils_test.ts
Normal file
45
packages/asset-swapper/test/protocol_fee_utils_test.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import * as chai from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
import { rest } from 'msw';
|
||||||
|
import { setupServer } from 'msw/node';
|
||||||
|
|
||||||
|
import { ProtocolFeeUtils } from '../src';
|
||||||
|
|
||||||
|
import { chaiSetup } from './utils/chai_setup';
|
||||||
|
chaiSetup.configure();
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
const server = setupServer(
|
||||||
|
rest.get('https://mock-0x-gas-api.org/median', (_req, res, ctx) => {
|
||||||
|
return res(
|
||||||
|
ctx.json({
|
||||||
|
result: {
|
||||||
|
source: 'MEDIAN',
|
||||||
|
timestamp: 1659386474,
|
||||||
|
instant: 22000000000,
|
||||||
|
fast: 18848500000,
|
||||||
|
standard: 14765010000,
|
||||||
|
low: 13265000000,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
describe('ProtocolFeeUtils', () => {
|
||||||
|
describe('getGasPriceEstimationOrThrowAsync', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
server.listen();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses fast gas price response correctly', async () => {
|
||||||
|
const utils = ProtocolFeeUtils.getInstance(420000, 'https://mock-0x-gas-api.org/median');
|
||||||
|
const gasPrice = await utils.getGasPriceEstimationOrThrowAsync();
|
||||||
|
expect(gasPrice.toNumber()).to.eq(18848500000);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"extends": ["@0x/tslint-config"],
|
"extends": ["@0x/tslint-config"],
|
||||||
"rules": {
|
"rules": {
|
||||||
|
"custom-no-magic-numbers": false,
|
||||||
"max-file-line-count": false,
|
"max-file-line-count": false,
|
||||||
"binary-expression-operand-order": false
|
"binary-expression-operand-order": false
|
||||||
},
|
},
|
||||||
|
Reference in New Issue
Block a user