From e0ab5ef53ce3c530e6b711c657b0f35ba187053e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9ctor=20G=C3=B3mez?= Date: Fri, 16 Feb 2024 13:26:33 +0100 Subject: [PATCH] Add SafeBalancesApi (#1151) - Moves `TransactionApi` balances-related functions (`getBalances`, `clearBalances`, `getCollectibles`, `clearCollectibles`) to `SafeBalancesApi`. - Adds `SafeBalancesApi` routing (between different chain-specific Transaction Services) to `BalancesApiManager` (heavily inspired by `TransactionApiManager`). - Unifies naming to stick to the interface (between `clearLocalBalances` and `clearBalances`). - Simplifies both `BalancesRepository` and `CollectiblesRepository`, as some complexity is pushed to `BalancesApiManager`. --- .../balances-api/balances-api.manager.spec.ts | 111 +++++++++++++++-- .../balances-api/balances-api.manager.ts | 46 +++++-- .../balances-api/safe-balances-api.service.ts | 117 ++++++++++++++++++ src/datasources/cache/cache.router.ts | 1 - .../transaction-api.service.spec.ts | 49 -------- .../transaction-api.service.ts | 78 ------------ .../balances/balances.repository.interface.ts | 5 +- src/domain/balances/balances.repository.ts | 17 +-- .../collectibles/collectibles.repository.ts | 20 +-- .../interfaces/balances-api.interface.ts | 10 +- .../balances-api.manager.interface.ts | 2 +- .../interfaces/transaction-api.interface.ts | 20 --- src/routes/cache-hooks/cache-hooks.service.ts | 8 +- 13 files changed, 282 insertions(+), 202 deletions(-) create mode 100644 src/datasources/balances-api/safe-balances-api.service.ts diff --git a/src/datasources/balances-api/balances-api.manager.spec.ts b/src/datasources/balances-api/balances-api.manager.spec.ts index af98743d4f..44160652cf 100644 --- a/src/datasources/balances-api/balances-api.manager.spec.ts +++ b/src/datasources/balances-api/balances-api.manager.spec.ts @@ -1,14 +1,34 @@ import { IConfigurationService } from '@/config/configuration.service.interface'; import { BalancesApiManager } from '@/datasources/balances-api/balances-api.manager'; +import { CacheFirstDataSource } from '@/datasources/cache/cache.first.data.source'; +import { ICacheService } from '@/datasources/cache/cache.service.interface'; +import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; +import { chainBuilder } from '@/domain/chains/entities/__tests__/chain.builder'; import { IBalancesApi } from '@/domain/interfaces/balances-api.interface'; +import { IConfigApi } from '@/domain/interfaces/config-api.interface'; +import { faker } from '@faker-js/faker'; const configurationService = { getOrThrow: jest.fn(), - get: jest.fn(), -} as IConfigurationService; +} as jest.MockedObjectDeep; const configurationServiceMock = jest.mocked(configurationService); +const configApi = { + getChain: jest.fn(), +} as jest.MockedObjectDeep; + +const configApiMock = jest.mocked(configApi); + +const dataSource = { + get: jest.fn(), +} as jest.MockedObjectDeep; + +const dataSourceMock = jest.mocked(dataSource); + +const cacheService = {} as jest.MockedObjectDeep; +const httpErrorFactory = {} as jest.MockedObjectDeep; + const zerionBalancesApi = { getBalances: jest.fn(), clearBalances: jest.fn(), @@ -31,6 +51,10 @@ describe('Balances API Manager Tests', () => { it('should return true if the chain is included in the balance-externalized chains', () => { const manager = new BalancesApiManager( configurationService, + configApiMock, + dataSourceMock, + cacheService, + httpErrorFactory, zerionBalancesApiMock, ); expect(manager.useExternalApi('1')).toEqual(true); @@ -40,6 +64,10 @@ describe('Balances API Manager Tests', () => { it('should return false if the chain is included in the balance-externalized chains', () => { const manager = new BalancesApiManager( configurationService, + configApiMock, + dataSourceMock, + cacheService, + httpErrorFactory, zerionBalancesApiMock, ); expect(manager.useExternalApi('4')).toEqual(false); @@ -47,20 +75,85 @@ describe('Balances API Manager Tests', () => { }); describe('getBalancesApi checks', () => { - it('should return the Zerion API', () => { + it('should return the Zerion API', async () => { const manager = new BalancesApiManager( configurationService, + configApiMock, + dataSourceMock, + cacheService, + httpErrorFactory, zerionBalancesApiMock, ); - expect(manager.getBalancesApi('2')).toEqual(zerionBalancesApi); + + const result = await manager.getBalancesApi('2'); + + expect(result).toEqual(zerionBalancesApi); }); - it('should throw an error if no API is found for the input chainId', () => { - const manager = new BalancesApiManager( + const txServiceUrl = faker.internet.url({ appendSlash: false }); + const vpcTxServiceUrl = faker.internet.url({ appendSlash: false }); + + /** + * In the following tests, getBalances is used to check the parameters to + * which {@link CacheFirstDataSource} was called with. + */ + it.each([ + [true, vpcTxServiceUrl], + [false, txServiceUrl], + ])('vpcUrl is %s', async (useVpcUrl, expectedUrl) => { + const zerionChainIds = ['1', '2', '3']; + const chain = chainBuilder() + .with('chainId', '4') + .with('transactionService', txServiceUrl) + .with('vpcTransactionService', vpcTxServiceUrl) + .build(); + const expirationTimeInSeconds = faker.number.int(); + const notFoundExpireTimeSeconds = faker.number.int(); + configurationServiceMock.getOrThrow.mockImplementation((key) => { + if (key === 'safeTransaction.useVpcUrl') return useVpcUrl; + else if (key === 'expirationTimeInSeconds.default') + return expirationTimeInSeconds; + else if (key === 'expirationTimeInSeconds.notFound.default') + return notFoundExpireTimeSeconds; + else if (key === 'features.zerionBalancesChainIds') + return zerionChainIds; + throw new Error(`Unexpected key: ${key}`); + }); + configApiMock.getChain.mockResolvedValue(chain); + const balancesApiManager = new BalancesApiManager( configurationService, + configApiMock, + dataSourceMock, + cacheService, + httpErrorFactory, zerionBalancesApiMock, ); - expect(() => manager.getBalancesApi('5')).toThrow(); + + const safeBalancesApi = await balancesApiManager.getBalancesApi( + chain.chainId, + ); + const safeAddress = faker.finance.ethereumAddress(); + const trusted = faker.datatype.boolean(); + const excludeSpam = faker.datatype.boolean(); + + await safeBalancesApi.getBalances({ + safeAddress, + trusted, + excludeSpam, + }); + + expect(dataSourceMock.get).toHaveBeenCalledWith({ + cacheDir: expect.anything(), + url: `${expectedUrl}/api/v1/safes/${safeAddress}/balances/`, + notFoundExpireTimeSeconds: notFoundExpireTimeSeconds, + expireTimeSeconds: expirationTimeInSeconds, + networkRequest: expect.objectContaining({ + params: expect.objectContaining({ + trusted: trusted, + exclude_spam: excludeSpam, + }), + }), + }); }); }); @@ -69,6 +162,10 @@ describe('Balances API Manager Tests', () => { zerionBalancesApiMock.getFiatCodes.mockReturnValue(['EUR', 'GBP', 'ETH']); const manager = new BalancesApiManager( configurationService, + configApiMock, + dataSourceMock, + cacheService, + httpErrorFactory, zerionBalancesApiMock, ); diff --git a/src/datasources/balances-api/balances-api.manager.ts b/src/datasources/balances-api/balances-api.manager.ts index 30ab9c7fdf..66a7a8f94b 100644 --- a/src/datasources/balances-api/balances-api.manager.ts +++ b/src/datasources/balances-api/balances-api.manager.ts @@ -1,35 +1,65 @@ import { IConfigurationService } from '@/config/configuration.service.interface'; +import { SafeBalancesApi } from '@/datasources/balances-api/safe-balances-api.service'; import { IZerionBalancesApi } from '@/datasources/balances-api/zerion-balances-api.service'; +import { CacheFirstDataSource } from '@/datasources/cache/cache.first.data.source'; +import { + CacheService, + ICacheService, +} from '@/datasources/cache/cache.service.interface'; +import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; import { IBalancesApi } from '@/domain/interfaces/balances-api.interface'; import { IBalancesApiManager } from '@/domain/interfaces/balances-api.manager.interface'; +import { IConfigApi } from '@/domain/interfaces/config-api.interface'; import { Inject, Injectable } from '@nestjs/common'; @Injectable() export class BalancesApiManager implements IBalancesApiManager { - private readonly zerionBalancesChainIds: string[]; + private safeBalancesApiMap: Record = {}; + private readonly zerionChainIds: string[]; private readonly zerionBalancesApi: IBalancesApi; + private readonly useVpcUrl: boolean; constructor( @Inject(IConfigurationService) private readonly configurationService: IConfigurationService, + @Inject(IConfigApi) private readonly configApi: IConfigApi, + private readonly dataSource: CacheFirstDataSource, + @Inject(CacheService) private readonly cacheService: ICacheService, + private readonly httpErrorFactory: HttpErrorFactory, @Inject(IZerionBalancesApi) zerionBalancesApi: IBalancesApi, ) { - this.zerionBalancesChainIds = this.configurationService.getOrThrow< - string[] - >('features.zerionBalancesChainIds'); + this.zerionChainIds = this.configurationService.getOrThrow( + 'features.zerionBalancesChainIds', + ); + this.useVpcUrl = this.configurationService.getOrThrow( + 'safeTransaction.useVpcUrl', + ); this.zerionBalancesApi = zerionBalancesApi; } useExternalApi(chainId: string): boolean { - return this.zerionBalancesChainIds.includes(chainId); + return this.zerionChainIds.includes(chainId); } - getBalancesApi(chainId: string): IBalancesApi { + async getBalancesApi(chainId: string): Promise { if (this._isSupportedByZerion(chainId)) { return this.zerionBalancesApi; } - throw new Error(`Chain ID ${chainId} balances provider is not configured`); + + const safeBalancesApi = this.safeBalancesApiMap[chainId]; + if (safeBalancesApi !== undefined) return safeBalancesApi; + + const chain = await this.configApi.getChain(chainId); + this.safeBalancesApiMap[chainId] = new SafeBalancesApi( + chainId, + this.useVpcUrl ? chain.vpcTransactionService : chain.transactionService, + this.dataSource, + this.cacheService, + this.configurationService, + this.httpErrorFactory, + ); + return this.safeBalancesApiMap[chainId]; } getFiatCodes(): string[] { @@ -37,6 +67,6 @@ export class BalancesApiManager implements IBalancesApiManager { } private _isSupportedByZerion(chainId: string): boolean { - return this.zerionBalancesChainIds.includes(chainId); + return this.zerionChainIds.includes(chainId); } } diff --git a/src/datasources/balances-api/safe-balances-api.service.ts b/src/datasources/balances-api/safe-balances-api.service.ts new file mode 100644 index 0000000000..f0245954e3 --- /dev/null +++ b/src/datasources/balances-api/safe-balances-api.service.ts @@ -0,0 +1,117 @@ +import { IConfigurationService } from '@/config/configuration.service.interface'; +import { CacheFirstDataSource } from '@/datasources/cache/cache.first.data.source'; +import { CacheRouter } from '@/datasources/cache/cache.router'; +import { ICacheService } from '@/datasources/cache/cache.service.interface'; +import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; +import { Balance } from '@/domain/balances/entities/balance.entity'; +import { Collectible } from '@/domain/collectibles/entities/collectible.entity'; +import { Page } from '@/domain/entities/page.entity'; +import { IBalancesApi } from '@/domain/interfaces/balances-api.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SafeBalancesApi implements IBalancesApi { + private readonly defaultExpirationTimeInSeconds: number; + private readonly defaultNotFoundExpirationTimeSeconds: number; + + constructor( + private readonly chainId: string, + private readonly baseUrl: string, + private readonly dataSource: CacheFirstDataSource, + private readonly cacheService: ICacheService, + private readonly configurationService: IConfigurationService, + private readonly httpErrorFactory: HttpErrorFactory, + ) { + this.defaultExpirationTimeInSeconds = + this.configurationService.getOrThrow( + 'expirationTimeInSeconds.default', + ); + this.defaultNotFoundExpirationTimeSeconds = + this.configurationService.getOrThrow( + 'expirationTimeInSeconds.notFound.default', + ); + } + + async getBalances(args: { + safeAddress: string; + trusted?: boolean; + excludeSpam?: boolean; + }): Promise { + try { + const cacheDir = CacheRouter.getBalancesCacheDir({ + chainId: this.chainId, + ...args, + }); + const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/balances/`; + return await this.dataSource.get({ + cacheDir, + url, + notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, + networkRequest: { + params: { + trusted: args.trusted, + exclude_spam: args.excludeSpam, + }, + }, + expireTimeSeconds: this.defaultExpirationTimeInSeconds, + }); + } catch (error) { + throw this.httpErrorFactory.from(error); + } + } + + async clearBalances(args: { safeAddress: string }): Promise { + const key = CacheRouter.getBalancesCacheKey({ + chainId: this.chainId, + safeAddress: args.safeAddress, + }); + await this.cacheService.deleteByKey(key); + } + + async getCollectibles(args: { + safeAddress: string; + limit?: number; + offset?: number; + trusted?: boolean; + excludeSpam?: boolean; + }): Promise> { + try { + const cacheDir = CacheRouter.getCollectiblesCacheDir({ + chainId: this.chainId, + ...args, + }); + const url = `${this.baseUrl}/api/v2/safes/${args.safeAddress}/collectibles/`; + return await this.dataSource.get({ + cacheDir, + url, + notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, + networkRequest: { + params: { + limit: args.limit, + offset: args.offset, + trusted: args.trusted, + exclude_spam: args.excludeSpam, + }, + }, + expireTimeSeconds: this.defaultExpirationTimeInSeconds, + }); + } catch (error) { + throw this.httpErrorFactory.from(error); + } + } + + async clearCollectibles(args: { safeAddress: string }): Promise { + const key = CacheRouter.getCollectiblesKey({ + chainId: this.chainId, + safeAddress: args.safeAddress, + }); + await this.cacheService.deleteByKey(key); + } + + /** + * No fiat prices are available from this provider. + */ + getFiatCodes(): string[] { + return []; + } +} diff --git a/src/datasources/cache/cache.router.ts b/src/datasources/cache/cache.router.ts index 1888ca0767..e7b8e0c1f1 100644 --- a/src/datasources/cache/cache.router.ts +++ b/src/datasources/cache/cache.router.ts @@ -51,7 +51,6 @@ export class CacheRouter { ); } - // TODO: remove this prefixed key if eventually only one balances provider is used static getZerionBalancesCacheKey(args: { chainId: string; safeAddress: string; diff --git a/src/datasources/transaction-api/transaction-api.service.spec.ts b/src/datasources/transaction-api/transaction-api.service.spec.ts index f0a6e89296..9af5fe4c8b 100644 --- a/src/datasources/transaction-api/transaction-api.service.spec.ts +++ b/src/datasources/transaction-api/transaction-api.service.spec.ts @@ -9,7 +9,6 @@ import { TransactionApi } from '@/datasources/transaction-api/transaction-api.se import { backboneBuilder } from '@/domain/backbone/entities/__tests__/backbone.builder'; import { DataSourceError } from '@/domain/errors/data-source.error'; import { safeBuilder } from '@/domain/safe/entities/__tests__/safe.builder'; -import { balanceBuilder } from '@/domain/balances/entities/__tests__/balance.builder'; const dataSource = { get: jest.fn(), @@ -77,39 +76,6 @@ describe('TransactionApi', () => { ); }); - describe('Balances', () => { - it('should return the balances retrieved', async () => { - const data = [balanceBuilder().build(), balanceBuilder().build()]; - mockDataSource.get.mockResolvedValue(data); - - const actual = await service.getBalances({ - safeAddress: 'test', - trusted: true, - excludeSpam: true, - }); - - expect(actual).toBe(data); - expect(mockHttpErrorFactory.from).toHaveBeenCalledTimes(0); - }); - - it('should forward error', async () => { - const expected = new DataSourceError('something happened'); - mockDataSource.get.mockRejectedValueOnce(new Error('Some error')); - mockHttpErrorFactory.from.mockReturnValue(expected); - - await expect( - service.getBalances({ - safeAddress: 'test', - trusted: true, - excludeSpam: true, - }), - ).rejects.toThrow(expected); - - expect(mockDataSource.get).toHaveBeenCalledTimes(1); - expect(mockHttpErrorFactory.from).toHaveBeenCalledTimes(1); - }); - }); - describe('Backbone', () => { it('should return the backbone retrieved', async () => { const data = backboneBuilder().build(); @@ -133,21 +99,6 @@ describe('TransactionApi', () => { }); }); - describe('Clear Local Balances', () => { - it('should call delete', async () => { - const safeAddress = faker.finance.ethereumAddress(); - mockCacheService.deleteByKey.mockResolvedValueOnce(1); - - await service.clearLocalBalances(safeAddress); - - expect(mockCacheService.deleteByKey).toHaveBeenCalledTimes(1); - expect(mockCacheService.deleteByKey).toHaveBeenCalledWith( - `${chainId}_balances_${safeAddress}`, - ); - expect(mockHttpErrorFactory.from).toHaveBeenCalledTimes(0); - }); - }); - describe('Safe', () => { describe('getSafe', () => { it('should return retrieved safe', async () => { diff --git a/src/datasources/transaction-api/transaction-api.service.ts b/src/datasources/transaction-api/transaction-api.service.ts index 06ecac925c..96efb865b9 100644 --- a/src/datasources/transaction-api/transaction-api.service.ts +++ b/src/datasources/transaction-api/transaction-api.service.ts @@ -6,7 +6,6 @@ import { HttpErrorFactory } from '@/datasources/errors/http-error-factory'; import { INetworkService } from '@/datasources/network/network.service.interface'; import { Backbone } from '@/domain/backbone/entities/backbone.entity'; import { Singleton } from '@/domain/chains/entities/singleton.entity'; -import { Collectible } from '@/domain/collectibles/entities/collectible.entity'; import { Contract } from '@/domain/contracts/entities/contract.entity'; import { DataDecoded } from '@/domain/data-decoder/entities/data-decoded.entity'; import { Delegate } from '@/domain/delegate/entities/delegate.entity'; @@ -26,7 +25,6 @@ import { Transfer } from '@/domain/safe/entities/transfer.entity'; import { Token } from '@/domain/tokens/entities/token.entity'; import { AddConfirmationDto } from '@/domain/transactions/entities/add-confirmation.dto.entity'; import { ProposeTransactionDto } from '@/domain/transactions/entities/propose-transaction.dto.entity'; -import { Balance } from '@/domain/balances/entities/balance.entity'; export class TransactionApi implements ITransactionApi { private readonly defaultExpirationTimeInSeconds: number; @@ -61,42 +59,6 @@ export class TransactionApi implements ITransactionApi { ); } - async getBalances(args: { - safeAddress: string; - trusted?: boolean; - excludeSpam?: boolean; - }): Promise { - try { - const cacheDir = CacheRouter.getBalancesCacheDir({ - chainId: this.chainId, - ...args, - }); - const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/balances/`; - return await this.dataSource.get({ - cacheDir, - url, - notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, - networkRequest: { - params: { - trusted: args.trusted, - exclude_spam: args.excludeSpam, - }, - }, - expireTimeSeconds: this.defaultExpirationTimeInSeconds, - }); - } catch (error) { - throw this.httpErrorFactory.from(error); - } - } - - async clearLocalBalances(safeAddress: string): Promise { - const key = CacheRouter.getBalancesCacheKey({ - chainId: this.chainId, - safeAddress, - }); - await this.cacheService.deleteByKey(key); - } - async getDataDecoded(args: { data: string; to?: string; @@ -116,46 +78,6 @@ export class TransactionApi implements ITransactionApi { } } - async getCollectibles(args: { - safeAddress: string; - limit?: number; - offset?: number; - trusted?: boolean; - excludeSpam?: boolean; - }): Promise> { - try { - const cacheDir = CacheRouter.getCollectiblesCacheDir({ - chainId: this.chainId, - ...args, - }); - const url = `${this.baseUrl}/api/v2/safes/${args.safeAddress}/collectibles/`; - return await this.dataSource.get({ - cacheDir, - url, - notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, - networkRequest: { - params: { - limit: args.limit, - offset: args.offset, - trusted: args.trusted, - exclude_spam: args.excludeSpam, - }, - }, - expireTimeSeconds: this.defaultExpirationTimeInSeconds, - }); - } catch (error) { - throw this.httpErrorFactory.from(error); - } - } - - async clearCollectibles(safeAddress: string): Promise { - const key = CacheRouter.getCollectiblesKey({ - chainId: this.chainId, - safeAddress, - }); - await this.cacheService.deleteByKey(key); - } - // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] async getBackbone(): Promise { diff --git a/src/domain/balances/balances.repository.interface.ts b/src/domain/balances/balances.repository.interface.ts index 05d491d33f..d804754afe 100644 --- a/src/domain/balances/balances.repository.interface.ts +++ b/src/domain/balances/balances.repository.interface.ts @@ -17,10 +17,7 @@ export interface IBalancesRepository { /** * Clears any stored local balance data of {@link safeAddress} on {@link chainId} */ - clearLocalBalances(args: { - chainId: string; - safeAddress: string; - }): Promise; + clearBalances(args: { chainId: string; safeAddress: string }): Promise; /** * Gets the list of supported fiat codes. diff --git a/src/domain/balances/balances.repository.ts b/src/domain/balances/balances.repository.ts index 6ee66ced58..d1e621181c 100644 --- a/src/domain/balances/balances.repository.ts +++ b/src/domain/balances/balances.repository.ts @@ -38,19 +38,12 @@ export class BalancesRepository implements IBalancesRepository { : this._getBalancesFromTransactionApi(args); } - async clearLocalBalances(args: { + async clearBalances(args: { chainId: string; safeAddress: string; }): Promise { - if (this.balancesApiManager.useExternalApi(args.chainId)) { - const api = this.balancesApiManager.getBalancesApi(args.chainId); - await api.clearBalances(args); - } else { - const api = await this.transactionApiManager.getTransactionApi( - args.chainId, - ); - await api.clearLocalBalances(args.safeAddress); - } + const api = await this.balancesApiManager.getBalancesApi(args.chainId); + await api.clearBalances(args); } getFiatCodes(): string[] { @@ -64,7 +57,7 @@ export class BalancesRepository implements IBalancesRepository { trusted?: boolean; excludeSpam?: boolean; }): Promise { - const api = this.balancesApiManager.getBalancesApi(args.chainId); + const api = await this.balancesApiManager.getBalancesApi(args.chainId); const balances = await api.getBalances(args); return balances.map((balance) => this.balancesValidator.validate(balance)); } @@ -77,7 +70,7 @@ export class BalancesRepository implements IBalancesRepository { excludeSpam?: boolean; }): Promise { const { chainId, safeAddress, fiatCode, trusted, excludeSpam } = args; - const api = await this.transactionApiManager.getTransactionApi(chainId); + const api = await this.balancesApiManager.getBalancesApi(chainId); const balances = await api.getBalances({ safeAddress, trusted, diff --git a/src/domain/collectibles/collectibles.repository.ts b/src/domain/collectibles/collectibles.repository.ts index a66322d873..4ca206ece2 100644 --- a/src/domain/collectibles/collectibles.repository.ts +++ b/src/domain/collectibles/collectibles.repository.ts @@ -24,7 +24,6 @@ export class CollectiblesRepository implements ICollectiblesRepository { trusted?: boolean; excludeSpam?: boolean; }): Promise> { - // TODO: route TransactionApi collectibles retrieval from BalancesApiManager const page = (await this.balancesApiManager.useExternalApi(args.chainId)) ? await this._getCollectiblesFromBalancesApi(args) : await this._getCollectiblesFromTransactionApi(args); @@ -37,15 +36,8 @@ export class CollectiblesRepository implements ICollectiblesRepository { chainId: string; safeAddress: string; }): Promise { - if (this.balancesApiManager.useExternalApi(args.chainId)) { - const api = await this.balancesApiManager.getBalancesApi(args.chainId); - await api.clearCollectibles(args); - } else { - const transactionApi = await this.transactionApiManager.getTransactionApi( - args.chainId, - ); - await transactionApi.clearCollectibles(args.safeAddress); - } + const api = await this.balancesApiManager.getBalancesApi(args.chainId); + await api.clearCollectibles(args); } private async _getCollectiblesFromBalancesApi(args: { @@ -54,7 +46,7 @@ export class CollectiblesRepository implements ICollectiblesRepository { limit?: number; offset?: number; }): Promise> { - const api = this.balancesApiManager.getBalancesApi(args.chainId); + const api = await this.balancesApiManager.getBalancesApi(args.chainId); return api.getCollectibles(args); } @@ -66,10 +58,8 @@ export class CollectiblesRepository implements ICollectiblesRepository { trusted?: boolean; excludeSpam?: boolean; }): Promise> { - const transactionApi = await this.transactionApiManager.getTransactionApi( - args.chainId, - ); - return transactionApi.getCollectibles({ + const api = await this.balancesApiManager.getBalancesApi(args.chainId); + return api.getCollectibles({ safeAddress: args.safeAddress, limit: args.limit, offset: args.offset, diff --git a/src/domain/interfaces/balances-api.interface.ts b/src/domain/interfaces/balances-api.interface.ts index 9c67a6f6f3..86657000a5 100644 --- a/src/domain/interfaces/balances-api.interface.ts +++ b/src/domain/interfaces/balances-api.interface.ts @@ -4,18 +4,22 @@ import { Page } from '@/domain/entities/page.entity'; export interface IBalancesApi { getBalances(args: { - chainId: string; safeAddress: string; - fiatCode: string; + fiatCode?: string; + chainId?: string; + trusted?: boolean; + excludeSpam?: boolean; }): Promise; clearBalances(args: { chainId: string; safeAddress: string }): Promise; getCollectibles(args: { - chainId: string; safeAddress: string; + chainId?: string; limit?: number; offset?: number; + trusted?: boolean; + excludeSpam?: boolean; }): Promise>; clearCollectibles(args: { diff --git a/src/domain/interfaces/balances-api.manager.interface.ts b/src/domain/interfaces/balances-api.manager.interface.ts index 1721eb92b3..a838d61f3f 100644 --- a/src/domain/interfaces/balances-api.manager.interface.ts +++ b/src/domain/interfaces/balances-api.manager.interface.ts @@ -20,7 +20,7 @@ export interface IBalancesApiManager { * @param chainId - the chain identifier to check. * @returns {@link IBalancesApi} configured for the input chain ID. */ - getBalancesApi(chainId: string): IBalancesApi; + getBalancesApi(chainId: string): Promise; /** * Gets the list of supported fiat codes. diff --git a/src/domain/interfaces/transaction-api.interface.ts b/src/domain/interfaces/transaction-api.interface.ts index fef4371d05..3d8a287979 100644 --- a/src/domain/interfaces/transaction-api.interface.ts +++ b/src/domain/interfaces/transaction-api.interface.ts @@ -1,7 +1,5 @@ import { Backbone } from '@/domain/backbone/entities/backbone.entity'; -import { Balance } from '@/domain/balances/entities/balance.entity'; import { Singleton } from '@/domain/chains/entities/singleton.entity'; -import { Collectible } from '@/domain/collectibles/entities/collectible.entity'; import { Contract } from '@/domain/contracts/entities/contract.entity'; import { DataDecoded } from '@/domain/data-decoder/entities/data-decoded.entity'; import { Delegate } from '@/domain/delegate/entities/delegate.entity'; @@ -22,26 +20,8 @@ import { AddConfirmationDto } from '@/domain/transactions/entities/add-confirmat import { ProposeTransactionDto } from '@/domain/transactions/entities/propose-transaction.dto.entity'; export interface ITransactionApi { - getBalances(args: { - safeAddress: string; - trusted?: boolean; - excludeSpam?: boolean; - }): Promise; - - clearLocalBalances(safeAddress: string): Promise; - getDataDecoded(args: { data: string; to?: string }): Promise; - getCollectibles(args: { - safeAddress: string; - limit?: number; - offset?: number; - trusted?: boolean; - excludeSpam?: boolean; - }): Promise>; - - clearCollectibles(safeAddress: string): Promise; - getBackbone(): Promise; getSingletons(): Promise; diff --git a/src/routes/cache-hooks/cache-hooks.service.ts b/src/routes/cache-hooks/cache-hooks.service.ts index 0704347293..e2468ebbe3 100644 --- a/src/routes/cache-hooks/cache-hooks.service.ts +++ b/src/routes/cache-hooks/cache-hooks.service.ts @@ -144,7 +144,7 @@ export class CacheHooksService { // - the incoming transfers for that safe case EventType.INCOMING_ETHER: promises.push( - this.balancesRepository.clearLocalBalances({ + this.balancesRepository.clearBalances({ chainId: event.chainId, safeAddress: event.address, }), @@ -174,7 +174,7 @@ export class CacheHooksService { // - the transfers for that safe case EventType.OUTGOING_ETHER: promises.push( - this.balancesRepository.clearLocalBalances({ + this.balancesRepository.clearBalances({ chainId: event.chainId, safeAddress: event.address, }), @@ -202,7 +202,7 @@ export class CacheHooksService { // - the incoming transfers for that safe case EventType.INCOMING_TOKEN: promises.push( - this.balancesRepository.clearLocalBalances({ + this.balancesRepository.clearBalances({ chainId: event.chainId, safeAddress: event.address, }), @@ -237,7 +237,7 @@ export class CacheHooksService { // - the transfers for that safe case EventType.OUTGOING_TOKEN: promises.push( - this.balancesRepository.clearLocalBalances({ + this.balancesRepository.clearBalances({ chainId: event.chainId, safeAddress: event.address, }),