From 856f9296cba7a6ee3e7601d190c301e3f105ae79 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 2 Feb 2026 16:21:41 +0200 Subject: [PATCH 1/2] paginated filtered pairs --- src/endpoints/mex/graphql/settings.query.ts | 27 +++++++--- src/endpoints/mex/mex.settings.service.ts | 58 +++++++++++++++------ 2 files changed, 60 insertions(+), 25 deletions(-) diff --git a/src/endpoints/mex/graphql/settings.query.ts b/src/endpoints/mex/graphql/settings.query.ts index 66401c089..935639f08 100644 --- a/src/endpoints/mex/graphql/settings.query.ts +++ b/src/endpoints/mex/graphql/settings.query.ts @@ -1,14 +1,8 @@ import { gql } from "graphql-request"; -export const settingsQuery = (pairLimitCount: number) => gql` +// Base settings query without pairs to avoid heavy responses per page. +export const settingsBaseQuery = gql` query { - filteredPairs(pagination: {first: ${pairLimitCount}}, filters: {state: ["Active"]}) { - edges { - node { - address - } - } - } proxy { address lockedAssetTokens { @@ -58,3 +52,20 @@ query { } } `; + +// Minimal paginated pairs query for addresses only. +export const paginatedPairsAddressesQuery = gql` + query paginatedPairs($pagination: ConnectionArgs!, $filters: PairsFilter!) { + filteredPairs(pagination: $pagination, filters: $filters) { + edges { + cursor + node { + address + } + } + pageInfo { + hasNextPage + } + } + } +`; diff --git a/src/endpoints/mex/mex.settings.service.ts b/src/endpoints/mex/mex.settings.service.ts index faf14bfa4..527ba994f 100644 --- a/src/endpoints/mex/mex.settings.service.ts +++ b/src/endpoints/mex/mex.settings.service.ts @@ -7,8 +7,7 @@ import { TransactionMetadata } from "../transactions/transaction-action/entities import { TransactionMetadataTransfer } from "../transactions/transaction-action/entities/transaction.metadata.transfer"; import { MexSettings } from "./entities/mex.settings"; import { ApiConfigService } from "src/common/api-config/api.config.service"; -import { settingsQuery } from "./graphql/settings.query"; -import { pairCountQuery } from "./graphql/pairs.count.query"; +import { paginatedPairsAddressesQuery, settingsBaseQuery } from "./graphql/settings.query"; @Injectable() export class MexSettingsService { @@ -88,17 +87,49 @@ export class MexSettingsService { } public async getSettingsRaw(): Promise { - const pairLimitCount = await this.getPairLimitCount(); - const response = await this.graphQlService.getExchangeServiceData(settingsQuery(pairLimitCount)); - if (!response) { + // Fetch base settings (non-paginated parts) + const base = await this.graphQlService.getExchangeServiceData(settingsBaseQuery); + if (!base) { return null; } + // Page through pairs to avoid one heavy query + const pageSize = 50; + let cursor: string | undefined = undefined; + let hasNext = true; + const pairs: { address: string }[] = []; + + while (hasNext) { + const variables = { + pagination: cursor ? { first: pageSize, after: cursor } : { first: pageSize }, + filters: { state: ["Active"] }, + }; + + const page = await this.graphQlService.getExchangeServiceData( + paginatedPairsAddressesQuery, + variables, + ); + + if (!page || !page.filteredPairs) { + break; + } + + const edges: Array<{ cursor: string; node: { address: string } }> = page.filteredPairs.edges || []; + for (const edge of edges) { + pairs.push({ address: edge.node.address }); + } + + hasNext = page.filteredPairs.pageInfo?.hasNextPage === true; + cursor = edges.length > 0 ? edges[edges.length - 1].cursor : cursor; + + if (!hasNext) { + break; + } + } + const transformedResponse = { - ...response, - pairs: response.filteredPairs.edges.map((edge: { node: { address: string } }) => ({ - address: edge.node.address, - })), + ...base, + pairs, }; const settings = MexSettings.fromQueryResponse(transformedResponse); @@ -109,12 +140,5 @@ export class MexSettingsService { return this.wegldId; } - private async getPairLimitCount(): Promise { - const response = await this.graphQlService.getExchangeServiceData(pairCountQuery); - if (!response) { - return 500; - } - - return response.factory.pairCount; - } + // Pair count no longer needed; pagination uses fixed page size. } From 8e46377ad2f5b068af89d713109887f0ea396a5c Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Tue, 3 Feb 2026 13:59:17 +0200 Subject: [PATCH 2/2] only enable mex crons if cachewarmer is enabled --- src/endpoints/mex/mex.module.ts | 4 ++- src/endpoints/mex/mex.warmer.service.ts | 39 +++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/endpoints/mex/mex.module.ts b/src/endpoints/mex/mex.module.ts index cce2e0a0a..74d8b1e86 100644 --- a/src/endpoints/mex/mex.module.ts +++ b/src/endpoints/mex/mex.module.ts @@ -24,7 +24,9 @@ export class MexModule { ]; const isExchangeEnabled = configuration().features?.exchange?.enabled ?? false; - if (isExchangeEnabled) { + const isCacheWarmerEnabled = configuration().cron?.cacheWarmer ?? false; + // Enable MEX cron only when both Exchange and CacheWarmer flags are enabled + if (isExchangeEnabled && isCacheWarmerEnabled) { providers.push(MexWarmerService); } diff --git a/src/endpoints/mex/mex.warmer.service.ts b/src/endpoints/mex/mex.warmer.service.ts index b32a00d95..02ffb6325 100644 --- a/src/endpoints/mex/mex.warmer.service.ts +++ b/src/endpoints/mex/mex.warmer.service.ts @@ -9,6 +9,7 @@ import { MexTokenService } from "src/endpoints/mex/mex.token.service"; import { MexFarmService } from "src/endpoints/mex/mex.farm.service"; import { Lock, Locker } from "@multiversx/sdk-nestjs-common"; import { CacheService } from "@multiversx/sdk-nestjs-cache"; +import { ApiConfigService } from "src/common/api-config/api.config.service"; @Injectable() export class MexWarmerService { @@ -20,37 +21,68 @@ export class MexWarmerService { private readonly mexTokensService: MexTokenService, private readonly mexSettingsService: MexSettingsService, private readonly mexFarmsService: MexFarmService, + private readonly apiConfigService: ApiConfigService, ) { } @Cron(CronExpression.EVERY_MINUTE) async handleMexInvalidations() { + // Run only when both Exchange and CacheWarmer are enabled + if (!this.apiConfigService.isExchangeEnabled() || !this.apiConfigService.getIsCacheWarmerCronActive()) { + return; + } await Locker.lock('Refreshing mex pairs', async () => { await this.mexPairsService.refreshMexPairs(); + this.emitDeleteLocal([ + CacheInfo.MexPairs.key, + CacheInfo.MexPairsWithFarms.key, + ]); }, true); await Locker.lock('Refreshing mex economics', async () => { await this.mexEconomicsService.refreshMexEconomics(); + this.emitDeleteLocal([ + CacheInfo.MexEconomics.key, + ]); }, true); await Locker.lock('Refreshing mex tokens', async () => { await this.mexTokensService.refreshMexTokens(); + this.emitDeleteLocal([ + CacheInfo.MexTokens.key, + CacheInfo.MexTokenTypes.key, + CacheInfo.MexTokensIndexed.key, + CacheInfo.MexPrices.key, + ]); }, true); await Locker.lock('Refreshing mex farms', async () => { await this.mexFarmsService.refreshMexFarms(); + this.emitDeleteLocal([ + CacheInfo.MexFarms.key, + ]); }, true); await Locker.lock('Refreshing mex settings', async () => { await this.mexSettingsService.refreshSettings(); + this.emitDeleteLocal([ + CacheInfo.MexSettings.key, + CacheInfo.MexContracts.key, + ]); }, true); } @Cron(CronExpression.EVERY_10_MINUTES) @Lock({ name: 'Mex settings invalidations' }) async handleMexSettings() { + if (!this.apiConfigService.isExchangeEnabled() || !this.apiConfigService.getIsCacheWarmerCronActive()) { + return; + } const settings = await this.mexSettingsService.getSettingsRaw(); if (settings) { await this.invalidateKey(CacheInfo.MexSettings.key, settings, CacheInfo.MexSettings.ttl); + this.emitDeleteLocal([ + CacheInfo.MexSettings.key, + ]); } } @@ -62,4 +94,11 @@ export class MexWarmerService { private refreshCacheKey(key: string, ttl: number) { this.clientProxy.emit('refreshCacheKey', { key, ttl }); } + + private emitDeleteLocal(keys: string[]) { + if (!keys || keys.length === 0) { + return; + } + this.clientProxy.emit('deleteCacheKeys', keys); + } }