@uniswap/smart-order-router
Version:
Uniswap Smart Order Router
288 lines • 25 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubgraphProvider = exports.BASE_V4_PAGE_SIZE = exports.PAGE_SIZE = void 0;
const router_sdk_1 = require("@uniswap/router-sdk");
const sdk_core_1 = require("@uniswap/sdk-core");
const async_retry_1 = __importDefault(require("async-retry"));
const await_timeout_1 = __importDefault(require("await-timeout"));
const graphql_request_1 = require("graphql-request");
const lodash_1 = __importDefault(require("lodash"));
const util_1 = require("../util");
exports.PAGE_SIZE = 1000; // 1k is max possible query size from subgraph.
exports.BASE_V4_PAGE_SIZE = 3500; // TheGraph v4 base max pagesize is 3600.
class SubgraphProvider {
constructor(protocol, chainId, retries = 2, timeout = 30000, rollback = true, trackedEthThreshold = 0.01, trackedZoraEthThreshold = 0.001, zoraHooks,
// @ts-expect-error - kept for backward compatibility
untrackedUsdThreshold = Number.MAX_VALUE, subgraphUrl, bearerToken) {
this.protocol = protocol;
this.chainId = chainId;
this.retries = retries;
this.timeout = timeout;
this.rollback = rollback;
this.trackedEthThreshold = trackedEthThreshold;
this.trackedZoraEthThreshold = trackedZoraEthThreshold;
this.zoraHooks = zoraHooks;
this.untrackedUsdThreshold = untrackedUsdThreshold;
this.subgraphUrl = subgraphUrl;
this.bearerToken = bearerToken;
this.protocol = protocol;
if (!this.subgraphUrl) {
throw new Error(`No subgraph url for chain id: ${this.chainId}`);
}
util_1.log.info('bearerToken is', this.bearerToken);
if (this.bearerToken) {
this.client = new graphql_request_1.GraphQLClient(this.subgraphUrl, {
headers: {
authorization: `Bearer ${this.bearerToken}`,
},
});
}
else {
this.client = new graphql_request_1.GraphQLClient(this.subgraphUrl);
}
if (protocol === router_sdk_1.Protocol.V4 && this.zoraHooks.size === 0) {
throw new Error('Zora hooks param is mandatory for V4');
}
}
async getPools(_currencyIn, _currencyOut, providerConfig) {
const beforeAll = Date.now();
let blockNumber = (providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.blockNumber)
? await providerConfig.blockNumber
: undefined;
const pageSizeToUse = this.protocol === router_sdk_1.Protocol.V4 && this.chainId == sdk_core_1.ChainId.BASE
? exports.BASE_V4_PAGE_SIZE
: exports.PAGE_SIZE;
util_1.log.info(`Getting ${this.protocol} pools from the subgraph with page size ${pageSizeToUse}${(providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.blockNumber)
? ` as of block ${providerConfig === null || providerConfig === void 0 ? void 0 : providerConfig.blockNumber}`
: ''}.`);
// Define separate queries for each filtering condition
const queries = [
// 1. Pools with high tracked ETH (for both V3 and V4)
{
name: 'High tracked ETH pools',
query: (0, graphql_request_1.gql) `
query getHighTrackedETHPools($pageSize: Int!, $id: String, $threshold: String!) {
pools(
first: $pageSize
${blockNumber ? `block: { number: ${blockNumber} }` : ``}
where: {
id_gt: $id,
totalValueLockedETH_gt: $threshold
}
) {
${this.getPoolFields()}
}
}
`,
variables: { threshold: this.trackedEthThreshold.toString() },
},
// 2. V4: Non-Zora pools with liquidity > 0
...(this.protocol === router_sdk_1.Protocol.V4
? [
{
name: 'V4 non-Zora high liquidity pools',
query: (0, graphql_request_1.gql) `
query getV4NonZoraHighLiquidityPools($pageSize: Int!, $id: String, $zoraHooks: [String!]!) {
pools(
first: $pageSize
${blockNumber ? `block: { number: ${blockNumber} }` : ``}
where: {
id_gt: $id,
liquidity_gt: "0",
hooks_not_in: $zoraHooks
}
) {
${this.getPoolFields()}
}
}
`,
variables: { zoraHooks: Array.from(this.zoraHooks) },
},
// 3. V4: Zora pools with liquidity > 0 AND TVL > trackedZoraEthThreshold
{
name: 'V4 Zora high liquidity pools',
query: (0, graphql_request_1.gql) `
query getV4ZoraHighLiquidityPools($pageSize: Int!, $id: String, $zoraHooks: [String!]!, $zoraThreshold: String!) {
pools(
first: $pageSize
${blockNumber ? `block: { number: ${blockNumber} }` : ``}
where: {
id_gt: $id,
liquidity_gt: "0",
hooks_in: $zoraHooks,
totalValueLockedETH_gt: $zoraThreshold
}
) {
${this.getPoolFields()}
}
}
`,
variables: {
zoraHooks: Array.from(this.zoraHooks),
zoraThreshold: this.trackedZoraEthThreshold.toString(),
},
},
]
: []),
// 4. V3: Pools with liquidity > 0 AND totalValueLockedETH = 0 (special V3 condition)
...(this.protocol === router_sdk_1.Protocol.V3
? [
{
name: 'V3 zero ETH pools',
query: (0, graphql_request_1.gql) `
query getV3ZeroETHPools($pageSize: Int!, $id: String) {
pools(
first: $pageSize
${blockNumber ? `block: { number: ${blockNumber} }` : ``}
where: {
id_gt: $id,
liquidity_gt: "0",
totalValueLockedETH: "0"
}
) {
${this.getPoolFields()}
}
}
`,
variables: {},
},
]
: []),
];
let allPools = [];
let retries = 0;
await (0, async_retry_1.default)(async () => {
const timeout = new await_timeout_1.default();
const fetchPoolsForQuery = async (queryConfig) => {
let lastId = '';
let pools = [];
let poolsPage = [];
let totalPages = 0;
do {
totalPages += 1;
const start = Date.now();
util_1.log.info(`Starting fetching for ${queryConfig.name} page ${totalPages} with page size ${pageSizeToUse}`);
const poolsResult = await this.client.request(queryConfig.query, Object.assign({ pageSize: pageSizeToUse, id: lastId }, queryConfig.variables));
poolsPage = poolsResult.pools;
pools = pools.concat(poolsPage);
if (pools.length > 0) {
lastId = pools[pools.length - 1].id;
}
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.${queryConfig.name
.replace(/\s+/g, '_')
.toLowerCase()}.paginate.pageSize`, poolsPage.length);
util_1.log.info(`Fetched ${poolsPage.length} pools for ${queryConfig.name} in ${Date.now() - start}ms`);
} while (poolsPage.length > 0);
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.${queryConfig.name
.replace(/\s+/g, '_')
.toLowerCase()}.paginate`, totalPages);
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.${queryConfig.name
.replace(/\s+/g, '_')
.toLowerCase()}.pools.length`, pools.length);
return pools;
};
try {
// Fetch pools for each query in parallel
const poolPromises = queries.map((queryConfig) => fetchPoolsForQuery(queryConfig));
const allPoolsArrays = await Promise.all(poolPromises);
// Merge all results and deduplicate by pool ID
const poolMap = new Map();
allPoolsArrays.forEach((pools) => {
pools.forEach((pool) => {
poolMap.set(pool.id, pool);
});
});
allPools = Array.from(poolMap.values());
const getPoolsPromise = Promise.resolve(allPools);
const timerPromise = timeout.set(this.timeout).then(() => {
throw new Error(`Timed out getting pools from subgraph: ${this.timeout}`);
});
allPools = await Promise.race([getPoolsPromise, timerPromise]);
return;
}
catch (err) {
util_1.log.error({ err }, `Error fetching ${this.protocol} Subgraph Pools.`);
throw err;
}
finally {
timeout.clear();
}
}, {
retries: this.retries,
onRetry: (err, retry) => {
retries += 1;
if (this.rollback &&
blockNumber &&
lodash_1.default.includes(err.message, 'indexed up to')) {
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.indexError`, 1);
blockNumber = blockNumber - 10;
util_1.log.info(`Detected subgraph indexing error. Rolled back block number to: ${blockNumber}`);
}
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.timeout`, 1);
allPools = [];
util_1.log.info({ err }, `Failed to get pools from subgraph. Retry attempt: ${retry}`);
},
});
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.retries`, retries);
const beforeFilter = Date.now();
let poolsSanitized = [];
if (this.protocol === router_sdk_1.Protocol.V3) {
// Special treatment for all V3 pools in order to reduce latency due to thousands of pools with very low TVL locked
// - Include "parseFloat(pool.totalValueLockedETH) === 0" as in certain occasions we have no way of calculating derivedETH so this is 0
poolsSanitized = allPools
.filter((pool) => (parseInt(pool.liquidity) > 0 &&
parseFloat(pool.totalValueLockedETH) === 0) ||
parseFloat(pool.totalValueLockedETH) > this.trackedEthThreshold)
.map((pool) => {
return this.mapSubgraphPool(pool);
});
}
else if (this.protocol === router_sdk_1.Protocol.V4) {
// For V4, apply additional filtering as a safety measure even though queries are optimized
poolsSanitized = allPools
.filter((pool) => {
const liquidity = parseInt(pool.liquidity);
const tvl = parseFloat(pool.totalValueLockedETH);
const hooks = pool.hooks;
const isZora = this.zoraHooks.has(hooks);
if (isZora) {
return tvl > this.trackedZoraEthThreshold;
}
return liquidity > 0 || tvl > this.trackedEthThreshold;
})
.map((pool) => {
return this.mapSubgraphPool(pool);
});
}
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.filter.latency`, Date.now() - beforeFilter);
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.filter.length`, poolsSanitized.length);
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.filter.percent`, (poolsSanitized.length / allPools.length) * 100);
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools`, 1);
util_1.metric.putMetric(`${this.protocol}SubgraphProvider.chain_${this.chainId}.getPools.latency`, Date.now() - beforeAll);
util_1.log.info(`Got ${allPools.length} ${this.protocol} pools from the subgraph (after deduplication). ${poolsSanitized.length} after filtering`);
return poolsSanitized;
}
// Helper method to get the pool fields for GraphQL queries
getPoolFields() {
return `
id
token0 {
symbol
id
}
token1 {
symbol
id
}
feeTier
liquidity
totalValueLockedUSD
totalValueLockedETH
`;
}
}
exports.SubgraphProvider = SubgraphProvider;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3ViZ3JhcGgtcHJvdmlkZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcHJvdmlkZXJzL3N1YmdyYXBoLXByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7OztBQUFBLG9EQUErQztBQUMvQyxnREFBNkQ7QUFDN0QsOERBQWdDO0FBQ2hDLGtFQUFvQztBQUNwQyxxREFBcUQ7QUFDckQsb0RBQXVCO0FBR3ZCLGtDQUFzQztBQWF6QixRQUFBLFNBQVMsR0FBRyxJQUFJLENBQUMsQ0FBQywrQ0FBK0M7QUFDakUsUUFBQSxpQkFBaUIsR0FBRyxJQUFJLENBQUMsQ0FBQyx5Q0FBeUM7QUFpQ2hGLE1BQXNCLGdCQUFnQjtJQU1wQyxZQUNVLFFBQWtCLEVBQ2xCLE9BQWdCLEVBQ2hCLFVBQVUsQ0FBQyxFQUNYLFVBQVUsS0FBSyxFQUNmLFdBQVcsSUFBSSxFQUNmLHNCQUFzQixJQUFJLEVBQzFCLDBCQUEwQixLQUFLLEVBQy9CLFNBQXNCO0lBQzlCLHFEQUFxRDtJQUM3Qyx3QkFBd0IsTUFBTSxDQUFDLFNBQVMsRUFDeEMsV0FBb0IsRUFDcEIsV0FBb0I7UUFYcEIsYUFBUSxHQUFSLFFBQVEsQ0FBVTtRQUNsQixZQUFPLEdBQVAsT0FBTyxDQUFTO1FBQ2hCLFlBQU8sR0FBUCxPQUFPLENBQUk7UUFDWCxZQUFPLEdBQVAsT0FBTyxDQUFRO1FBQ2YsYUFBUSxHQUFSLFFBQVEsQ0FBTztRQUNmLHdCQUFtQixHQUFuQixtQkFBbUIsQ0FBTztRQUMxQiw0QkFBdUIsR0FBdkIsdUJBQXVCLENBQVE7UUFDL0IsY0FBUyxHQUFULFNBQVMsQ0FBYTtRQUV0QiwwQkFBcUIsR0FBckIscUJBQXFCLENBQW1CO1FBQ3hDLGdCQUFXLEdBQVgsV0FBVyxDQUFTO1FBQ3BCLGdCQUFXLEdBQVgsV0FBVyxDQUFTO1FBRTVCLElBQUksQ0FBQyxRQUFRLEdBQUcsUUFBUSxDQUFDO1FBQ3pCLElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxF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