Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 19 additions & 8 deletions src/endpoints/mex/graphql/settings.query.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
}
}
}
`;
4 changes: 3 additions & 1 deletion src/endpoints/mex/mex.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
58 changes: 41 additions & 17 deletions src/endpoints/mex/mex.settings.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -88,17 +87,49 @@ export class MexSettingsService {
}

public async getSettingsRaw(): Promise<MexSettings | null> {
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);
Expand All @@ -109,12 +140,5 @@ export class MexSettingsService {
return this.wegldId;
}

private async getPairLimitCount(): Promise<number> {
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.
}
39 changes: 39 additions & 0 deletions src/endpoints/mex/mex.warmer.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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,
]);
}
}

Expand All @@ -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);
}
}
Loading