Compare commits
18 Commits
@0x/contra
...
@0x/contra
Author | SHA1 | Date | |
---|---|---|---|
|
7ef75101b4 | ||
|
6f8aace00d | ||
|
6c264b2f18 | ||
|
df055e1958 | ||
|
70d2117470 | ||
|
2c173ccaf3 | ||
|
d2f4a0c5f3 | ||
|
0d6021e5e3 | ||
|
bb04726e7f | ||
|
220ca370c2 | ||
|
63af4e3e98 | ||
|
9754e12d82 | ||
|
d72ebed246 | ||
|
587fc71058 | ||
|
7d34e09a12 | ||
|
7d15baad0f | ||
|
1e6476ada7 | ||
|
1d6ca5f6b5 |
@@ -2,11 +2,11 @@ version: 2.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
resource_class: large
|
||||
resource_class: xlarge
|
||||
docker:
|
||||
- image: node:12
|
||||
environment:
|
||||
NODE_OPTIONS: '--max-old-space-size=6442'
|
||||
NODE_OPTIONS: '--max-old-space-size=16384'
|
||||
working_directory: ~/repo
|
||||
steps:
|
||||
- checkout
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/contracts-integrations",
|
||||
"version": "2.7.40",
|
||||
"version": "2.7.44",
|
||||
"private": true,
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
@@ -93,7 +93,7 @@
|
||||
"typescript": "4.2.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0x/asset-swapper": "^6.10.0",
|
||||
"@0x/asset-swapper": "^6.14.0",
|
||||
"@0x/base-contract": "^6.4.0",
|
||||
"@0x/contracts-asset-proxy": "^3.7.11",
|
||||
"@0x/contracts-erc1155": "^2.1.29",
|
||||
|
@@ -1,4 +1,60 @@
|
||||
[
|
||||
{
|
||||
"version": "6.14.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add support for additional sources and intermediate tokens on Ropsten",
|
||||
"pr": 231
|
||||
}
|
||||
],
|
||||
"timestamp": 1620810800
|
||||
},
|
||||
{
|
||||
"version": "6.13.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add LiquidityProvider to BSC sources",
|
||||
"pr": 234
|
||||
}
|
||||
],
|
||||
"timestamp": 1620703098
|
||||
},
|
||||
{
|
||||
"version": "6.12.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "`TwoHopSampler` to use `call` over `staticcall` in order to support sources like `Uniswap_V3` and `Balancer_V2`",
|
||||
"pr": 233
|
||||
}
|
||||
],
|
||||
"timestamp": 1620610602
|
||||
},
|
||||
{
|
||||
"version": "6.11.0",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Add price comparisons data separate from the quote report",
|
||||
"pr": 219
|
||||
},
|
||||
{
|
||||
"note": "Add caching for top Balancer V2 pools on startup and during regular intervals",
|
||||
"pr": 228
|
||||
},
|
||||
{
|
||||
"note": "Tweak compiler settings for smaller sampler bytecode",
|
||||
"pr": 229
|
||||
},
|
||||
{
|
||||
"note": "Fix Multiplex multihop encoding for ETH buys/sells",
|
||||
"pr": 230
|
||||
},
|
||||
{
|
||||
"note": "Fix Sampler address override for Ganache",
|
||||
"pr": 232
|
||||
}
|
||||
],
|
||||
"timestamp": 1620362129
|
||||
},
|
||||
{
|
||||
"version": "6.10.0",
|
||||
"changes": [
|
||||
|
@@ -5,6 +5,26 @@ Edit the package's CHANGELOG.json file only.
|
||||
|
||||
CHANGELOG
|
||||
|
||||
## v6.14.0 - _May 12, 2021_
|
||||
|
||||
* Add support for additional sources and intermediate tokens on Ropsten (#231)
|
||||
|
||||
## v6.13.0 - _May 11, 2021_
|
||||
|
||||
* Add LiquidityProvider to BSC sources (#234)
|
||||
|
||||
## v6.12.0 - _May 10, 2021_
|
||||
|
||||
* `TwoHopSampler` to use `call` over `staticcall` in order to support sources like `Uniswap_V3` and `Balancer_V2` (#233)
|
||||
|
||||
## v6.11.0 - _May 7, 2021_
|
||||
|
||||
* Add price comparisons data separate from the quote report (#219)
|
||||
* Add caching for top Balancer V2 pools on startup and during regular intervals (#228)
|
||||
* Tweak compiler settings for smaller sampler bytecode (#229)
|
||||
* Fix Multiplex multihop encoding for ETH buys/sells (#230)
|
||||
* Fix Sampler address override for Ganache (#232)
|
||||
|
||||
## v6.10.0 - _May 5, 2021_
|
||||
|
||||
* Reactivate PancakeSwapV2 and BakerySwap VIP on BSC (#222)
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"shouldSaveStandardInput": true,
|
||||
"compilerSettings": {
|
||||
"evmVersion": "istanbul",
|
||||
"optimizer": { "enabled": false, "runs": 200, "details": { "yul": true } },
|
||||
"optimizer": { "enabled": true, "runs": 200, "details": { "yul": true, "deduplicate": true } },
|
||||
"outputSelection": {
|
||||
"*": {
|
||||
"*": [
|
||||
|
@@ -37,7 +37,6 @@ contract TwoHopSampler {
|
||||
uint256 sellAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
@@ -47,7 +46,7 @@ contract TwoHopSampler {
|
||||
uint256 intermediateAssetAmount = 0;
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, sellAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(firstHopCalls[i]);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (amount > intermediateAssetAmount) {
|
||||
@@ -62,7 +61,7 @@ contract TwoHopSampler {
|
||||
}
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, intermediateAssetAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(secondHopCalls[j]);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (amount > buyAmount) {
|
||||
@@ -80,7 +79,6 @@ contract TwoHopSampler {
|
||||
uint256 buyAmount
|
||||
)
|
||||
public
|
||||
view
|
||||
returns (
|
||||
HopInfo memory firstHop,
|
||||
HopInfo memory secondHop,
|
||||
@@ -91,7 +89,7 @@ contract TwoHopSampler {
|
||||
uint256 intermediateAssetAmount = uint256(-1);
|
||||
for (uint256 j = 0; j != secondHopCalls.length; ++j) {
|
||||
secondHopCalls[j].writeUint256(secondHopCalls[j].length - 32, buyAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(secondHopCalls[j]);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(secondHopCalls[j]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (
|
||||
@@ -109,7 +107,7 @@ contract TwoHopSampler {
|
||||
}
|
||||
for (uint256 i = 0; i != firstHopCalls.length; ++i) {
|
||||
firstHopCalls[i].writeUint256(firstHopCalls[i].length - 32, intermediateAssetAmount);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).staticcall(firstHopCalls[i]);
|
||||
(bool didSucceed, bytes memory returnData) = address(this).call(firstHopCalls[i]);
|
||||
if (didSucceed) {
|
||||
uint256 amount = returnData.readUint256(returnData.length - 32);
|
||||
if (
|
||||
|
@@ -57,9 +57,9 @@ contract UniswapV3Sampler
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
/// @param path Token route. Should be takerToken -> makerToken
|
||||
/// @param takerTokenAmounts Taker token sell amount for each sample.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
/// @return makerTokenAmounts Maker amounts bought at each taker token
|
||||
/// amount.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
function sampleSellsFromUniswapV3(
|
||||
IUniswapV3Quoter quoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
@@ -67,8 +67,8 @@ contract UniswapV3Sampler
|
||||
)
|
||||
public
|
||||
returns (
|
||||
uint256[] memory makerTokenAmounts,
|
||||
bytes[] memory uniswapPaths
|
||||
bytes[] memory uniswapPaths,
|
||||
uint256[] memory makerTokenAmounts
|
||||
)
|
||||
{
|
||||
IUniswapV3Pool[][] memory poolPaths =
|
||||
@@ -108,9 +108,9 @@ contract UniswapV3Sampler
|
||||
/// @param quoter UniswapV3 Quoter contract.
|
||||
/// @param path Token route. Should be takerToken -> makerToken.
|
||||
/// @param makerTokenAmounts Maker token buy amount for each sample.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
/// @return takerTokenAmounts Taker amounts sold at each maker token
|
||||
/// amount.
|
||||
/// @return uniswapPaths The encoded uniswap path for each sample.
|
||||
function sampleBuysFromUniswapV3(
|
||||
IUniswapV3Quoter quoter,
|
||||
IERC20TokenV06[] memory path,
|
||||
@@ -118,8 +118,8 @@ contract UniswapV3Sampler
|
||||
)
|
||||
public
|
||||
returns (
|
||||
uint256[] memory takerTokenAmounts,
|
||||
bytes[] memory uniswapPaths
|
||||
bytes[] memory uniswapPaths,
|
||||
uint256[] memory takerTokenAmounts
|
||||
)
|
||||
{
|
||||
IUniswapV3Pool[][] memory poolPaths =
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@0x/asset-swapper",
|
||||
"version": "6.10.0",
|
||||
"version": "6.14.0",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
@@ -33,7 +33,8 @@
|
||||
"generate_contract_wrappers": "abi-gen --debug --abis ${npm_package_config_abis} --output test/generated-wrappers --backend ethers",
|
||||
"contracts:gen": "contracts-gen generate",
|
||||
"contracts:copy": "contracts-gen copy",
|
||||
"publish:private": "yarn build && gitpkg publish"
|
||||
"publish:private": "yarn build && gitpkg publish",
|
||||
"sampler-size": "jq .compilerOutput.evm.deployedBytecode.object -- test/generated-artifacts/ERC20BridgeSampler.json | echo $(( $(wc -c) / 2 - 1 ))"
|
||||
},
|
||||
"config": {
|
||||
"publicInterfaceContracts": "ERC20BridgeSampler,BalanceChecker,FakeTaker",
|
||||
@@ -66,7 +67,7 @@
|
||||
"@0x/dev-utils": "^4.2.7",
|
||||
"@0x/json-schemas": "^6.1.3",
|
||||
"@0x/protocol-utils": "^1.6.0",
|
||||
"@0x/quote-server": "^5.0.0",
|
||||
"@0x/quote-server": "^6.0.2",
|
||||
"@0x/types": "^3.3.3",
|
||||
"@0x/typescript-typings": "^5.2.0",
|
||||
"@0x/utils": "^6.4.3",
|
||||
|
@@ -5,7 +5,12 @@ export {
|
||||
SendTransactionOpts,
|
||||
} from '@0x/base-contract';
|
||||
export { ContractAddresses } from '@0x/contract-addresses';
|
||||
export { V4RFQFirmQuote, V4RFQIndicativeQuote, V4SignedRfqOrder, TakerRequestQueryParams } from '@0x/quote-server';
|
||||
export {
|
||||
V4RFQFirmQuote,
|
||||
V4RFQIndicativeQuote,
|
||||
V4SignedRfqOrder,
|
||||
TakerRequestQueryParamsUnnested as TakerRequestQueryParams,
|
||||
} from '@0x/quote-server';
|
||||
export { Asset, AssetPairsItem, DecodedLogEvent, EventCallback, IndexedFilterValues } from '@0x/types';
|
||||
export { BigNumber } from '@0x/utils';
|
||||
export {
|
||||
@@ -161,6 +166,7 @@ export {
|
||||
NativeRfqOrderQuoteReportEntry,
|
||||
QuoteReport,
|
||||
QuoteReportEntry,
|
||||
PriceComparisonsReport,
|
||||
} from './utils/quote_report_generator';
|
||||
export { QuoteRequestor } from './utils/quote_requestor';
|
||||
export { ERC20BridgeSamplerContract, BalanceCheckerContract, FakeTakerContract } from './wrappers';
|
||||
|
@@ -565,14 +565,17 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
|
||||
private _encodeMultiplexMultiHopFillCalldata(quote: SwapQuote, opts: ExchangeProxyContractOpts): string {
|
||||
const wrappedMultiHopCalls = [];
|
||||
const tokens: string[] = [];
|
||||
if (opts.isFromETH) {
|
||||
wrappedMultiHopCalls.push({
|
||||
selector: DUMMY_WETH_CONTRACT.getSelector('deposit'),
|
||||
data: NULL_BYTES,
|
||||
});
|
||||
tokens.push(ETH_TOKEN_ADDRESS);
|
||||
}
|
||||
const [firstHopOrder, secondHopOrder] = quote.orders;
|
||||
const intermediateToken = firstHopOrder.makerToken;
|
||||
tokens.push(quote.takerToken, intermediateToken, quote.makerToken);
|
||||
for (const order of [firstHopOrder, secondHopOrder]) {
|
||||
switch (order.source) {
|
||||
case ERC20BridgeSource.UniswapV2:
|
||||
@@ -607,11 +610,12 @@ export class ExchangeProxySwapQuoteConsumer implements SwapQuoteConsumerBase {
|
||||
selector: DUMMY_WETH_CONTRACT.getSelector('withdraw'),
|
||||
data: NULL_BYTES,
|
||||
});
|
||||
tokens.push(ETH_TOKEN_ADDRESS);
|
||||
}
|
||||
return this._exchangeProxy
|
||||
.multiHopFill(
|
||||
{
|
||||
tokens: [quote.takerToken, intermediateToken, quote.makerToken],
|
||||
tokens,
|
||||
sellAmount: quote.worstCaseQuoteInfo.totalTakerAmount,
|
||||
calls: wrappedMultiHopCalls,
|
||||
},
|
||||
|
@@ -114,9 +114,11 @@ export class SwapQuoter {
|
||||
);
|
||||
// Allow the sampler bytecode to be overwritten using geths override functionality
|
||||
const samplerBytecode = _.get(artifacts.ERC20BridgeSampler, 'compilerOutput.evm.deployedBytecode.object');
|
||||
// Allow address of the Sampler to be overridden, i.e in Ganache where overrides do not work
|
||||
const samplerAddress = (options.samplerOverrides && options.samplerOverrides.to) || SAMPLER_ADDRESS;
|
||||
const defaultCodeOverrides = samplerBytecode
|
||||
? {
|
||||
[SAMPLER_ADDRESS]: { code: samplerBytecode },
|
||||
[samplerAddress]: { code: samplerBytecode },
|
||||
}
|
||||
: {};
|
||||
const samplerOverrides = _.assign(
|
||||
@@ -125,7 +127,7 @@ export class SwapQuoter {
|
||||
);
|
||||
const fastAbi = new FastABI(ERC20BridgeSamplerContract.ABI() as MethodAbi[]);
|
||||
const samplerContract = new ERC20BridgeSamplerContract(
|
||||
SAMPLER_ADDRESS,
|
||||
samplerAddress,
|
||||
this.provider,
|
||||
{
|
||||
gas: samplerGasLimit,
|
||||
@@ -495,7 +497,14 @@ function createSwapQuote(
|
||||
gasSchedule: FeeSchedule,
|
||||
slippage: number,
|
||||
): SwapQuote {
|
||||
const { optimizedOrders, quoteReport, sourceFlags, takerAmountPerEth, makerAmountPerEth } = optimizerResult;
|
||||
const {
|
||||
optimizedOrders,
|
||||
quoteReport,
|
||||
sourceFlags,
|
||||
takerAmountPerEth,
|
||||
makerAmountPerEth,
|
||||
priceComparisonsReport,
|
||||
} = optimizerResult;
|
||||
const isTwoHop = sourceFlags === SOURCE_FLAGS[ERC20BridgeSource.MultiHop];
|
||||
|
||||
// Calculate quote info
|
||||
@@ -519,6 +528,7 @@ function createSwapQuote(
|
||||
makerAmountPerEth,
|
||||
quoteReport,
|
||||
isTwoHop,
|
||||
priceComparisonsReport,
|
||||
};
|
||||
|
||||
if (operation === MarketOperation.Buy) {
|
||||
|
@@ -7,7 +7,8 @@ import {
|
||||
RfqOrderFields,
|
||||
Signature,
|
||||
} from '@0x/protocol-utils';
|
||||
import { TakerRequestQueryParams, V4SignedRfqOrder } from '@0x/quote-server';
|
||||
import { TakerRequestQueryParamsUnnested, V4SignedRfqOrder } from '@0x/quote-server';
|
||||
import { Fee } from '@0x/quote-server/lib/src/types';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { AxiosRequestConfig } from 'axios';
|
||||
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
OptimizedMarketOrder,
|
||||
TokenAdjacencyGraph,
|
||||
} from './utils/market_operation_utils/types';
|
||||
import { QuoteReport } from './utils/quote_report_generator';
|
||||
import { PriceComparisonsReport, QuoteReport } from './utils/quote_report_generator';
|
||||
|
||||
/**
|
||||
* expiryBufferMs: The number of seconds to add when calculating whether an order is expired or not. Defaults to 300s (5m).
|
||||
@@ -169,6 +170,7 @@ export interface SwapQuoteBase {
|
||||
worstCaseQuoteInfo: SwapQuoteInfo;
|
||||
sourceBreakdown: SwapQuoteOrdersBreakdown;
|
||||
quoteReport?: QuoteReport;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
isTwoHop: boolean;
|
||||
makerTokenDecimals: number;
|
||||
takerTokenDecimals: number;
|
||||
@@ -231,6 +233,12 @@ export type SwapQuoteOrdersBreakdown = Partial<
|
||||
* If set to `true` and `ERC20BridgeSource.Native` is part of the `excludedSources`
|
||||
* array in `SwapQuoteRequestOpts`, an Error will be raised.
|
||||
*/
|
||||
|
||||
export interface RfqmRequestOptions extends RfqRequestOpts {
|
||||
isLastLook: true;
|
||||
fee: Fee;
|
||||
}
|
||||
|
||||
export interface RfqRequestOpts {
|
||||
takerAddress: string;
|
||||
txOrigin: string;
|
||||
@@ -241,6 +249,7 @@ export interface RfqRequestOpts {
|
||||
nativeExclusivelyRFQ?: boolean;
|
||||
altRfqAssetOfferings?: AltRfqMakerAssetOfferings;
|
||||
isLastLook?: boolean;
|
||||
fee?: Fee;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -365,7 +374,7 @@ export enum OrderPrunerPermittedFeeTypes {
|
||||
export interface MockedRfqQuoteResponse {
|
||||
endpoint: string;
|
||||
requestApiKey: string;
|
||||
requestParams: TakerRequestQueryParams;
|
||||
requestParams: TakerRequestQueryParamsUnnested;
|
||||
responseData: any;
|
||||
responseCode: number;
|
||||
callback?: (config: any) => Promise<any>;
|
||||
@@ -385,6 +394,7 @@ export interface AltMockedRfqQuoteResponse {
|
||||
export interface SamplerOverrides {
|
||||
overrides: GethCallOverrides;
|
||||
block: BlockParam;
|
||||
to?: string;
|
||||
}
|
||||
|
||||
export interface SamplerCallResult {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { Web3Wrapper } from '@0x/dev-utils';
|
||||
import { TakerRequestQueryParams, V4RFQFirmQuote, V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { TakerRequestQueryParamsUnnested, V4RFQFirmQuote, V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { AxiosInstance, CancelToken } from 'axios';
|
||||
|
||||
@@ -123,7 +123,7 @@ export async function returnQuoteFromAltMMAsync<ResponseT>(
|
||||
takerToken: string,
|
||||
maxResponseTimeMs: number,
|
||||
altRfqAssetOfferings: AltRfqMakerAssetOfferings,
|
||||
takerRequestQueryParams: TakerRequestQueryParams,
|
||||
takerRequestQueryParams: TakerRequestQueryParamsUnnested,
|
||||
axiosInstance: AxiosInstance,
|
||||
warningLogger: LogFunction,
|
||||
cancelToken: CancelToken,
|
||||
|
@@ -99,6 +99,8 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.Mooniswap,
|
||||
]),
|
||||
[ChainId.Rinkeby]: new SourceFilters([ERC20BridgeSource.Native]),
|
||||
[ChainId.Kovan]: new SourceFilters([ERC20BridgeSource.Native]),
|
||||
@@ -120,6 +122,7 @@ export const SELL_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
]),
|
||||
},
|
||||
|
||||
@@ -169,6 +172,8 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.Uniswap,
|
||||
ERC20BridgeSource.UniswapV2,
|
||||
ERC20BridgeSource.UniswapV3,
|
||||
ERC20BridgeSource.Curve,
|
||||
ERC20BridgeSource.Mooniswap,
|
||||
]),
|
||||
[ChainId.Rinkeby]: new SourceFilters([ERC20BridgeSource.Native]),
|
||||
[ChainId.Kovan]: new SourceFilters([ERC20BridgeSource.Native]),
|
||||
@@ -190,6 +195,7 @@ export const BUY_SOURCE_FILTER_BY_CHAIN_ID = valueByChainId<SourceFilters>(
|
||||
ERC20BridgeSource.CafeSwap,
|
||||
ERC20BridgeSource.CheeseSwap,
|
||||
ERC20BridgeSource.JulSwap,
|
||||
ERC20BridgeSource.LiquidityProvider,
|
||||
]),
|
||||
},
|
||||
new SourceFilters([]),
|
||||
@@ -414,7 +420,11 @@ export const DEFAULT_INTERMEDIATE_TOKENS_BY_CHAIN_ID = valueByChainId<string[]>(
|
||||
'0x2170ed0880ac9a755fd29b2688956bd959f933f8', // ETH
|
||||
'0x55d398326f99059ff775485246999027b3197955', // BUSD-T
|
||||
],
|
||||
[ChainId.Ropsten]: [getContractAddressesForChainOrThrow(ChainId.Ropsten).etherToken],
|
||||
[ChainId.Ropsten]: [
|
||||
getContractAddressesForChainOrThrow(ChainId.Ropsten).etherToken,
|
||||
'0xad6d458402f60fd3bd25163575031acdce07538d', // DAI
|
||||
'0x07865c6e87b9f70255377e024ace6630c1eaa37f', // USDC
|
||||
],
|
||||
},
|
||||
[],
|
||||
);
|
||||
@@ -453,7 +463,10 @@ export const NATIVE_FEE_TOKEN_BY_CHAIN_ID = valueByChainId<string>(
|
||||
NULL_ADDRESS,
|
||||
);
|
||||
|
||||
export const NATIVE_FEE_TOKEN_AMOUNT_BY_CHAIN_ID = valueByChainId({}, ONE_ETHER);
|
||||
export const NATIVE_FEE_TOKEN_AMOUNT_BY_CHAIN_ID = valueByChainId(
|
||||
{ [ChainId.Mainnet]: ONE_ETHER.times(0.1) },
|
||||
ONE_ETHER,
|
||||
);
|
||||
|
||||
// Order dependent
|
||||
const CURVE_TRI_POOL_MAINNET_TOKENS = [MAINNET_TOKENS.DAI, MAINNET_TOKENS.USDC, MAINNET_TOKENS.USDT];
|
||||
@@ -1059,7 +1072,6 @@ export const BALANCER_V2_SUBGRAPH_URL = 'https://api.thegraph.com/subgraphs/name
|
||||
|
||||
export const UNISWAPV3_CONFIG_BY_CHAIN_ID = valueByChainId(
|
||||
{
|
||||
// Unconfirmed Mainnet contracts, please confirm
|
||||
[ChainId.Mainnet]: {
|
||||
quoter: '0xb27308f9f90d607463bb33ea1bebb41c27ce5ab6',
|
||||
router: '0xe592427a0aece92de3edee1f18e0157c05861564',
|
||||
@@ -1335,5 +1347,6 @@ export const DEFAULT_GET_MARKET_ORDERS_OPTS: GetMarketOrdersOpts = {
|
||||
exchangeProxyOverhead: () => ZERO_AMOUNT,
|
||||
allowFallback: true,
|
||||
shouldGenerateQuoteReport: true,
|
||||
shouldIncludePriceComparisonsReport: false,
|
||||
tokenAdjacencyGraph: { default: [] },
|
||||
};
|
||||
|
@@ -16,7 +16,14 @@ import {
|
||||
getNativeAdjustedMakerFillAmount,
|
||||
} from '../utils';
|
||||
|
||||
import { generateQuoteReport, QuoteReport } from './../quote_report_generator';
|
||||
import {
|
||||
dexSampleToReportSource,
|
||||
generateQuoteReport,
|
||||
multiHopSampleToReportSource,
|
||||
nativeOrderToReportEntry,
|
||||
PriceComparisonsReport,
|
||||
QuoteReport,
|
||||
} from './../quote_report_generator';
|
||||
import { getComparisonPrices } from './comparison_price';
|
||||
import {
|
||||
BUY_SOURCE_FILTER_BY_CHAIN_ID,
|
||||
@@ -68,6 +75,27 @@ export class MarketOperationUtils {
|
||||
return generateQuoteReport(side, quotes.nativeOrders, liquidityDelivered, comparisonPrice, quoteRequestor);
|
||||
}
|
||||
|
||||
private static _computePriceComparisonsReport(
|
||||
quoteRequestor: QuoteRequestor | undefined,
|
||||
marketSideLiquidity: MarketSideLiquidity,
|
||||
comparisonPrice?: BigNumber | undefined,
|
||||
): PriceComparisonsReport {
|
||||
const { side, quotes } = marketSideLiquidity;
|
||||
const dexSources = _.flatten(quotes.dexQuotes).map(quote => dexSampleToReportSource(quote, side));
|
||||
const multiHopSources = quotes.twoHopQuotes.map(quote => multiHopSampleToReportSource(quote, side));
|
||||
const nativeSources = quotes.nativeOrders.map(order =>
|
||||
nativeOrderToReportEntry(
|
||||
order.type,
|
||||
order as any,
|
||||
order.fillableTakerAmount,
|
||||
comparisonPrice,
|
||||
quoteRequestor,
|
||||
),
|
||||
);
|
||||
|
||||
return { dexSources, multiHopSources, nativeSources };
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly _sampler: DexOrderSampler,
|
||||
private readonly contractAddresses: AssetSwapperContractAddresses,
|
||||
@@ -677,7 +705,16 @@ export class MarketOperationUtils {
|
||||
wholeOrderPrice,
|
||||
);
|
||||
}
|
||||
return { ...optimizerResult, quoteReport };
|
||||
|
||||
let priceComparisonsReport: PriceComparisonsReport | undefined;
|
||||
if (_opts.shouldIncludePriceComparisonsReport) {
|
||||
priceComparisonsReport = MarketOperationUtils._computePriceComparisonsReport(
|
||||
_opts.rfqt ? _opts.rfqt.quoteRequestor : undefined,
|
||||
marketSideLiquidity,
|
||||
wholeOrderPrice,
|
||||
);
|
||||
}
|
||||
return { ...optimizerResult, quoteReport, priceComparisonsReport };
|
||||
}
|
||||
|
||||
private async _refreshPoolCacheIfRequiredAsync(takerToken: string, makerToken: string): Promise<void> {
|
||||
|
@@ -0,0 +1,149 @@
|
||||
import { BigNumber } from '@0x/utils';
|
||||
/**
|
||||
* This has been copied from https://github.com/balancer-labs/balancer-sor/blob/john/rc2/src/helpers.ts.
|
||||
* Still awaiting V2 support for @balancer-labs/sor, once full V2 support is shipped we can upgrade sor and delete this file
|
||||
*/
|
||||
export const parsePoolData = (
|
||||
directPools: SubGraphPoolDictionary,
|
||||
tokenIn: string,
|
||||
tokenOut: string,
|
||||
mostLiquidPoolsFirstHop: SubGraphPool[] = [],
|
||||
mostLiquidPoolsSecondHop: SubGraphPool[] = [],
|
||||
hopTokens: string[] = [],
|
||||
): [SubGraphPoolDictionary, Path[]] => {
|
||||
const pathDataList: Path[] = [];
|
||||
const pools: SubGraphPoolDictionary = {};
|
||||
|
||||
// First add direct pair paths
|
||||
// tslint:disable-next-line:forin
|
||||
for (const idKey in directPools) {
|
||||
const p: SubGraphPool = directPools[idKey];
|
||||
// Add pool to the set with all pools (only adds if it's still not present in dict)
|
||||
pools[idKey] = p;
|
||||
|
||||
const swap: Swap = {
|
||||
pool: p.id,
|
||||
tokenIn,
|
||||
tokenOut,
|
||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
||||
tokenOutDecimals: 18,
|
||||
};
|
||||
|
||||
const path: Path = {
|
||||
id: p.id,
|
||||
swaps: [swap],
|
||||
};
|
||||
pathDataList.push(path);
|
||||
}
|
||||
|
||||
// Now add multi-hop paths.
|
||||
// mostLiquidPoolsFirstHop and mostLiquidPoolsSecondHop always has the same
|
||||
// lengh of hopTokens
|
||||
for (let i = 0; i < hopTokens.length; i++) {
|
||||
// Add pools to the set with all pools (only adds if it's still not present in dict)
|
||||
pools[mostLiquidPoolsFirstHop[i].id] = mostLiquidPoolsFirstHop[i];
|
||||
pools[mostLiquidPoolsSecondHop[i].id] = mostLiquidPoolsSecondHop[i];
|
||||
|
||||
const swap1: Swap = {
|
||||
pool: mostLiquidPoolsFirstHop[i].id,
|
||||
tokenIn,
|
||||
tokenOut: hopTokens[i],
|
||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
||||
tokenOutDecimals: 18,
|
||||
};
|
||||
|
||||
const swap2: Swap = {
|
||||
pool: mostLiquidPoolsSecondHop[i].id,
|
||||
tokenIn: hopTokens[i],
|
||||
tokenOut,
|
||||
tokenInDecimals: 18, // Placeholder for actual decimals
|
||||
tokenOutDecimals: 18,
|
||||
};
|
||||
|
||||
const path: Path = {
|
||||
id: mostLiquidPoolsFirstHop[i].id + mostLiquidPoolsSecondHop[i].id, // Path id is the concatenation of the ids of poolFirstHop and poolSecondHop
|
||||
swaps: [swap1, swap2],
|
||||
};
|
||||
pathDataList.push(path);
|
||||
}
|
||||
return [pools, pathDataList];
|
||||
};
|
||||
|
||||
interface SubGraphPool {
|
||||
id: string;
|
||||
swapFee: string;
|
||||
totalWeight: string;
|
||||
totalShares: string;
|
||||
tokens: SubGraphToken[];
|
||||
tokensList: string[];
|
||||
poolType?: string;
|
||||
|
||||
// Only for stable pools
|
||||
amp: string;
|
||||
|
||||
// Only for element pools
|
||||
lpShares?: BigNumber;
|
||||
time?: BigNumber;
|
||||
principalToken?: string;
|
||||
baseToken?: string;
|
||||
}
|
||||
|
||||
interface SubGraphPoolDictionary {
|
||||
[poolId: string]: SubGraphPool;
|
||||
}
|
||||
|
||||
interface SubGraphToken {
|
||||
address: string;
|
||||
balance: string;
|
||||
decimals: string | number;
|
||||
// Stable & Element field
|
||||
weight?: string;
|
||||
}
|
||||
interface Path {
|
||||
id: string; // pool address if direct path, contactenation of pool addresses if multihop
|
||||
swaps: Swap[];
|
||||
poolPairData?: PoolPairData[];
|
||||
limitAmount?: BigNumber;
|
||||
filterEffectivePrice?: BigNumber; // TODO: This is just used for filtering, maybe there is a better way to filter?
|
||||
}
|
||||
|
||||
interface Swap {
|
||||
pool: string;
|
||||
tokenIn: string;
|
||||
tokenOut: string;
|
||||
swapAmount?: string;
|
||||
limitReturnAmount?: string;
|
||||
maxPrice?: string;
|
||||
tokenInDecimals: number;
|
||||
tokenOutDecimals: number;
|
||||
}
|
||||
|
||||
export interface PoolPairData {
|
||||
id: string;
|
||||
poolType?: string; // Todo: make this a mandatory field?
|
||||
pairType?: string; // Todo: make this a mandatory field?
|
||||
tokenIn: string;
|
||||
tokenOut: string;
|
||||
balanceIn?: BigNumber;
|
||||
balanceOut?: BigNumber;
|
||||
decimalsIn: number;
|
||||
decimalsOut: number;
|
||||
swapFee: BigNumber;
|
||||
|
||||
// For weighted & element pools
|
||||
weightIn?: BigNumber;
|
||||
weightOut?: BigNumber;
|
||||
|
||||
// Only for stable pools
|
||||
allBalances: BigNumber[];
|
||||
invariant?: BigNumber;
|
||||
amp?: BigNumber;
|
||||
tokenIndexIn?: number;
|
||||
tokenIndexOut?: number;
|
||||
|
||||
// Only for element pools
|
||||
lpShares?: BigNumber;
|
||||
time?: BigNumber;
|
||||
principalToken?: string;
|
||||
baseToken?: string;
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor';
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_SUBGRAPH_URL, BALANCER_TOP_POOLS_FETCHED } from '../constants';
|
||||
|
||||
@@ -49,16 +50,13 @@ export class BalancerPoolsCache extends PoolsCache {
|
||||
} = {};
|
||||
|
||||
const pools = await this._fetchTopPoolsAsync();
|
||||
pools.forEach(pool => {
|
||||
for (const pool of pools) {
|
||||
const { tokensList } = pool;
|
||||
for (const from of tokensList) {
|
||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
||||
if (!fromToPools[from]) {
|
||||
fromToPools[from] = {};
|
||||
}
|
||||
if (!fromToPools[from][to]) {
|
||||
fromToPools[from][to] = [];
|
||||
}
|
||||
fromToPools[from] = fromToPools[from] || {};
|
||||
fromToPools[from][to] = fromToPools[from][to] || [];
|
||||
|
||||
try {
|
||||
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
|
||||
const poolData = parsePoolData([pool], from, to);
|
||||
@@ -71,45 +69,37 @@ export class BalancerPoolsCache extends PoolsCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
|
||||
const query = `
|
||||
query {
|
||||
pools (first: ${
|
||||
this._topPoolsFetched
|
||||
}, where: {publicSwap: true, liquidity_gt: 0}, orderBy: swapsCount, orderDirection: desc) {
|
||||
id
|
||||
publicSwap
|
||||
swapFee
|
||||
totalWeight
|
||||
tokensList
|
||||
tokens {
|
||||
id
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
symbol
|
||||
denormWeight
|
||||
const query = gql`
|
||||
query fetchTopPools($topPoolsFetched: Int!) {
|
||||
pools(
|
||||
first: $topPoolsFetched
|
||||
where: { publicSwap: true, liquidity_gt: 0 }
|
||||
orderBy: swapsCount
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
publicSwap
|
||||
swapFee
|
||||
totalWeight
|
||||
tokensList
|
||||
tokens {
|
||||
id
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
symbol
|
||||
denormWeight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
`;
|
||||
try {
|
||||
const response = await fetch(this._subgraphUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
}),
|
||||
});
|
||||
|
||||
const { data } = await response.json();
|
||||
return data.pools;
|
||||
const { pools } = await request(this._subgraphUrl, query, { topPoolsFetched: this._topPoolsFetched });
|
||||
return pools;
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
|
@@ -1,19 +1,60 @@
|
||||
// import { getPoolsWithTokens, parsePoolData } from '@balancer-labs/sor'; // TODO - upgrade to v2
|
||||
import { BigNumber } from '@0x/utils';
|
||||
// import { parsePoolData } from '@balancer-labs'; // TODO - upgrade to v2
|
||||
import { Pool } from '@balancer-labs/sor/dist/types';
|
||||
import { request } from 'graphql-request';
|
||||
import { gql, request } from 'graphql-request';
|
||||
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_V2_SUBGRAPH_URL } from '../constants';
|
||||
import { DEFAULT_WARNING_LOGGER } from '../../../constants';
|
||||
import { LogFunction } from '../../../types';
|
||||
import { BALANCER_MAX_POOLS_FETCHED, BALANCER_TOP_POOLS_FETCHED, BALANCER_V2_SUBGRAPH_URL } from '../constants';
|
||||
|
||||
import { parsePoolData } from './balancer_sor_v2';
|
||||
import { CacheValue, PoolsCache } from './pools_cache';
|
||||
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
interface BalancerPoolResponse {
|
||||
id: string;
|
||||
swapFee: string;
|
||||
tokens: Array<{ address: string; decimals: number; balance: string; weight: string; symbol: string }>;
|
||||
tokensList: string[];
|
||||
totalWeight: string;
|
||||
totalShares: string;
|
||||
amp: string | null;
|
||||
}
|
||||
|
||||
export class BalancerV2PoolsCache extends PoolsCache {
|
||||
private static _parseSubgraphPoolData(pool: any, takerToken: string, makerToken: string): Pool {
|
||||
const tToken = pool.tokens.find((t: any) => t.address === takerToken);
|
||||
const mToken = pool.tokens.find((t: any) => t.address === makerToken);
|
||||
const swap = pool.swaps && pool.swaps[0];
|
||||
const tokenAmountOut = swap ? swap.tokenAmountOut : undefined;
|
||||
const tokenAmountIn = swap ? swap.tokenAmountIn : undefined;
|
||||
const spotPrice =
|
||||
tokenAmountOut && tokenAmountIn ? new BigNumber(tokenAmountOut).div(tokenAmountIn) : undefined; // TODO: xianny check
|
||||
|
||||
return {
|
||||
id: pool.id,
|
||||
balanceIn: new BigNumber(tToken.balance),
|
||||
balanceOut: new BigNumber(mToken.balance),
|
||||
weightIn: new BigNumber(tToken.weight),
|
||||
weightOut: new BigNumber(mToken.weight),
|
||||
swapFee: new BigNumber(pool.swapFee),
|
||||
spotPrice,
|
||||
};
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly subgraphUrl: string = BALANCER_V2_SUBGRAPH_URL,
|
||||
private readonly maxPoolsFetched: number = BALANCER_MAX_POOLS_FETCHED,
|
||||
private readonly _topPoolsFetched: number = BALANCER_TOP_POOLS_FETCHED,
|
||||
private readonly _warningLogger: LogFunction = DEFAULT_WARNING_LOGGER,
|
||||
cache: { [key: string]: CacheValue } = {},
|
||||
) {
|
||||
super(cache);
|
||||
void this._loadTopPoolsAsync();
|
||||
// Reload the top pools every 12 hours
|
||||
setInterval(async () => void this._loadTopPoolsAsync(), ONE_DAY_MS / 2);
|
||||
}
|
||||
|
||||
// protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
@@ -29,8 +70,71 @@ export class BalancerV2PoolsCache extends PoolsCache {
|
||||
// }
|
||||
// }
|
||||
|
||||
protected async _fetchTopPoolsAsync(): Promise<BalancerPoolResponse[]> {
|
||||
const query = gql`
|
||||
query fetchTopPools($topPoolsFetched: Int!) {
|
||||
pools(
|
||||
first: $topPoolsFetched
|
||||
where: { totalLiquidity_gt: 0 }
|
||||
orderBy: swapsCount
|
||||
orderDirection: desc
|
||||
) {
|
||||
id
|
||||
swapFee
|
||||
totalWeight
|
||||
tokensList
|
||||
amp
|
||||
totalShares
|
||||
tokens {
|
||||
id
|
||||
address
|
||||
balance
|
||||
decimals
|
||||
symbol
|
||||
weight
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const { pools } = await request<{ pools: BalancerPoolResponse[] }>(this.subgraphUrl, query, {
|
||||
topPoolsFetched: this._topPoolsFetched,
|
||||
});
|
||||
|
||||
return pools;
|
||||
}
|
||||
protected async _loadTopPoolsAsync(): Promise<void> {
|
||||
const fromToPools: {
|
||||
[from: string]: { [to: string]: Pool[] };
|
||||
} = {};
|
||||
|
||||
const pools = await this._fetchTopPoolsAsync();
|
||||
for (const pool of pools) {
|
||||
const { tokensList } = pool;
|
||||
for (const from of tokensList) {
|
||||
for (const to of tokensList.filter(t => t.toLowerCase() !== from.toLowerCase())) {
|
||||
fromToPools[from] = fromToPools[from] || {};
|
||||
fromToPools[from][to] = fromToPools[from][to] || [];
|
||||
|
||||
try {
|
||||
// The list of pools must be relevant to `from` and `to` for `parsePoolData`
|
||||
const [poolData] = parsePoolData({ [pool.id]: pool as any }, from, to);
|
||||
fromToPools[from][to].push(
|
||||
BalancerV2PoolsCache._parseSubgraphPoolData(poolData[pool.id], from, to),
|
||||
);
|
||||
// Cache this as we progress through
|
||||
const expiresAt = Date.now() + this._cacheTimeMs;
|
||||
this._cachePoolsForPair(from, to, fromToPools[from][to], expiresAt);
|
||||
} catch (err) {
|
||||
this._warningLogger(err, `Failed to load Balancer V2 top pools`);
|
||||
// soldier on
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
protected async _fetchPoolsForPairAsync(takerToken: string, makerToken: string): Promise<Pool[]> {
|
||||
const query = `
|
||||
const query = gql`
|
||||
query getPools {
|
||||
pools(
|
||||
first: ${this.maxPoolsFetched},
|
||||
@@ -60,25 +164,7 @@ export class BalancerV2PoolsCache extends PoolsCache {
|
||||
`;
|
||||
try {
|
||||
const { pools } = await request(this.subgraphUrl, query);
|
||||
return pools.map((pool: any) => {
|
||||
const tToken = pool.tokens.find((t: any) => t.address === takerToken);
|
||||
const mToken = pool.tokens.find((t: any) => t.address === makerToken);
|
||||
const swap = pool.swaps[0];
|
||||
const tokenAmountOut = swap ? swap.tokenAmountOut : undefined;
|
||||
const tokenAmountIn = swap ? swap.tokenAmountIn : undefined;
|
||||
const spotPrice =
|
||||
tokenAmountOut && tokenAmountIn ? new BigNumber(tokenAmountOut).div(tokenAmountIn) : undefined; // TODO: xianny check
|
||||
|
||||
return {
|
||||
id: pool.id,
|
||||
balanceIn: new BigNumber(tToken.balance),
|
||||
balanceOut: new BigNumber(mToken.balance),
|
||||
weightIn: new BigNumber(tToken.weight),
|
||||
weightOut: new BigNumber(mToken.weight),
|
||||
swapFee: new BigNumber(pool.swapFee),
|
||||
spotPrice,
|
||||
};
|
||||
});
|
||||
return pools.map((pool: any) => BalancerV2PoolsCache._parseSubgraphPoolData(pool, takerToken, makerToken));
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { ChainId } from '@0x/contract-addresses';
|
||||
import { LimitOrderFields } from '@0x/protocol-utils';
|
||||
import { BigNumber } from '@0x/utils';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { SamplerCallResult, SignedNativeOrder } from '../../types';
|
||||
@@ -692,7 +692,7 @@ export class SamplerOperations {
|
||||
function: this._samplerContract.sampleSellsFromUniswapV3,
|
||||
params: [quoter, tokenAddressPath, takerFillAmounts],
|
||||
callback: (callResults: string, fillData: UniswapV3FillData): BigNumber[] => {
|
||||
const [samples, paths] = this._samplerContract.getABIDecodedReturnData<[BigNumber[], string[]]>(
|
||||
const [paths, samples] = this._samplerContract.getABIDecodedReturnData<[string[], BigNumber[]]>(
|
||||
'sampleSellsFromUniswapV3',
|
||||
callResults,
|
||||
);
|
||||
@@ -720,7 +720,7 @@ export class SamplerOperations {
|
||||
function: this._samplerContract.sampleBuysFromUniswapV3,
|
||||
params: [quoter, tokenAddressPath, makerFillAmounts],
|
||||
callback: (callResults: string, fillData: UniswapV3FillData): BigNumber[] => {
|
||||
const [samples, paths] = this._samplerContract.getABIDecodedReturnData<[BigNumber[], string[]]>(
|
||||
const [paths, samples] = this._samplerContract.getABIDecodedReturnData<[string[], BigNumber[]]>(
|
||||
'sampleBuysFromUniswapV3',
|
||||
callResults,
|
||||
);
|
||||
@@ -783,7 +783,10 @@ export class SamplerOperations {
|
||||
};
|
||||
});
|
||||
},
|
||||
() => [],
|
||||
() => {
|
||||
logUtils.warn('SamplerContractOperation: Two hop sampler reverted');
|
||||
return [];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -838,7 +841,10 @@ export class SamplerOperations {
|
||||
};
|
||||
});
|
||||
},
|
||||
() => [],
|
||||
() => {
|
||||
logUtils.warn('SamplerContractOperation: Two hop sampler reverted');
|
||||
return [];
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -9,7 +9,7 @@ import { BigNumber } from '@0x/utils';
|
||||
|
||||
import { NativeOrderWithFillableAmounts, RfqFirmQuoteValidator, RfqRequestOpts } from '../../types';
|
||||
import { QuoteRequestor } from '../../utils/quote_requestor';
|
||||
import { QuoteReport } from '../quote_report_generator';
|
||||
import { PriceComparisonsReport, QuoteReport } from '../quote_report_generator';
|
||||
|
||||
import { CollapsedPath } from './path';
|
||||
import { SourceFilters } from './source_filters';
|
||||
@@ -401,6 +401,11 @@ export interface GetMarketOrdersOpts {
|
||||
* Whether to generate a quote report
|
||||
*/
|
||||
shouldGenerateQuoteReport: boolean;
|
||||
|
||||
/**
|
||||
* Whether to include price comparison data in the quote
|
||||
*/
|
||||
shouldIncludePriceComparisonsReport: boolean;
|
||||
/**
|
||||
* Token addresses with a list of adjacent intermediary tokens to consider
|
||||
* hopping to. E.g DAI->USDC via an adjacent token WETH
|
||||
@@ -435,6 +440,7 @@ export interface OptimizerResult {
|
||||
|
||||
export interface OptimizerResultWithReport extends OptimizerResult {
|
||||
quoteReport?: QuoteReport;
|
||||
priceComparisonsReport?: PriceComparisonsReport;
|
||||
}
|
||||
|
||||
export type MarketDepthSide = Array<Array<DexSample<FillData>>>;
|
||||
|
@@ -60,6 +60,12 @@ export interface QuoteReport {
|
||||
sourcesDelivered: QuoteReportEntry[];
|
||||
}
|
||||
|
||||
export interface PriceComparisonsReport {
|
||||
dexSources: BridgeQuoteReportEntry[];
|
||||
multiHopSources: MultiHopQuoteReportEntry[];
|
||||
nativeSources: Array<NativeLimitOrderQuoteReportEntry | NativeRfqOrderQuoteReportEntry>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report of sources considered while computing the optimized
|
||||
* swap quote, and the sources ultimately included in the computed quote.
|
||||
@@ -72,7 +78,7 @@ export function generateQuoteReport(
|
||||
quoteRequestor?: QuoteRequestor,
|
||||
): QuoteReport {
|
||||
const nativeOrderSourcesConsidered = nativeOrders.map(order =>
|
||||
_nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
|
||||
nativeOrderToReportEntry(order.type, order as any, order.fillableTakerAmount, comparisonPrice, quoteRequestor),
|
||||
);
|
||||
const sourcesConsidered = [...nativeOrderSourcesConsidered.filter(order => order.isRfqt)];
|
||||
|
||||
@@ -87,7 +93,7 @@ export function generateQuoteReport(
|
||||
// map sources delivered
|
||||
sourcesDelivered = liquidityDelivered.map(collapsedFill => {
|
||||
if (_isNativeOrderFromCollapsedFill(collapsedFill)) {
|
||||
return _nativeOrderToReportEntry(
|
||||
return nativeOrderToReportEntry(
|
||||
collapsedFill.type,
|
||||
collapsedFill.fillData,
|
||||
nativeOrderSignaturesToFillableAmounts[_nativeDataToId(collapsedFill.fillData)],
|
||||
@@ -95,13 +101,13 @@ export function generateQuoteReport(
|
||||
quoteRequestor,
|
||||
);
|
||||
} else {
|
||||
return _dexSampleToReportSource(collapsedFill, marketOperation);
|
||||
return dexSampleToReportSource(collapsedFill, marketOperation);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sourcesDelivered = [
|
||||
// tslint:disable-next-line: no-unnecessary-type-assertion
|
||||
_multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
|
||||
multiHopSampleToReportSource(liquidityDelivered as DexSample<MultiHopFillData>, marketOperation),
|
||||
];
|
||||
}
|
||||
return {
|
||||
@@ -115,7 +121,11 @@ function _nativeDataToId(data: { signature: Signature }): string {
|
||||
return `${v}${r}${s}`;
|
||||
}
|
||||
|
||||
function _dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperation): BridgeQuoteReportEntry {
|
||||
/**
|
||||
* Generates a report sample for a DEX source
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
*/
|
||||
export function dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperation): BridgeQuoteReportEntry {
|
||||
const liquiditySource = ds.source;
|
||||
|
||||
if (liquiditySource === ERC20BridgeSource.Native) {
|
||||
@@ -143,7 +153,11 @@ function _dexSampleToReportSource(ds: DexSample, marketOperation: MarketOperatio
|
||||
}
|
||||
}
|
||||
|
||||
function _multiHopSampleToReportSource(
|
||||
/**
|
||||
* Generates a report sample for a MultiHop source
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
*/
|
||||
export function multiHopSampleToReportSource(
|
||||
ds: DexSample<MultiHopFillData>,
|
||||
marketOperation: MarketOperation,
|
||||
): MultiHopQuoteReportEntry {
|
||||
@@ -176,7 +190,11 @@ function _isNativeOrderFromCollapsedFill(cf: CollapsedFill): cf is NativeCollaps
|
||||
return type === FillQuoteTransformerOrderType.Limit || type === FillQuoteTransformerOrderType.Rfq;
|
||||
}
|
||||
|
||||
function _nativeOrderToReportEntry(
|
||||
/**
|
||||
* Generates a report entry for a native order
|
||||
* NOTE: this is used for the QuoteReport and quote price comparison data
|
||||
*/
|
||||
export function nativeOrderToReportEntry(
|
||||
type: FillQuoteTransformerOrderType,
|
||||
fillData: NativeLimitOrderFillData | NativeRfqOrderFillData,
|
||||
fillableAmount: BigNumber,
|
||||
|
@@ -1,6 +1,12 @@
|
||||
import { schemas, SchemaValidator } from '@0x/json-schemas';
|
||||
import { FillQuoteTransformerOrderType, Signature } from '@0x/protocol-utils';
|
||||
import { TakerRequestQueryParams, V4RFQFirmQuote, V4RFQIndicativeQuote, V4SignedRfqOrder } from '@0x/quote-server';
|
||||
import {
|
||||
TakerRequestQueryParamsUnnested,
|
||||
V4RFQFirmQuote,
|
||||
V4RFQIndicativeQuote,
|
||||
V4SignedRfqOrder,
|
||||
} from '@0x/quote-server';
|
||||
import { Fee } from '@0x/quote-server/lib/src/types';
|
||||
import { BigNumber, NULL_ADDRESS } from '@0x/utils';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
|
||||
@@ -11,6 +17,7 @@ import {
|
||||
LogFunction,
|
||||
MarketOperation,
|
||||
RfqMakerAssetOfferings,
|
||||
RfqmRequestOptions,
|
||||
RfqPairType,
|
||||
RfqRequestOpts,
|
||||
SignedNativeOrder,
|
||||
@@ -84,7 +91,8 @@ export class QuoteRequestor {
|
||||
assetFillAmount: BigNumber,
|
||||
comparisonPrice?: BigNumber,
|
||||
isLastLook?: boolean | undefined,
|
||||
): TakerRequestQueryParams {
|
||||
fee?: Fee | undefined,
|
||||
): TakerRequestQueryParamsUnnested {
|
||||
const { buyAmountBaseUnits, sellAmountBaseUnits } =
|
||||
marketOperation === MarketOperation.Buy
|
||||
? {
|
||||
@@ -97,7 +105,7 @@ export class QuoteRequestor {
|
||||
};
|
||||
|
||||
const requestParamsWithBigNumbers: Pick<
|
||||
TakerRequestQueryParams,
|
||||
TakerRequestQueryParamsUnnested,
|
||||
| 'txOrigin'
|
||||
| 'takerAddress'
|
||||
| 'buyTokenAddress'
|
||||
@@ -105,6 +113,9 @@ export class QuoteRequestor {
|
||||
| 'comparisonPrice'
|
||||
| 'isLastLook'
|
||||
| 'protocolVersion'
|
||||
| 'feeAmount'
|
||||
| 'feeToken'
|
||||
| 'feeType'
|
||||
> = {
|
||||
txOrigin,
|
||||
takerAddress,
|
||||
@@ -114,7 +125,13 @@ export class QuoteRequestor {
|
||||
protocolVersion: '4',
|
||||
};
|
||||
if (isLastLook) {
|
||||
if (fee === undefined) {
|
||||
throw new Error(`isLastLook cannot be passed without a fee parameter`);
|
||||
}
|
||||
requestParamsWithBigNumbers.isLastLook = isLastLook.toString();
|
||||
requestParamsWithBigNumbers.feeAmount = fee.amount.toString();
|
||||
requestParamsWithBigNumbers.feeToken = fee.token;
|
||||
requestParamsWithBigNumbers.feeType = fee.type;
|
||||
}
|
||||
|
||||
// convert BigNumbers to strings
|
||||
@@ -181,12 +198,11 @@ export class QuoteRequestor {
|
||||
assetFillAmount: BigNumber,
|
||||
marketOperation: MarketOperation,
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqRequestOpts,
|
||||
options: RfqmRequestOptions,
|
||||
): Promise<SignedNativeOrder[]> {
|
||||
const _opts: RfqRequestOpts = {
|
||||
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||
...options,
|
||||
isLastLook: true,
|
||||
};
|
||||
|
||||
return this._fetchAndValidateFirmQuotesAsync(
|
||||
@@ -230,12 +246,11 @@ export class QuoteRequestor {
|
||||
assetFillAmount: BigNumber,
|
||||
marketOperation: MarketOperation,
|
||||
comparisonPrice: BigNumber | undefined,
|
||||
options: RfqRequestOpts,
|
||||
options: RfqmRequestOptions,
|
||||
): Promise<V4RFQIndicativeQuote[]> {
|
||||
const _opts: RfqRequestOpts = {
|
||||
...constants.DEFAULT_RFQT_REQUEST_OPTS,
|
||||
...options,
|
||||
isLastLook: true,
|
||||
};
|
||||
|
||||
return this._fetchAndValidateIndicativeQuotesAsync(
|
||||
@@ -344,6 +359,7 @@ export class QuoteRequestor {
|
||||
assetFillAmount,
|
||||
comparisonPrice,
|
||||
options.isLastLook,
|
||||
options.fee,
|
||||
);
|
||||
|
||||
const quotePath = (() => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { tokenUtils } from '@0x/dev-utils';
|
||||
import { FillQuoteTransformerOrderType, SignatureType } from '@0x/protocol-utils';
|
||||
import { TakerRequestQueryParams, V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { ETH_TOKEN_ADDRESS, FillQuoteTransformerOrderType, SignatureType } from '@0x/protocol-utils';
|
||||
import { TakerRequestQueryParamsUnnested, V4RFQIndicativeQuote } from '@0x/quote-server';
|
||||
import { StatusCodes } from '@0x/types';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import Axios from 'axios';
|
||||
@@ -75,7 +75,7 @@ describe('QuoteRequestor', async () => {
|
||||
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||
const altMockedRequests: AltMockedRfqQuoteResponse[] = [];
|
||||
|
||||
const expectedParams: TakerRequestQueryParams = {
|
||||
const expectedParams: TakerRequestQueryParamsUnnested = {
|
||||
sellTokenAddress: takerToken,
|
||||
buyTokenAddress: makerToken,
|
||||
sellAmountBaseUnits: '10000',
|
||||
@@ -84,6 +84,9 @@ describe('QuoteRequestor', async () => {
|
||||
txOrigin,
|
||||
isLastLook: 'true', // the major difference between RFQ-T and RFQ-M
|
||||
protocolVersion: '4',
|
||||
feeAmount: '1000000000',
|
||||
feeToken: ETH_TOKEN_ADDRESS,
|
||||
feeType: 'fixed',
|
||||
};
|
||||
const mockedDefaults = {
|
||||
requestApiKey: apiKey,
|
||||
@@ -242,6 +245,12 @@ describe('QuoteRequestor', async () => {
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
altRfqAssetOfferings,
|
||||
isLastLook: true,
|
||||
fee: {
|
||||
amount: new BigNumber('1000000000'),
|
||||
token: ETH_TOKEN_ADDRESS,
|
||||
type: 'fixed',
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(resp).to.deep.eq([
|
||||
@@ -265,7 +274,7 @@ describe('QuoteRequestor', async () => {
|
||||
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||
const altMockedRequests: AltMockedRfqQuoteResponse[] = [];
|
||||
|
||||
const expectedParams: TakerRequestQueryParams = {
|
||||
const expectedParams: TakerRequestQueryParamsUnnested = {
|
||||
sellTokenAddress: takerToken,
|
||||
buyTokenAddress: makerToken,
|
||||
sellAmountBaseUnits: '10000',
|
||||
@@ -451,7 +460,7 @@ describe('QuoteRequestor', async () => {
|
||||
// Set up RFQ responses
|
||||
// tslint:disable-next-line:array-type
|
||||
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||
const expectedParams: TakerRequestQueryParams = {
|
||||
const expectedParams: TakerRequestQueryParamsUnnested = {
|
||||
sellTokenAddress: takerToken,
|
||||
buyTokenAddress: makerToken,
|
||||
sellAmountBaseUnits: '10000',
|
||||
@@ -460,6 +469,9 @@ describe('QuoteRequestor', async () => {
|
||||
txOrigin: takerAddress,
|
||||
isLastLook: 'true', // the major difference between RFQ-T and RFQ-M
|
||||
protocolVersion: '4',
|
||||
feeAmount: '1000000000',
|
||||
feeToken: ETH_TOKEN_ADDRESS,
|
||||
feeType: 'fixed',
|
||||
};
|
||||
const mockedDefaults = {
|
||||
requestApiKey: apiKey,
|
||||
@@ -543,6 +555,12 @@ describe('QuoteRequestor', async () => {
|
||||
takerAddress,
|
||||
txOrigin: takerAddress,
|
||||
intentOnFilling: true,
|
||||
isLastLook: true,
|
||||
fee: {
|
||||
type: 'fixed',
|
||||
token: ETH_TOKEN_ADDRESS,
|
||||
amount: new BigNumber('1000000000'),
|
||||
},
|
||||
},
|
||||
);
|
||||
expect(resp.sort()).to.eql([successfulQuote1, successfulQuote1].sort());
|
||||
@@ -571,7 +589,7 @@ describe('QuoteRequestor', async () => {
|
||||
// Set up RFQT responses
|
||||
// tslint:disable-next-line:array-type
|
||||
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||
const expectedParams: TakerRequestQueryParams = {
|
||||
const expectedParams: TakerRequestQueryParamsUnnested = {
|
||||
sellTokenAddress: takerToken,
|
||||
buyTokenAddress: makerToken,
|
||||
sellAmountBaseUnits: '10000',
|
||||
@@ -678,7 +696,7 @@ describe('QuoteRequestor', async () => {
|
||||
// Set up RFQT responses
|
||||
// tslint:disable-next-line:array-type
|
||||
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||
const expectedParams: TakerRequestQueryParams = {
|
||||
const expectedParams: TakerRequestQueryParamsUnnested = {
|
||||
sellTokenAddress: takerToken,
|
||||
buyTokenAddress: makerToken,
|
||||
sellAmountBaseUnits: '10000',
|
||||
@@ -763,7 +781,7 @@ describe('QuoteRequestor', async () => {
|
||||
// Set up RFQT responses
|
||||
// tslint:disable-next-line:array-type
|
||||
const mockedRequests: MockedRfqQuoteResponse[] = [];
|
||||
const expectedParams: TakerRequestQueryParams = {
|
||||
const expectedParams: TakerRequestQueryParamsUnnested = {
|
||||
sellTokenAddress: takerToken,
|
||||
buyTokenAddress: makerToken,
|
||||
buyAmountBaseUnits: '10000',
|
||||
|
13
yarn.lock
13
yarn.lock
@@ -783,7 +783,7 @@
|
||||
lodash "^4.17.11"
|
||||
web3-provider-engine "14.0.6"
|
||||
|
||||
"@0x/json-schemas@^5.0.1", "@0x/json-schemas@^5.0.7", "@0x/json-schemas@^5.3.3":
|
||||
"@0x/json-schemas@^5.0.1", "@0x/json-schemas@^5.3.3":
|
||||
version "5.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-5.3.3.tgz#4b9de100385ca23b0cd58a454165df2e9758e453"
|
||||
dependencies:
|
||||
@@ -801,7 +801,7 @@
|
||||
jsonschema "^1.2.0"
|
||||
lodash.values "^4.3.0"
|
||||
|
||||
"@0x/json-schemas@^6.1.3":
|
||||
"@0x/json-schemas@^6.0.1", "@0x/json-schemas@^6.1.3":
|
||||
version "6.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@0x/json-schemas/-/json-schemas-6.1.3.tgz#da71ed2e50ae6813a6d4d0fe5f8ad69b8e6a7435"
|
||||
dependencies:
|
||||
@@ -850,11 +850,12 @@
|
||||
typedoc "~0.16.11"
|
||||
yargs "^10.0.3"
|
||||
|
||||
"@0x/quote-server@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@0x/quote-server/-/quote-server-5.0.0.tgz#15554099bdfdf71e2910430860257d622f24f703"
|
||||
"@0x/quote-server@^6.0.2":
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@0x/quote-server/-/quote-server-6.0.2.tgz#cb99e00c737e0f97a2a32bc7e7be6db65243c3af"
|
||||
integrity sha512-SS5LfAgKSRjEszWVZl5UtRDBkrsqAvYn/lPB4hxtKky8XitClUYFQ2pSnrFuyQSVft3tFxH4p7eC65YQN5wkcA==
|
||||
dependencies:
|
||||
"@0x/json-schemas" "^5.0.7"
|
||||
"@0x/json-schemas" "^6.0.1"
|
||||
"@0x/order-utils" "^10.2.4"
|
||||
"@0x/protocol-utils" "^1.0.1"
|
||||
"@0x/utils" "^5.4.1"
|
||||
|
Reference in New Issue
Block a user