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
2 changes: 1 addition & 1 deletion proto/dex_state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ message GetFilteredStakingFarmsRequest {
}

message UpdateStakingFarmsRequest {
repeated StakingProxy stakingFarms = 1;
repeated StakingFarm stakingFarms = 1;
google.protobuf.FieldMask updateMask = 2;
}

Expand Down
53 changes: 47 additions & 6 deletions src/modules/auto-router/services/auto-router.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
XoxnoQuoteModel,
XoxnoPathModel,
} from '../models/xoxno-aggregator.model';
import { TokenService } from 'src/modules/tokens/services/token.service';

@Injectable()
export class AutoRouterService {
Expand All @@ -61,6 +62,7 @@ export class AutoRouterService {
private readonly composeTasksAbi: ComposableTasksAbiService,
private readonly pairsState: PairsStateService,
private readonly tokensState: TokensStateService,
private readonly tokenService: TokenService,
private readonly xoxnoAggregatorService: XoxnoAggregatorService,
private readonly apiConfigService: ApiConfigService,
@Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
Expand Down Expand Up @@ -1052,7 +1054,13 @@ export class AutoRouterService {
quote.amountOut,
);
const feeTokenID = await this.toWrappedIfEGLD([quote.feeToken]);
const feeToken = await this.tokensState.getTokens([feeTokenID[0]]);
const feeTokenFromState = await this.tokensState.getTokens([
feeTokenID[0],
]);
const feeToken =
feeTokenFromState.length > 0 && feeTokenFromState[0]
? feeTokenFromState
: await this.tokenService.getAllTokensMetadata([feeTokenID[0]]);

return new SmartSwapModel({
amountOut: quote.amountOut,
Expand Down Expand Up @@ -1088,14 +1096,47 @@ export class AutoRouterService {
paths.flatMap((p) => p.swaps.flatMap((s) => [s.from, s.to])),
),
];
const esdtTokenIDs = await this.toWrappedIfEGLD(tokenIDs);

// Batch fetch all token metadata (cached)
const tokensMetadata = await this.tokensState.getTokens(esdtTokenIDs);
const tokenMap = new Map(
tokenIDs.map((id, i) => [id, tokensMetadata[i]]),
const wrappedEgldTokenID = await this.wrapAbi.wrappedEgldTokenID();
const esdtTokenIDs = tokenIDs.map((t) =>
t === mxConfig.EGLDIdentifier ? wrappedEgldTokenID : t,
);

const allStateTokenIdentifiers = new Set(
(await this.tokensState.getAllTokens(['identifier'])).map(
(t) => t.identifier,
),
);
const knownTokenIDs = esdtTokenIDs.filter((id) =>
allStateTokenIdentifiers.has(id),
);
const unknownTokenIDs = esdtTokenIDs.filter(
(id) => !allStateTokenIdentifiers.has(id),
);

const tokenMap = new Map<string, EsdtToken>();
if (knownTokenIDs.length > 0) {
const stateTokens = await this.tokensState.getTokens(knownTokenIDs);
stateTokens.forEach((token) => {
if (token) tokenMap.set(token.identifier, token);
});
}

if (unknownTokenIDs.length > 0) {
const unknownTokensMetadata =
await this.tokenService.getAllTokensMetadata(unknownTokenIDs);
unknownTokensMetadata
.filter((token) => token != null)
.forEach((token) => tokenMap.set(token.identifier, token));
}

if (tokenIDs.includes(mxConfig.EGLDIdentifier)) {
tokenMap.set(
mxConfig.EGLDIdentifier,
tokenMap.get(wrappedEgldTokenID),
);
}

return paths.map((path) => {
const swaps = path.swaps;

Expand Down
5 changes: 5 additions & 0 deletions src/modules/state/entities/state.tasks.entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export enum StateTasks {
REFRESH_STAKING_FARM = 'refreshStakingFarm',
REFRESH_STAKING_FARMS = 'refreshAllStakingFarms',
REFRESH_FEES_COLLECTOR = 'refreshFeesCollector',
REFRESH_TOKEN = 'refreshToken',
REFRESH_TOKENS = 'refreshAllTokens',
}

export const StateTaskPriority: Record<StateTasks, number> = {
Expand All @@ -35,8 +37,10 @@ export const StateTaskPriority: Record<StateTasks, number> = {
refreshAllFarms: 13,
refreshAllStakingFarms: 13,
refreshFeesCollector: 13,
refreshAllTokens: 13,
refreshFarm: 15,
refreshStakingFarm: 15,
refreshToken: 15,
broadcastPriceUpdates: 30,
indexLpToken: 100,
updateSnapshot: 200,
Expand All @@ -48,6 +52,7 @@ export const StateTasksWithArguments = [
StateTasks.INDEX_PAIR,
StateTasks.REFRESH_FARM,
StateTasks.REFRESH_STAKING_FARM,
StateTasks.REFRESH_TOKEN,
];

export const PENDING_PRICE_UPDATES_KEY = 'dexService.pendingPriceUpdates';
Expand Down
4 changes: 4 additions & 0 deletions src/modules/state/mocks/tokens.state.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ export class TokensStateServiceMock {
return tokenIDs.map((tokenID) => Tokens(tokenID));
}

async getAllTokens(fields: (keyof EsdtToken)[] = []): Promise<EsdtToken[]> {
return this.getTokens(MockedTokens, fields);
}

async getFilteredTokens(
offset: number,
limit: number,
Expand Down
6 changes: 6 additions & 0 deletions src/modules/state/services/state.sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,12 @@ export class StateSyncService {
return this.pairsSync.getPairReservesAndState(pair);
}

async refreshTokenMetadata(
tokenID: string,
): Promise<Partial<EsdtToken> | undefined> {
return this.tokensSync.refreshTokenMetadata(tokenID);
}

async getPairAnalytics(pair: PairModel): Promise<Partial<PairModel>> {
return this.analyticsSync.getPairAnalytics(pair);
}
Expand Down
56 changes: 56 additions & 0 deletions src/modules/state/services/state.tasks.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const MAX_TASK_RETRIES = 5;
const TASK_RETRY_TTL_SECONDS = 86400;
const INDEX_LP_MAX_ATTEMPTS = 60;
const PAIR_REFRESH_CONCURRENCY = 50;
const TOKEN_REFRESH_CONCURRENCY = 50;

function getTaskRetryKey(task: TaskDto): string {
const base = `${TASK_RETRY_COUNT_KEY_PREFIX}:${task.name}`;
Expand Down Expand Up @@ -167,6 +168,12 @@ export class StateTasksService {
case StateTasks.REFRESH_FEES_COLLECTOR:
await this.refreshFeesCollector();
break;
case StateTasks.REFRESH_TOKEN:
await this.refreshToken(task.args[0]);
break;
case StateTasks.REFRESH_TOKENS:
await this.refreshTokens();
break;
default:
break;
}
Expand Down Expand Up @@ -549,4 +556,53 @@ export class StateTasksService {

await this.feesCollectorState.updateFeesCollector(feesCollectorUpdates);
}

async refreshToken(identifier: string): Promise<void> {
const updates = await this.syncService.refreshTokenMetadata(identifier);

if (!updates) {
throw new Error(`Token ${identifier} not found`);
}

const tokenUpdates = new Map<string, Partial<EsdtToken>>();
tokenUpdates.set(identifier, updates);

const updateResult = await this.tokensState.updateTokens(tokenUpdates);

this.logger.debug(`Refresh token ${identifier} task completed`, {
context: StateTasksService.name,
updateResult,
});
}

async refreshTokens(): Promise<void> {
const tokens = await this.tokensState.getAllTokens(['identifier']);

const tokenUpdates = new Map<string, Partial<EsdtToken>>();

const profiler = new PerformanceProfiler();

for (let i = 0; i < tokens.length; i += TOKEN_REFRESH_CONCURRENCY) {
const chunk = tokens.slice(i, i + TOKEN_REFRESH_CONCURRENCY);
const results = await Promise.all(
chunk.map((token) =>
this.syncService.refreshTokenMetadata(token.identifier),
),
);
results.forEach((updates, idx) => {
if (updates) {
tokenUpdates.set(chunk[idx].identifier, updates);
}
});
}

profiler.stop('Finished syncing tokens metadata in', true);

const updateResult = await this.tokensState.updateTokens(tokenUpdates);

this.logger.debug(`Refresh all tokens metadata task completed`, {
context: StateTasksService.name,
updateResult,
});
}
}
20 changes: 20 additions & 0 deletions src/modules/state/services/sync/tokens.sync.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ export class TokensSyncService {
return token as EsdtToken;
}

async refreshTokenMetadata(
tokenID: string,
): Promise<Partial<EsdtToken> | undefined> {
const tokenMetadata = await this.apiService.getToken(tokenID);

if (tokenMetadata === undefined) {
return undefined;
}

const token = this.getTokenFromMetadata(tokenMetadata);

if (token.assets) {
token.assets.lockedAccounts = token.assets.lockedAccounts
? Object.keys(token.assets.lockedAccounts)
: [];
}

return token;
}

private getTokenFromMetadata(tokenMetadata: EsdtToken): Partial<EsdtToken> {
const token: Partial<EsdtToken> = {
identifier: tokenMetadata.identifier,
Expand Down
18 changes: 9 additions & 9 deletions src/modules/tokens/models/esdtToken.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,21 @@ export class EsdtToken extends BaseEsdtToken implements IEsdtToken {
@Field()
derivedEGLD: string;
price?: string;
@Field()
@Field({ nullable: true })
previous24hPrice?: string;
@Field()
@Field({ nullable: true })
previous7dPrice?: string;
@Field()
@Field({ nullable: true })
volumeUSD24h?: string;
@Field()
@Field({ nullable: true })
previous24hVolume?: string;
@Field()
@Field({ nullable: true })
liquidityUSD?: string;
@Field()
@Field({ nullable: true })
swapCount24h?: number;
@Field()
@Field({ nullable: true })
previous24hSwapCount?: number;
@Field()
@Field({ nullable: true })
trendingScore?: string;
supply?: string;
circulatingSupply?: string;
Expand All @@ -75,7 +75,7 @@ export class EsdtToken extends BaseEsdtToken implements IEsdtToken {
roles?: RolesModel[];
type?: string;
balance?: string;
@Field()
@Field({ nullable: true })
createdAt?: string;
pairAddress?: string;
priceChange24h?: number;
Expand Down
Loading