From 745193ce5fce578e8c20683fd7101f43b8955622 Mon Sep 17 00:00:00 2001 From: Aaron Cook Date: Wed, 6 Nov 2024 17:44:29 +0100 Subject: [PATCH] Type `ITransactionApi` responses at "raw" --- .../transaction-api.service.ts | 118 ++++++++++-------- src/domain/chains/chains.repository.ts | 4 +- .../entities/schemas/singleton.schema.ts | 2 + .../interfaces/transaction-api.interface.ts | 54 ++++---- src/domain/safe/safe.repository.ts | 46 ++++--- 5 files changed, 119 insertions(+), 105 deletions(-) diff --git a/src/datasources/transaction-api/transaction-api.service.ts b/src/datasources/transaction-api/transaction-api.service.ts index 953f7942c4..54245f97c0 100644 --- a/src/datasources/transaction-api/transaction-api.service.ts +++ b/src/datasources/transaction-api/transaction-api.service.ts @@ -29,8 +29,13 @@ import type { Token } from '@/domain/tokens/entities/token.entity'; import type { AddConfirmationDto } from '@/domain/transactions/entities/add-confirmation.dto.entity'; import type { ProposeTransactionDto } from '@/domain/transactions/entities/propose-transaction.dto.entity'; import type { ILoggingService } from '@/logging/logging.interface'; +import type { Raw } from '@/validation/entities/raw.entity'; import { get } from 'lodash'; +/** + * TODO: Move all usage of Raw to NetworkService after fully migrated + * to "Raw" type implementation. + */ export class TransactionApi implements ITransactionApi { private static readonly ERROR_ARRAY_PATH = 'nonFieldErrors'; private static readonly HOLESKY_CHAIN_ID = '17000'; @@ -93,18 +98,18 @@ export class TransactionApi implements ITransactionApi { async getDataDecoded(args: { data: `0x${string}`; to?: `0x${string}`; - }): Promise { + }): Promise> { try { const url = `${this.baseUrl}/api/v1/data-decoder/`; - const { data: dataDecoded } = await this.networkService.post( - { - url, - data: { - data: args.data, - to: args.to, - }, + const { data: dataDecoded } = await this.networkService.post< + Raw + >({ + url, + data: { + data: args.data, + to: args.to, }, - ); + }); return dataDecoded; } catch (error) { throw this.httpErrorFactory.from(this.mapError(error)); @@ -113,11 +118,11 @@ export class TransactionApi implements ITransactionApi { // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] - async getBackbone(): Promise { + async getBackbone(): Promise> { try { const cacheDir = CacheRouter.getBackboneCacheDir(this.chainId); const url = `${this.baseUrl}/api/v1/about`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -130,11 +135,11 @@ export class TransactionApi implements ITransactionApi { // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] - async getSingletons(): Promise { + async getSingletons(): Promise> { try { const cacheDir = CacheRouter.getSingletonsCacheDir(this.chainId); const url = `${this.baseUrl}/api/v1/about/singletons/`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -145,11 +150,11 @@ export class TransactionApi implements ITransactionApi { } } - async getIndexingStatus(): Promise { + async getIndexingStatus(): Promise> { try { const cacheDir = CacheRouter.getIndexingCacheDir(this.chainId); const url = `${this.baseUrl}/api/v1/about/indexing/`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -160,14 +165,14 @@ export class TransactionApi implements ITransactionApi { } } - async getSafe(safeAddress: `0x${string}`): Promise { + async getSafe(safeAddress: `0x${string}`): Promise> { try { const cacheDir = CacheRouter.getSafeCacheDir({ chainId: this.chainId, safeAddress, }); const url = `${this.baseUrl}/api/v1/safes/${safeAddress}`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -188,6 +193,7 @@ export class TransactionApi implements ITransactionApi { // TODO: this replicates logic from the CacheFirstDataSource.get method to avoid // implementation of response remapping but we should refactor it to avoid duplication + // TODO: Change to Raw when cache service is migrated async isSafe(safeAddress: `0x${string}`): Promise { const cacheDir = CacheRouter.getIsSafeCacheDir({ chainId: this.chainId, @@ -250,14 +256,14 @@ export class TransactionApi implements ITransactionApi { // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] - async getContract(contractAddress: `0x${string}`): Promise { + async getContract(contractAddress: `0x${string}`): Promise> { try { const cacheDir = CacheRouter.getContractCacheDir({ chainId: this.chainId, contractAddress, }); const url = `${this.baseUrl}/api/v1/contracts/${contractAddress}`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.contractNotFoundExpirationTimeSeconds, @@ -275,14 +281,14 @@ export class TransactionApi implements ITransactionApi { label?: string; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getDelegatesCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/delegates/`; - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -309,14 +315,14 @@ export class TransactionApi implements ITransactionApi { label?: string; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getDelegatesCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v2/delegates/`; - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -447,14 +453,14 @@ export class TransactionApi implements ITransactionApi { // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] - async getTransfer(transferId: string): Promise { + async getTransfer(transferId: string): Promise> { try { const cacheDir = CacheRouter.getTransferCacheDir({ chainId: this.chainId, transferId, }); const url = `${this.baseUrl}/api/v1/transfer/${transferId}`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -471,14 +477,14 @@ export class TransactionApi implements ITransactionApi { onlyErc721: boolean; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getTransfersCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/transfers/`; - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -515,14 +521,14 @@ export class TransactionApi implements ITransactionApi { txHash?: string; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getIncomingTransfersCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/incoming-transfers/`; - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -570,10 +576,10 @@ export class TransactionApi implements ITransactionApi { } } - async getSafesByModule(moduleAddress: `0x${string}`): Promise { + async getSafesByModule(moduleAddress: `0x${string}`): Promise> { try { const url = `${this.baseUrl}/api/v1/modules/${moduleAddress}/safes/`; - const { data } = await this.networkService.get({ url }); + const { data } = await this.networkService.get>({ url }); return data; } catch (error) { throw this.httpErrorFactory.from(this.mapError(error)); @@ -584,14 +590,14 @@ export class TransactionApi implements ITransactionApi { // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] async getModuleTransaction( moduleTransactionId: string, - ): Promise { + ): Promise> { try { const cacheDir = CacheRouter.getModuleTransactionCacheDir({ chainId: this.chainId, moduleTransactionId, }); const url = `${this.baseUrl}/api/v1/module-transaction/${moduleTransactionId}`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -609,14 +615,14 @@ export class TransactionApi implements ITransactionApi { module?: string; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getModuleTransactionsCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/module-transactions/`; - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -657,14 +663,14 @@ export class TransactionApi implements ITransactionApi { nonceGte?: number; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getMultisigTransactionsCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/multisig-transactions/`; - return await this.dataSource.get>({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -701,14 +707,14 @@ export class TransactionApi implements ITransactionApi { async getMultisigTransaction( safeTransactionHash: string, - ): Promise { + ): Promise> { try { const cacheDir = CacheRouter.getMultisigTransactionCacheDir({ chainId: this.chainId, safeTransactionHash, }); const url = `${this.baseUrl}/api/v1/multisig-transactions/${safeTransactionHash}/`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -773,14 +779,14 @@ export class TransactionApi implements ITransactionApi { queued?: boolean; limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getAllTransactionsCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/all-transactions/`; - return await this.dataSource.get>({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -811,14 +817,14 @@ export class TransactionApi implements ITransactionApi { // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [defaultExpirationTimeInSeconds] - async getToken(address: `0x${string}`): Promise { + async getToken(address: `0x${string}`): Promise> { try { const cacheDir = CacheRouter.getTokenCacheDir({ chainId: this.chainId, address, }); const url = `${this.baseUrl}/api/v1/tokens/${address}`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.tokenNotFoundExpirationTimeSeconds, @@ -834,14 +840,14 @@ export class TransactionApi implements ITransactionApi { async getTokens(args: { limit?: number; offset?: number; - }): Promise> { + }): Promise>> { try { const cacheDir = CacheRouter.getTokensCacheDir({ chainId: this.chainId, ...args, }); const url = `${this.baseUrl}/api/v1/tokens/`; - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -860,14 +866,14 @@ export class TransactionApi implements ITransactionApi { // Important: there is no hook which invalidates this endpoint, // Therefore, this data will live in cache until [ownersExpirationTimeSeconds] - async getSafesByOwner(ownerAddress: `0x${string}`): Promise { + async getSafesByOwner(ownerAddress: `0x${string}`): Promise> { try { const cacheDir = CacheRouter.getSafesByOwnerCacheDir({ chainId: this.chainId, ownerAddress, }); const url = `${this.baseUrl}/api/v1/owners/${ownerAddress}/safes/`; - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -928,10 +934,12 @@ export class TransactionApi implements ITransactionApi { async getEstimation(args: { address: `0x${string}`; getEstimationDto: GetEstimationDto; - }): Promise { + }): Promise> { try { const url = `${this.baseUrl}/api/v1/safes/${args.address}/multisig-transactions/estimations/`; - const { data: estimation } = await this.networkService.post({ + const { data: estimation } = await this.networkService.post< + Raw + >({ url, data: { to: args.getEstimationDto.to, @@ -946,14 +954,14 @@ export class TransactionApi implements ITransactionApi { } } - async getMessageByHash(messageHash: string): Promise { + async getMessageByHash(messageHash: string): Promise> { try { const url = `${this.baseUrl}/api/v1/messages/${messageHash}`; const cacheDir = CacheRouter.getMessageByHashCacheDir({ chainId: this.chainId, messageHash, }); - return await this.dataSource.get({ + return await this.dataSource.get>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -968,14 +976,14 @@ export class TransactionApi implements ITransactionApi { safeAddress: `0x${string}`; limit?: number | undefined; offset?: number | undefined; - }): Promise> { + }): Promise>> { try { const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/messages/`; const cacheDir = CacheRouter.getMessagesBySafeCacheDir({ chainId: this.chainId, ...args, }); - return await this.dataSource.get({ + return await this.dataSource.get>>({ cacheDir, url, notFoundExpireTimeSeconds: this.defaultNotFoundExpirationTimeSeconds, @@ -1028,10 +1036,10 @@ export class TransactionApi implements ITransactionApi { safeAppId: number | null; signature: string; origin: string | null; - }): Promise { + }): Promise> { try { const url = `${this.baseUrl}/api/v1/safes/${args.safeAddress}/messages/`; - const { data } = await this.networkService.post({ + const { data } = await this.networkService.post>({ url, data: { message: args.message, diff --git a/src/domain/chains/chains.repository.ts b/src/domain/chains/chains.repository.ts index 8b47540522..e7dd498593 100644 --- a/src/domain/chains/chains.repository.ts +++ b/src/domain/chains/chains.repository.ts @@ -6,7 +6,7 @@ import { } from '@/domain/chains/entities/schemas/chain.schema'; import { Chain } from '@/domain/chains/entities/chain.entity'; import { Singleton } from '@/domain/chains/entities/singleton.entity'; -import { SingletonSchema } from '@/domain/chains/entities/schemas/singleton.schema'; +import { SingletonsSchema } from '@/domain/chains/entities/schemas/singleton.schema'; import { Page } from '@/domain/entities/page.entity'; import { IConfigApi } from '@/domain/interfaces/config-api.interface'; import { ITransactionApiManager } from '@/domain/interfaces/transaction-api.manager.interface'; @@ -94,7 +94,7 @@ export class ChainsRepository implements IChainsRepository { async getSingletons(chainId: string): Promise { const transactionApi = await this.transactionApiManager.getApi(chainId); const singletons = await transactionApi.getSingletons(); - return singletons.map((singleton) => SingletonSchema.parse(singleton)); + return SingletonsSchema.parse(singletons); } async getIndexingStatus(chainId: string): Promise { diff --git a/src/domain/chains/entities/schemas/singleton.schema.ts b/src/domain/chains/entities/schemas/singleton.schema.ts index f057cadfc4..d2e6cb9eb8 100644 --- a/src/domain/chains/entities/schemas/singleton.schema.ts +++ b/src/domain/chains/entities/schemas/singleton.schema.ts @@ -9,3 +9,5 @@ export const SingletonSchema = z.object({ lastIndexedBlockNumber: z.number(), l2: z.boolean(), }); + +export const SingletonsSchema = z.array(SingletonSchema); diff --git a/src/domain/interfaces/transaction-api.interface.ts b/src/domain/interfaces/transaction-api.interface.ts index 93d0560d77..1585cc86e4 100644 --- a/src/domain/interfaces/transaction-api.interface.ts +++ b/src/domain/interfaces/transaction-api.interface.ts @@ -19,20 +19,21 @@ import type { Transfer } from '@/domain/safe/entities/transfer.entity'; import type { Token } from '@/domain/tokens/entities/token.entity'; import type { AddConfirmationDto } from '@/domain/transactions/entities/add-confirmation.dto.entity'; import type { ProposeTransactionDto } from '@/domain/transactions/entities/propose-transaction.dto.entity'; +import type { Raw } from '@/validation/entities/raw.entity'; export interface ITransactionApi { getDataDecoded(args: { data: `0x${string}`; to?: `0x${string}`; - }): Promise; + }): Promise>; - getBackbone(): Promise; + getBackbone(): Promise>; - getSingletons(): Promise; + getSingletons(): Promise>; - getIndexingStatus(): Promise; + getIndexingStatus(): Promise>; - getSafe(safeAddress: `0x${string}`): Promise; + getSafe(safeAddress: `0x${string}`): Promise>; clearSafe(address: `0x${string}`): Promise; @@ -40,7 +41,7 @@ export interface ITransactionApi { clearIsSafe(address: `0x${string}`): Promise; - getContract(contractAddress: `0x${string}`): Promise; + getContract(contractAddress: `0x${string}`): Promise>; getDelegates(args: { safeAddress?: `0x${string}`; @@ -49,7 +50,7 @@ export interface ITransactionApi { label?: string; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; getDelegatesV2(args: { safeAddress?: `0x${string}`; @@ -58,7 +59,7 @@ export interface ITransactionApi { label?: string; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; postDelegate(args: { safeAddress: `0x${string}` | null; @@ -95,7 +96,7 @@ export interface ITransactionApi { signature: string; }): Promise; - getTransfer(transferId: string): Promise; + getTransfer(transferId: string): Promise>; getTransfers(args: { safeAddress: `0x${string}`; @@ -103,7 +104,7 @@ export interface ITransactionApi { onlyErc721?: boolean; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; clearTransfers(safeAddress: `0x${string}`): Promise; @@ -117,7 +118,7 @@ export interface ITransactionApi { txHash?: string; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; clearIncomingTransfers(safeAddress: `0x${string}`): Promise; @@ -126,9 +127,11 @@ export interface ITransactionApi { addConfirmationDto: AddConfirmationDto; }): Promise; - getSafesByModule(moduleAddress: `0x${string}`): Promise; + getSafesByModule(moduleAddress: `0x${string}`): Promise>; - getModuleTransaction(moduleTransactionId: string): Promise; + getModuleTransaction( + moduleTransactionId: string, + ): Promise>; getModuleTransactions(args: { safeAddress: `0x${string}`; @@ -137,13 +140,13 @@ export interface ITransactionApi { module?: string; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; clearModuleTransactions(safeAddress: `0x${string}`): Promise; getMultisigTransaction( safeTransactionHash: string, - ): Promise; + ): Promise>; deleteTransaction(args: { safeTxHash: string; @@ -165,7 +168,7 @@ export interface ITransactionApi { nonceGte?: number; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; clearMultisigTransactions(safeAddress: `0x${string}`): Promise; @@ -180,15 +183,18 @@ export interface ITransactionApi { queued?: boolean; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; clearAllTransactions(safeAddress: `0x${string}`): Promise; - getToken(address: `0x${string}`): Promise; + getToken(address: `0x${string}`): Promise>; - getTokens(args: { limit?: number; offset?: number }): Promise>; + getTokens(args: { + limit?: number; + offset?: number; + }): Promise>>; - getSafesByOwner(ownerAddress: `0x${string}`): Promise; + getSafesByOwner(ownerAddress: `0x${string}`): Promise>; postDeviceRegistration(args: { device: Device; @@ -206,15 +212,15 @@ export interface ITransactionApi { getEstimation(args: { address: `0x${string}`; getEstimationDto: GetEstimationDto; - }): Promise; + }): Promise>; - getMessageByHash(messageHash: string): Promise; + getMessageByHash(messageHash: string): Promise>; getMessagesBySafe(args: { safeAddress: `0x${string}`; limit?: number; offset?: number; - }): Promise>; + }): Promise>>; postMultisigTransaction(args: { address: `0x${string}`; @@ -227,7 +233,7 @@ export interface ITransactionApi { safeAppId: number | null; signature: string; origin: string | null; - }): Promise; + }): Promise>; postMessageSignature(args: { messageHash: string; diff --git a/src/domain/safe/safe.repository.ts b/src/domain/safe/safe.repository.ts index 7943a36cfb..ee3684e861 100644 --- a/src/domain/safe/safe.repository.ts +++ b/src/domain/safe/safe.repository.ts @@ -241,13 +241,12 @@ export class SafeRepository implements ISafeRepository { const transactionService = await this.transactionApiManager.getApi( args.chainId, ); - const page: Page = - await transactionService.getMultisigTransactions({ - ...args, - safeAddress: args.safe.address, - executed: false, - nonceGte: args.safe.nonce, - }); + const page = await transactionService.getMultisigTransactions({ + ...args, + safeAddress: args.safe.address, + executed: false, + nonceGte: args.safe.nonce, + }); return MultisigTransactionPageSchema.parse(page); } @@ -283,13 +282,11 @@ export class SafeRepository implements ISafeRepository { const transactionService = await this.transactionApiManager.getApi( args.chainId, ); - const page: Page = await transactionService.getAllTransactions( - { - ...args, - executed: true, - queued: false, - }, - ); + const page = await transactionService.getAllTransactions({ + ...args, + executed: true, + queued: false, + }); return TransactionTypePageSchema.parse(page); } @@ -338,9 +335,10 @@ export class SafeRepository implements ISafeRepository { const transactionService = await this.transactionApiManager.getApi( args.chainId, ); - const { safe } = await transactionService.getMultisigTransaction( + const transaction = await transactionService.getMultisigTransaction( args.safeTxHash, ); + const { safe } = MultisigTransactionSchema.parse(transaction); await transactionService.deleteTransaction(args); // Ensure transaction is removed from cache in case event is not received @@ -458,17 +456,17 @@ export class SafeRepository implements ISafeRepository { const transactionService = await this.transactionApiManager.getApi( args.chainId, ); - const page: Page = - await transactionService.getMultisigTransactions({ - ...args, - ordering: '-nonce', - trusted: true, - limit: 1, - }); + const page = await transactionService.getMultisigTransactions({ + ...args, + ordering: '-nonce', + trusted: true, + limit: 1, + }); + const { results } = MultisigTransactionPageSchema.parse(page); - return isEmpty(page.results) + return isEmpty(results) ? null - : MultisigTransactionSchema.parse(page.results[0]); + : MultisigTransactionSchema.parse(results[0]); } async proposeTransaction(args: {