diff --git a/.env.sample b/.env.sample index ffbbcee17..f0df425ed 100644 --- a/.env.sample +++ b/.env.sample @@ -7,4 +7,12 @@ # e.g: ENABLED_APPS="tokemak,synthetix" ENABLED_APPS= -ENABLED_HELPERS= \ No newline at end of file + + +# Control which apps' positions should be resolved from Zapper API +# This is particularly useful when you're trying to write an app +# having position dependencies on other apps present on Studio that are failing/slow. +# This helps to scope your work purely on your app, resolving its dependencies externally. +# The name of the apps MUST correspond to their folder name +# e.g: API_RESOLVED_POSITIONS="tokemak,synthetix" +API_RESOLVED_POSITIONS= \ No newline at end of file diff --git a/src/apps/curve/ethereum/curve.pool.token-fetcher.ts b/src/apps/curve/ethereum/curve.pool.token-fetcher.ts index fbc5780c7..145831f07 100644 --- a/src/apps/curve/ethereum/curve.pool.token-fetcher.ts +++ b/src/apps/curve/ethereum/curve.pool.token-fetcher.ts @@ -1,5 +1,5 @@ import { Inject } from '@nestjs/common'; -import { uniqBy } from 'lodash'; +import { compact, uniqBy } from 'lodash'; import { Register } from '~app-toolkit/decorators'; import { SYNTHETIX_DEFINITION } from '~apps/synthetix'; @@ -104,9 +104,7 @@ export class EthereumCurvePoolTokenFetcher implements PositionFetcher v.address, - ); + const tokens = compact([v1Pools, v1MetaPools, v2Pools, v1FactoryPools, v2FactoryPools, cryptoFactoryPools].flat()); + return uniqBy(tokens, v => v.address); } } diff --git a/src/cache/cache-on-interval.service.ts b/src/cache/cache-on-interval.service.ts index 6a2d5cfd4..b16fb5621 100644 --- a/src/cache/cache-on-interval.service.ts +++ b/src/cache/cache-on-interval.service.ts @@ -53,7 +53,6 @@ export class CacheOnIntervalService implements OnModuleInit, OnModuleDestroy { const methodRef = instance[methodName]; const cacheKey: CacheOnIntervalOptions['key'] = this.reflector.get(CACHE_ON_INTERVAL_KEY, methodRef); const cacheTimeout: CacheOnIntervalOptions['timeout'] = this.reflector.get(CACHE_ON_INTERVAL_TIMEOUT, methodRef); - const ttl = Math.floor(cacheTimeout / 1000); // Don't register cache on interval when missing parameters if (!cacheKey || !cacheTimeout) return; @@ -88,7 +87,7 @@ export class CacheOnIntervalService implements OnModuleInit, OnModuleDestroy { } liveData .then(d => { - return cacheManager.set(cacheKey, d, { ttl }); + return cacheManager.set(cacheKey, d); }) .then(() => { logger.log(`Cache ready for for ${instance.constructor.name}#${methodName}`); @@ -101,7 +100,7 @@ export class CacheOnIntervalService implements OnModuleInit, OnModuleDestroy { const interval = setInterval(async () => { try { const liveData = await methodRef.apply(instance); - await cacheManager.set(cacheKey, liveData, { ttl }); + await cacheManager.set(cacheKey, liveData); } catch (e) { logger.error(`@CacheOnInterval error for ${instance.constructor.name}#${methodName}`, e); } diff --git a/src/cache/cache.service.ts b/src/cache/cache.service.ts index 274a1d8fd..4df752026 100644 --- a/src/cache/cache.service.ts +++ b/src/cache/cache.service.ts @@ -35,11 +35,6 @@ export class CacheService implements OnModuleInit { return typeof cacheKey === 'function' ? cacheKey(...args) : cacheKey; } - private extractTtl(cacheTtl: CacheOptions['ttl'], args: any[]) { - if (isNil(cacheTtl)) return 0; - return typeof cacheTtl === 'function' ? cacheTtl(...args) : cacheTtl; - } - private registerCache(instance: any, methodName: string) { const logger = this.logger; const methodRef = instance[methodName]; @@ -52,7 +47,6 @@ export class CacheService implements OnModuleInit { // Service references const cacheManager = this.cacheManager; const extractKey = this.extractKey; - const extractTtl = this.extractTtl; // Augment the method to be cached with caching mechanism instance[methodName] = async function (...args: any[]) { @@ -63,9 +57,8 @@ export class CacheService implements OnModuleInit { return cachedValue; } else { try { - const cacheTtl = extractTtl(rawCacheTtl, args); const liveData = await methodRef.apply(this, args); - await cacheManager.set(cacheKey, liveData, { ttl: cacheTtl }); + await cacheManager.set(cacheKey, liveData); return liveData; } catch (e) { logger.error(`@Cache error for ${instance.constructor.name}#${methodName}`, e); diff --git a/src/main.module.ts b/src/main.module.ts index 9429602fb..33357210f 100644 --- a/src/main.module.ts +++ b/src/main.module.ts @@ -1,5 +1,6 @@ import { Module } from '@nestjs/common'; import { ConfigModule } from '@nestjs/config'; +import { compact } from 'lodash'; import { AppToolkitModule } from '~app-toolkit/app-toolkit.module'; import { AppsModule } from '~apps/apps.module'; @@ -22,6 +23,7 @@ import { StatsModule } from '~stats/stats.module'; url: process.env.ZAPPER_API_URL ?? 'https://api.zapper.fi', key: process.env.ZAPPER_API_KEY ?? '96e0cc51-a62e-42ca-acee-910ea7d2a241', }, + apiResolvedPositions: compact((process.env.API_RESOLVED_POSITIONS ?? '').split(',')), }), ], }), diff --git a/src/position/position-source/position-source.registry.ts b/src/position/position-source/position-source.registry.ts index 2fd383488..84c504d54 100644 --- a/src/position/position-source/position-source.registry.ts +++ b/src/position/position-source/position-source.registry.ts @@ -1,4 +1,5 @@ import { Inject, Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { partition, groupBy, map, compact } from 'lodash'; import { ContractType } from '~position/contract.interface'; @@ -10,15 +11,24 @@ import { PositionSource } from './position-source.interface'; @Injectable() export class RegistryPositionSource implements PositionSource { - constructor(@Inject(PositionFetcherRegistry) private readonly positionFetcherRegistry: PositionFetcherRegistry) {} + constructor( + @Inject(ConfigService) private readonly configService: ConfigService, + @Inject(PositionFetcherRegistry) private readonly positionFetcherRegistry: PositionFetcherRegistry, + ) {} + + private getApiResolvedPositions(): string[] { + return this.configService.get('apiResolvedPositions') ?? []; + } getSupported(definitions: AppGroupsDefinition[], contractType: ContractType) { const defs = definitions.flatMap(({ appId, groupIds, network }) => groupIds.map(groupId => { const def = { appId, groupId, network, contractType }; try { + // Will throw if not found this.positionFetcherRegistry.get({ type: contractType, appId, groupId, network }); - return { ...def, supported: true }; + const isLocallyProvided = !this.getApiResolvedPositions().includes(appId); + return { ...def, supported: isLocallyProvided }; } catch (e) { return { ...def, supported: false }; }