From 64040d5bce6abfb9eafa2494fd5a62f02baee705 Mon Sep 17 00:00:00 2001 From: Traian Anghel Date: Mon, 14 Oct 2024 16:05:11 +0300 Subject: [PATCH 1/2] Basic support for innerTx in transfers endpoint (#1351) --- .../indexer/elastic/elastic.indexer.helper.ts | 5 ++++- .../transactions/entities/transaction.type.ts | 6 +++++- src/endpoints/transfers/transfer.service.ts | 13 ++++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/common/indexer/elastic/elastic.indexer.helper.ts b/src/common/indexer/elastic/elastic.indexer.helper.ts index 26cb0d0fb..73c4838d6 100644 --- a/src/common/indexer/elastic/elastic.indexer.helper.ts +++ b/src/common/indexer/elastic/elastic.indexer.helper.ts @@ -286,7 +286,10 @@ export class ElasticIndexerHelper { QueryType.Exists('canBeIgnored'), ])) .withCondition(QueryConditionOptions.should, QueryType.Must([ - QueryType.Match('type', 'normal'), + QueryType.Should([ + QueryType.Match('type', 'normal'), + QueryType.Match('type', 'innerTx'), + ]), QueryType.Should([ QueryType.Match('sender', filter.address), QueryType.Match('receiver', filter.address), diff --git a/src/endpoints/transactions/entities/transaction.type.ts b/src/endpoints/transactions/entities/transaction.type.ts index 4b36cbd7f..c74e4ce50 100644 --- a/src/endpoints/transactions/entities/transaction.type.ts +++ b/src/endpoints/transactions/entities/transaction.type.ts @@ -3,7 +3,8 @@ import { registerEnumType } from "@nestjs/graphql"; export enum TransactionType { Transaction = 'Transaction', SmartContractResult = 'SmartContractResult', - Reward = 'Reward' + InnerTransaction = 'InnerTransaction', + Reward = 'Reward', } registerEnumType(TransactionType, { @@ -16,6 +17,9 @@ registerEnumType(TransactionType, { SmartContractResult: { description: 'SmartContractResult type.', }, + InnerTransaction: { + description: 'InnerTransaction type.', + }, Reward: { description: 'Reward type.', }, diff --git a/src/endpoints/transfers/transfer.service.ts b/src/endpoints/transfers/transfer.service.ts index 4ec698e2a..c4321ba3f 100644 --- a/src/endpoints/transfers/transfer.service.ts +++ b/src/endpoints/transfers/transfer.service.ts @@ -50,9 +50,20 @@ export class TransferService { for (const elasticOperation of elasticOperations) { const transaction = ApiUtils.mergeObjects(new TransactionDetailed(), elasticOperation); - transaction.type = elasticOperation.type === 'normal' ? TransactionType.Transaction : TransactionType.SmartContractResult; transaction.relayer = elasticOperation.relayerAddr; + switch (elasticOperation.type) { + case 'normal': + transaction.type = TransactionType.Transaction; + break; + case 'unsigned': + transaction.type = TransactionType.SmartContractResult; + break; + case 'innerTx': + transaction.type = TransactionType.InnerTransaction; + break; + } + if (transaction.type === TransactionType.SmartContractResult) { delete transaction.gasLimit; delete transaction.gasPrice; From 595359b569e7bef763428723e4a455e4432b5c9c Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Tue, 22 Oct 2024 09:48:52 +0300 Subject: [PATCH 2/2] feat/sovereign (#1341) * add cacheDuration * add support for NftSubType * Update elastic.indexer.helper.ts * Update nft.filter.ts * Update nft.controller.ts * add filter.type on collection elastic * add subType even for SFT * fix mex.token.charts.spec.ts * Update nft.controller.ts * revert changes * add subType filter * add collections subType filter * add address collection roles subType * add subType filter for roles/collections count * add accounts/nfts subType filter * add accounts/collection subType Filter * improved support for subType filtering * commented out field decoration for inner transactions in context of graphgql * fix array subType filter * Update elastic.indexer.helper.ts * remove empty line --------- Co-authored-by: tanghel --- config/config.devnet.yaml | 1 + config/config.mainnet.yaml | 1 + config/config.testnet.yaml | 1 + src/common/api-config/api.config.service.ts | 3 + .../indexer/elastic/elastic.indexer.helper.ts | 30 +++++++++- .../elastic/elastic.indexer.service.ts | 39 +++++++++++-- src/endpoints/accounts/account.controller.ts | 55 +++++++++++++++---- .../collections/collection.controller.ts | 6 ++ .../collections/collection.service.ts | 5 +- .../collections/entities/collection.filter.ts | 2 + src/endpoints/esdt/esdt.address.service.ts | 12 +++- src/endpoints/esdt/esdt.service.ts | 1 + src/endpoints/nfts/entities/nft.filter.ts | 4 +- src/endpoints/nfts/entities/nft.sub.type.ts | 12 ++++ src/endpoints/nfts/entities/nft.ts | 4 +- src/endpoints/nfts/nft.controller.ts | 20 +++++-- .../entities/transaction.detailed.ts | 2 +- .../account.detailed.input.ts | 4 +- src/graphql/entities/nft/nft.input.ts | 4 +- src/graphql/schema/schema.gql | 48 ++++++++++++---- src/main.ts | 2 + 21 files changed, 204 insertions(+), 52 deletions(-) diff --git a/config/config.devnet.yaml b/config/config.devnet.yaml index 66df9bc22..c0ce425ba 100644 --- a/config/config.devnet.yaml +++ b/config/config.devnet.yaml @@ -144,6 +144,7 @@ caching: cacheTtl: 6 processTtl: 600 poolLimit: 50 + cacheDuration: 3 keepAliveTimeout: downstream: 61000 upstream: 60000 diff --git a/config/config.mainnet.yaml b/config/config.mainnet.yaml index 020316e01..7bb286ea8 100644 --- a/config/config.mainnet.yaml +++ b/config/config.mainnet.yaml @@ -148,6 +148,7 @@ caching: cacheTtl: 6 processTtl: 600 poolLimit: 50 + cacheDuration: 3 keepAliveTimeout: downstream: 61000 upstream: 60000 diff --git a/config/config.testnet.yaml b/config/config.testnet.yaml index cd72ece09..dd4bb8984 100644 --- a/config/config.testnet.yaml +++ b/config/config.testnet.yaml @@ -147,6 +147,7 @@ caching: cacheTtl: 6 processTtl: 600 poolLimit: 50 + cacheDuration: 3 keepAliveTimeout: downstream: 61000 upstream: 60000 diff --git a/src/common/api-config/api.config.service.ts b/src/common/api-config/api.config.service.ts index aae84f9c3..beff35a77 100644 --- a/src/common/api-config/api.config.service.ts +++ b/src/common/api-config/api.config.service.ts @@ -905,4 +905,7 @@ export class ApiConfigService { return serviceUrl; } + getCacheDuration(): number { + return this.configService.get('caching.cacheDuration') ?? 3; + } } diff --git a/src/common/indexer/elastic/elastic.indexer.helper.ts b/src/common/indexer/elastic/elastic.indexer.helper.ts index 73c4838d6..c4b7fbafe 100644 --- a/src/common/indexer/elastic/elastic.indexer.helper.ts +++ b/src/common/indexer/elastic/elastic.indexer.helper.ts @@ -124,7 +124,7 @@ export class ElasticIndexerHelper { elasticQuery = this.getRoleCondition(elasticQuery, 'ESDTTransferRole', address, filter.canTransferRole); } - if (filter.excludeMetaESDT === true) { + if (filter.excludeMetaESDT === true && !filter.type) { elasticQuery = elasticQuery.withMustMultiShouldCondition([ ...this.nonFungibleEsdtTypes, ...this.semiFungibleEsdtTypes, @@ -151,6 +151,10 @@ export class ElasticIndexerHelper { elasticQuery = elasticQuery.withMustMultiShouldCondition(types, type => QueryType.Match('type', type)); } + if (filter.subType) { + elasticQuery = elasticQuery.withMustMultiShouldCondition(filter.subType, subType => QueryType.Match('type', subType)); + } + return elasticQuery.withMustMatchCondition('token', filter.collection, QueryOperator.AND) .withMustMultiShouldCondition(filter.identifiers, identifier => QueryType.Match('token', identifier, QueryOperator.AND)) .withSearchWildcardCondition(filter.search, ['token', 'name']); @@ -175,12 +179,32 @@ export class ElasticIndexerHelper { elasticQuery = elasticQuery.withSearchWildcardCondition(filter.search, ['token', 'name']); } - if (filter.type !== undefined) { - const types = (filter.type ?? '').split(','); + if (filter.type) { + const types = []; + + for (const type of filter.type) { + switch (type) { + case NftType.NonFungibleESDT: + types.push(...this.nonFungibleEsdtTypes); + break; + case NftType.SemiFungibleESDT: + types.push(...this.semiFungibleEsdtTypes); + break; + case NftType.MetaESDT: + types.push(...this.metaEsdtTypes); + break; + default: + types.push(filter.type); + } + } elasticQuery = elasticQuery.withMustMultiShouldCondition(types, type => QueryType.Match('type', type)); } + if (filter.subType) { + elasticQuery = elasticQuery.withMustMultiShouldCondition(filter.subType, subType => QueryType.Match('type', subType, QueryOperator.AND)); + } + if (identifier !== undefined) { elasticQuery = elasticQuery.withMustCondition(QueryType.Match('identifier', identifier, QueryOperator.AND)); } diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index 3bfcb19a3..550297a0e 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -3,7 +3,6 @@ import { BinaryUtils } from "@multiversx/sdk-nestjs-common"; import { ElasticService, ElasticQuery, QueryOperator, QueryType, QueryConditionOptions, ElasticSortOrder, ElasticSortProperty, TermsQuery, RangeGreaterThanOrEqual, MatchQuery } from "@multiversx/sdk-nestjs-elastic"; import { IndexerInterface } from "../indexer.interface"; import { ApiConfigService } from "src/common/api-config/api.config.service"; -import { NftType } from "src/endpoints/nfts/entities/nft.type"; import { CollectionFilter } from "src/endpoints/collections/entities/collection.filter"; import { QueryPagination } from "src/common/entities/query.pagination"; import { EsdtType } from "src/endpoints/esdt/entities/esdt.type"; @@ -27,10 +26,14 @@ import { AccountHistoryFilter } from "src/endpoints/accounts/entities/account.hi import { AccountAssets } from "src/common/assets/entities/account.assets"; import { NotWritableError } from "../entities/not.writable.error"; import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter"; - +import { NftType } from "../entities/nft.type"; @Injectable() export class ElasticIndexerService implements IndexerInterface { + private nonFungibleEsdtTypes: NftType[] = [NftType.NonFungibleESDT, NftType.NonFungibleESDTv2, NftType.DynamicNonFungibleESDT]; + private semiFungibleEsdtTypes: NftType[] = [NftType.SemiFungibleESDT, NftType.DynamicSemiFungibleESDT]; + private metaEsdtTypes: NftType[] = [NftType.MetaESDT, NftType.DynamicMetaESDT]; + constructor( private readonly apiConfigService: ApiConfigService, private readonly elasticService: ElasticService, @@ -774,11 +777,34 @@ export class ElasticIndexerService implements IndexerInterface { filter: CollectionFilter, pagination: QueryPagination ): Promise<{ collection: string, count: number, balance: number }[]> { - const types = [NftType.SemiFungibleESDT, NftType.NonFungibleESDT]; + let filterTypes = [...this.nonFungibleEsdtTypes, ...this.semiFungibleEsdtTypes]; + if (!filter.excludeMetaESDT) { - types.push(NftType.MetaESDT); + filterTypes.push(...this.metaEsdtTypes); } + if (filter.type && filter.type.length > 0) { + filterTypes = []; + + for (const type of filter.type) { + switch (type) { + case NftType.NonFungibleESDT: + filterTypes.push(...this.nonFungibleEsdtTypes); + break; + case NftType.SemiFungibleESDT: + filterTypes.push(...this.semiFungibleEsdtTypes); + break; + case NftType.MetaESDT: + filterTypes.push(...this.metaEsdtTypes); + break; + default: + filterTypes.push(type); + } + } + } + + console.log({ subType: filter.subType }); + const elasticQuery = ElasticQuery.create() .withMustExistCondition('identifier') .withMustMatchCondition('address', address) @@ -786,8 +812,8 @@ export class ElasticIndexerService implements IndexerInterface { .withMustMatchCondition('token', filter.collection, QueryOperator.AND) .withMustMultiShouldCondition(filter.identifiers, identifier => QueryType.Match('token', identifier, QueryOperator.AND)) .withSearchWildcardCondition(filter.search, ['token', 'name']) - .withMustMultiShouldCondition(filter.type, type => QueryType.Match('type', type)) - .withMustMultiShouldCondition(types, type => QueryType.Match('type', type)) + .withMustMultiShouldCondition(filterTypes, type => QueryType.Match('type', type)) + .withMustMultiShouldCondition(filter.subType, subType => QueryType.Match('type', subType)) .withExtra({ aggs: { collections: { @@ -828,6 +854,7 @@ export class ElasticIndexerService implements IndexerInterface { return data; } + async getAssetsForToken(identifier: string): Promise { return await this.elasticService.getCustomValue('tokens', identifier, 'assets'); } diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 66658221e..1899e2970 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -56,6 +56,7 @@ import { AccountKeyFilter } from './entities/account.key.filter'; import { ScamType } from 'src/common/entities/scam-type.enum'; import { DeepHistoryInterceptor } from 'src/interceptors/deep-history.interceptor'; import { MexPairType } from '../mex/entities/mex.pair.type'; +import { NftSubType } from '../nfts/entities/nft.sub.type'; import { AccountContract } from './entities/account.contract'; @Controller() @@ -351,6 +352,7 @@ export class AccountController { @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by type (NonFungibleESDTv2/DynamicNonFungibleESDT/DynamicSemiFungibleESDT)', required: false }) @ApiQuery({ name: 'owner', description: 'Filter by collection owner', required: false }) @ApiQuery({ name: 'canCreate', description: 'Filter by property canCreate (boolean)', required: false }) @ApiQuery({ name: 'canBurn', description: 'Filter by property canBurn (boolean)', required: false }) @@ -366,6 +368,7 @@ export class AccountController { @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('owner', ParseAddressPipe) owner?: string, @Query('canCreate', new ParseBoolPipe) canCreate?: boolean, @Query('canBurn', new ParseBoolPipe) canBurn?: boolean, @@ -375,13 +378,28 @@ export class AccountController { @Query('canTransferRole', new ParseBoolPipe) canTransferRole?: boolean, @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { - return await this.collectionService.getCollectionsWithRolesForAddress(address, new CollectionFilter({ search, type, owner, canCreate, canBurn, canAddQuantity, canUpdateAttributes, canAddUri, canTransferRole, excludeMetaESDT }), new QueryPagination({ from, size })); + return await this.collectionService.getCollectionsWithRolesForAddress( + address, + new CollectionFilter({ + search, + type, + subType, + owner, + canCreate, + canBurn, + canAddQuantity, + canUpdateAttributes, + canAddUri, + canTransferRole, + excludeMetaESDT, + }), new QueryPagination({ from, size })); } @Get("/accounts/:address/roles/collections/count") @ApiOperation({ summary: 'Account collection count', description: 'Returns the total number of NFT/SFT/MetaESDT collections where the account is owner or has some special roles assigned to it' }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by type (NonFungibleESDTv2/DynamicNonFungibleESDT/DynamicSemiFungibleESDT)', required: false }) @ApiQuery({ name: 'owner', description: 'Filter by collection owner', required: false }) @ApiQuery({ name: 'canCreate', description: 'Filter by property canCreate (boolean)', required: false }) @ApiQuery({ name: 'canBurn', description: 'Filter by property canCreate (boolean)', required: false }) @@ -392,13 +410,14 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('owner', ParseAddressPipe) owner?: string, @Query('canCreate', new ParseBoolPipe) canCreate?: boolean, @Query('canBurn', new ParseBoolPipe) canBurn?: boolean, @Query('canAddQuantity', new ParseBoolPipe) canAddQuantity?: boolean, @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { - return await this.collectionService.getCollectionCountForAddressWithRoles(address, new CollectionFilter({ search, type, owner, canCreate, canBurn, canAddQuantity, excludeMetaESDT })); + return await this.collectionService.getCollectionCountForAddressWithRoles(address, new CollectionFilter({ search, type, subType, owner, canCreate, canBurn, canAddQuantity, excludeMetaESDT })); } @Get("/accounts/:address/roles/collections/c") @@ -407,6 +426,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('owner', ParseAddressPipe) owner?: string, @Query('canCreate', new ParseBoolPipe) canCreate?: boolean, @Query('canBurn', new ParseBoolPipe) canBurn?: boolean, @@ -414,7 +434,7 @@ export class AccountController { @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionCountForAddressWithRoles(address, new CollectionFilter({ - search, type, owner, canCreate, canBurn, canAddQuantity, excludeMetaESDT, + search, type, subType, owner, canCreate, canBurn, canAddQuantity, excludeMetaESDT, })); } @@ -509,6 +529,7 @@ export class AccountController { @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by type (NonFungibleESDTv2/DynamicNonFungibleESDT/DynamicSemiFungibleESDT)', required: false }) @ApiQuery({ name: 'excludeMetaESDT', description: 'Exclude collections of type "MetaESDT" in the response', required: false, type: Boolean }) @ApiOkResponse({ type: [NftCollectionAccount] }) async getAccountNftCollections( @@ -517,24 +538,30 @@ export class AccountController { @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { - return await this.collectionService.getCollectionsForAddress(address, new CollectionFilter({ search, type, excludeMetaESDT }), new QueryPagination({ from, size })); + return await this.collectionService.getCollectionsForAddress( + address, + new CollectionFilter({ search, type, subType, excludeMetaESDT }), + new QueryPagination({ from, size })); } @Get("/accounts/:address/collections/count") @ApiOperation({ summary: 'Account collection count', description: 'Returns the total number of NFT/SFT/MetaESDT collections where the account is owner or has some special roles assigned to it' }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by type (NonFungibleESDTv2/DynamicNonFungibleESDT/DynamicSemiFungibleESDT)', required: false }) @ApiQuery({ name: 'excludeMetaESDT', description: 'Exclude collections of type "MetaESDT" in the response', required: false, type: Boolean }) @ApiOkResponse({ type: Number }) async getNftCollectionCount( @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { - return await this.collectionService.getCollectionCountForAddress(address, new CollectionFilter({ search, type, excludeMetaESDT })); + return await this.collectionService.getCollectionCountForAddress(address, new CollectionFilter({ search, type, subType, excludeMetaESDT })); } @Get("/accounts/:address/collections/c") @@ -543,9 +570,10 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { - return await this.collectionService.getCollectionCountForAddress(address, new CollectionFilter({ search, type, excludeMetaESDT })); + return await this.collectionService.getCollectionCountForAddress(address, new CollectionFilter({ search, type, subType, excludeMetaESDT })); } @Get("/accounts/:address/collections/:collection") @@ -572,6 +600,7 @@ export class AccountController { @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'identifiers', description: 'Filter by identifiers, comma-separated', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by type (NonFungibleESDTv2/DynamicNonFungibleESDT/DynamicSemiFungibleESDT)', required: false }) @ApiQuery({ name: 'collection', description: 'Get all tokens by token collection. Deprecated, replaced by collections parameter', required: false, deprecated: true }) @ApiQuery({ name: 'collections', description: 'Get all tokens by token collections, comma-separated', required: false }) @ApiQuery({ name: 'name', description: 'Get all nfts by name', required: false }) @@ -594,7 +623,8 @@ export class AccountController { @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], - @Query('type') type?: NftType, + @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('collection') collection?: string, @Query('collections', ParseArrayPipe) collections?: string[], @Query('name') name?: string, @@ -617,6 +647,7 @@ export class AccountController { search, identifiers, type, + subType, collection, name, collections, @@ -640,6 +671,7 @@ export class AccountController { @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'identifiers', description: 'Filter by identifiers, comma-separated', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by subType', required: false }) @ApiQuery({ name: 'collection', description: 'Get all tokens by token collection', required: false }) @ApiQuery({ name: 'collections', description: 'Get all tokens by token collections, comma-separated', required: false }) @ApiQuery({ name: 'name', description: 'Get all nfts by name', required: false }) @@ -656,7 +688,8 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], @Query('search') search?: string, - @Query('type') type?: NftType, + @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('collection') collection?: string, @Query('collections', ParseArrayPipe) collections?: string[], @Query('name') name?: string, @@ -673,6 +706,7 @@ export class AccountController { search, identifiers, type, + subType, collection, collections, name, @@ -693,7 +727,8 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], - @Query('type') type?: NftType, + @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('collection') collection?: string, @Query('collections', ParseArrayPipe) collections?: string[], @Query('name') name?: string, @@ -706,7 +741,7 @@ export class AccountController { @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, @Query('timestamp', ParseIntPipe) _timestamp?: number, ): Promise { - return await this.nftService.getNftCountForAddress(address, new NftFilter({ search, identifiers, type, collection, collections, name, tags, creator, hasUris, includeFlagged, excludeMetaESDT, isScam, scamType })); + return await this.nftService.getNftCountForAddress(address, new NftFilter({ search, identifiers, type, subType, collection, collections, name, tags, creator, hasUris, includeFlagged, excludeMetaESDT, isScam, scamType })); } @Get("/accounts/:address/nfts/:nft") diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index 22b3efbb7..5f34ca7c2 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -25,6 +25,7 @@ import { Response } from "express"; import { SortCollections } from "./entities/sort.collections"; import { ParseArrayPipeOptions } from "@multiversx/sdk-nestjs-common/lib/pipes/entities/parse.array.options"; import { TransferService } from "../transfers/transfer.service"; +import { NftSubType } from "../nfts/entities/nft.sub.type"; @Controller() @ApiTags('collections') @@ -44,6 +45,7 @@ export class CollectionController { @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'identifiers', description: 'Search by collection identifiers, comma-separated', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by type (NonFungibleESDTv2/DynamicNonFungibleESDT/DynamicSemiFungibleESDT)', required: false }) @ApiQuery({ name: 'creator', description: 'Filter collections where the given address has a creator role', required: false, deprecated: true }) @ApiQuery({ name: 'before', description: 'Return all collections before given timestamp', required: false, type: Number }) @ApiQuery({ name: 'after', description: 'Return all collections after given timestamp', required: false, type: Number }) @@ -62,6 +64,7 @@ export class CollectionController { @Query('search') search?: string, @Query('identifiers', ParseCollectionArrayPipe) identifiers?: string[], @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('creator', ParseAddressPipe) creator?: string, @Query('before', new ParseIntPipe) before?: number, @Query('after', new ParseIntPipe) after?: number, @@ -78,6 +81,7 @@ export class CollectionController { return await this.collectionService.getNftCollections(new QueryPagination({ from, size }), new CollectionFilter({ search, type, + subType, identifiers, canCreate: canCreate ?? creator, before, @@ -111,6 +115,7 @@ export class CollectionController { async getCollectionCount( @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('creator', ParseAddressPipe) creator?: string, @Query('before', new ParseIntPipe) before?: number, @Query('after', new ParseIntPipe) after?: number, @@ -125,6 +130,7 @@ export class CollectionController { return await this.collectionService.getNftCollectionCount(new CollectionFilter({ search, type, + subType, canCreate: canCreate ?? creator, before, after, diff --git a/src/endpoints/collections/collection.service.ts b/src/endpoints/collections/collection.service.ts index 8fd59355b..ecff14fd2 100644 --- a/src/endpoints/collections/collection.service.ts +++ b/src/endpoints/collections/collection.service.ts @@ -98,10 +98,7 @@ export class CollectionService { break; } - if (nftCollection.type !== indexedCollection.type) { - nftCollection.subType = indexedCollection.type as NftSubType; - } - + nftCollection.subType = indexedCollection.type as NftSubType; nftCollection.timestamp = indexedCollection.timestamp; if (nftCollection.type.in(NftType.NonFungibleESDT, NftType.SemiFungibleESDT)) { diff --git a/src/endpoints/collections/entities/collection.filter.ts b/src/endpoints/collections/entities/collection.filter.ts index ba6764d7d..d30a652da 100644 --- a/src/endpoints/collections/entities/collection.filter.ts +++ b/src/endpoints/collections/entities/collection.filter.ts @@ -1,4 +1,5 @@ import { SortOrder } from "src/common/entities/sort.order"; +import { NftSubType } from "src/endpoints/nfts/entities/nft.sub.type"; import { NftType } from "../../nfts/entities/nft.type"; import { SortCollections } from "./sort.collections"; @@ -11,6 +12,7 @@ export class CollectionFilter { identifiers?: string[]; search?: string; type?: NftType[]; + subType?: NftSubType[]; owner?: string; before?: number; after?: number; diff --git a/src/endpoints/esdt/esdt.address.service.ts b/src/endpoints/esdt/esdt.address.service.ts index de7c1dfc2..a5346eb7f 100644 --- a/src/endpoints/esdt/esdt.address.service.ts +++ b/src/endpoints/esdt/esdt.address.service.ts @@ -68,7 +68,6 @@ export class EsdtAddressService { private async getNftsForAddressFromElastic(address: string, filter: NftFilter, pagination: QueryPagination): Promise { const esdts = await this.indexerService.getNftsForAddress(address, filter, pagination) as any; - const gatewayNfts: GatewayNft[] = []; for (const esdt of esdts) { @@ -116,6 +115,7 @@ export class EsdtAddressService { const indexedCollection = indexedCollections[accountCollection.collection]; if (indexedCollection) { accountCollection.timestamp = indexedCollection.timestamp; + accountCollection.subType = indexedCollection.type; const collectionWithRoles = ApiUtils.mergeObjects(new NftCollectionWithRoles(), accountCollection); @@ -386,9 +386,15 @@ export class EsdtAddressService { } if (filter.type) { - const types = (filter.type ?? '').split(','); + const nftTypes = filter.type ?? []; + + nfts = nfts.filter(x => nftTypes.includes(x.type)); + } + + if (filter.subType) { + const nftSubTypes = filter.subType ?? []; - nfts = nfts.filter(x => types.includes(x.type)); + nfts = nfts.filter(x => nftSubTypes.includes(x.subType)); } if (filter.collection) { diff --git a/src/endpoints/esdt/esdt.service.ts b/src/endpoints/esdt/esdt.service.ts index 7d1cb7b54..ed0a618fd 100644 --- a/src/endpoints/esdt/esdt.service.ts +++ b/src/endpoints/esdt/esdt.service.ts @@ -213,6 +213,7 @@ export class EsdtService { identifier: elasticProperties.identifier, name: elasticProperties.name, type: elasticProperties.type as EsdtType, + subType: elasticProperties.type as EsdtSubType, owner: elasticProperties.currentOwner, decimals: elasticProperties.numDecimals, canUpgrade: elasticProperties.properties?.canUpgrade ?? false, diff --git a/src/endpoints/nfts/entities/nft.filter.ts b/src/endpoints/nfts/entities/nft.filter.ts index 83b663eaf..a1a49cda6 100644 --- a/src/endpoints/nfts/entities/nft.filter.ts +++ b/src/endpoints/nfts/entities/nft.filter.ts @@ -2,6 +2,7 @@ import { SortOrder } from "src/common/entities/sort.order"; import { SortCollectionNfts } from "src/endpoints/collections/entities/sort.collection.nfts"; import { NftType } from "./nft.type"; import { ScamType } from "src/common/entities/scam-type.enum"; +import { NftSubType } from "./nft.sub.type"; export class NftFilter { constructor(init?: Partial) { @@ -10,7 +11,8 @@ export class NftFilter { search?: string; identifiers?: string[]; - type?: NftType; + type?: NftType[]; + subType?: NftSubType[]; collection?: string; collections?: string[]; tags?: string[]; diff --git a/src/endpoints/nfts/entities/nft.sub.type.ts b/src/endpoints/nfts/entities/nft.sub.type.ts index 8da4d3935..b0bfe15ea 100644 --- a/src/endpoints/nfts/entities/nft.sub.type.ts +++ b/src/endpoints/nfts/entities/nft.sub.type.ts @@ -1,6 +1,9 @@ import { registerEnumType } from "@nestjs/graphql"; export enum NftSubType { + NonFungibleESDT = 'NonFungibleESDT', + SemiFungibleESDT = 'SemiFungibleESDT', + MetaESDT = 'MetaESDT', NonFungibleESDTv2 = 'NonFungibleESDTv2', DynamicNonFungibleESDT = 'DynamicNonFungibleESDT', DynamicSemiFungibleESDT = 'DynamicSemiFungibleESDT', @@ -11,6 +14,15 @@ registerEnumType(NftSubType, { name: 'NftSubType', description: 'NFT subtype.', valuesMap: { + NonFungibleESDT: { + description: 'Non-fungible ESDT NFT type.', + }, + SemiFungibleESDT: { + description: 'Semi-fungible ESDT NFT type.', + }, + MetaESDT: { + description: 'Meta ESDT NFT type.', + }, NonFungibleESDTv2: { description: 'Non-fungible ESDT v2 NFT type.', }, diff --git a/src/endpoints/nfts/entities/nft.ts b/src/endpoints/nfts/entities/nft.ts index ddb34956e..06365a96e 100644 --- a/src/endpoints/nfts/entities/nft.ts +++ b/src/endpoints/nfts/entities/nft.ts @@ -43,8 +43,8 @@ export class Nft { type: NftType = NftType.NonFungibleESDT; @Field(() => NftSubType, { description: "NFT sub type for the given NFT.", nullable: true }) - @ApiProperty({ enum: NftSubType, nullable: true }) - subType: NftSubType | undefined = undefined; + @ApiProperty({ enum: NftSubType }) + subType: NftSubType = NftSubType.NonFungibleESDT; @Field(() => String, { description: "Name for the given NFT." }) @ApiProperty({ type: String }) diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index cf0da0893..539d1797e 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -9,7 +9,7 @@ import { NftType } from "./entities/nft.type"; import { NftService } from "./nft.service"; import { QueryPagination } from 'src/common/entities/query.pagination'; import { NftQueryOptions } from './entities/nft.query.options'; -import { ParseAddressPipe, ParseBoolPipe, ParseArrayPipe, ParseIntPipe, ParseNftPipe, ParseCollectionPipe, ApplyComplexity, ParseAddressArrayPipe, ParseBlockHashPipe, ParseEnumPipe, ParseRecordPipe, ParseNftArrayPipe, ParseCollectionArrayPipe } from '@multiversx/sdk-nestjs-common'; +import { ParseAddressPipe, ParseBoolPipe, ParseArrayPipe, ParseIntPipe, ParseNftPipe, ParseCollectionPipe, ApplyComplexity, ParseAddressArrayPipe, ParseBlockHashPipe, ParseEnumPipe, ParseRecordPipe, ParseNftArrayPipe, ParseCollectionArrayPipe, ParseEnumArrayPipe } from '@multiversx/sdk-nestjs-common'; import { TransactionDetailed } from '../transactions/entities/transaction.detailed'; import { TransactionStatus } from '../transactions/entities/transaction.status'; import { SortOrder } from 'src/common/entities/sort.order'; @@ -19,6 +19,7 @@ import { TransactionFilter } from '../transactions/entities/transaction.filter'; import { Transaction } from '../transactions/entities/transaction'; import { ScamType } from 'src/common/entities/scam-type.enum'; import { TransferService } from '../transfers/transfer.service'; +import { NftSubType } from './entities/nft.sub.type'; @Controller() @ApiTags('nfts') @@ -38,7 +39,8 @@ export class NftController { @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'identifiers', description: 'Search by token identifiers, comma-separated', required: false }) - @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by subType', required: false }) @ApiQuery({ name: 'collection', description: 'Get all tokens by token collection', required: false }) @ApiQuery({ name: 'collections', description: 'Get all tokens by token collections, comma-separated', required: false }) @ApiQuery({ name: 'name', description: 'Get all nfts by name', required: false }) @@ -59,7 +61,8 @@ export class NftController { @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], - @Query('type') type?: NftType, + @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('collection', ParseCollectionPipe) collection?: string, @Query('collections', ParseCollectionArrayPipe) collections?: string[], @Query('name') name?: string, @@ -82,6 +85,7 @@ export class NftController { search, identifiers, type, + subType, collection, collections, name, @@ -106,6 +110,7 @@ export class NftController { @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'identifiers', description: 'Search by token identifiers, comma-separated', required: false }) @ApiQuery({ name: 'type', description: 'Filter by type (NonFungibleESDT/SemiFungibleESDT/MetaESDT)', required: false }) + @ApiQuery({ name: 'subType', description: 'Filter by subType', required: false }) @ApiQuery({ name: 'collection', description: 'Get all tokens by token collection', required: false }) @ApiQuery({ name: 'collections', description: 'Get all tokens by token collections, comma-separated', required: false }) @ApiQuery({ name: 'name', description: 'Get all nfts by name', required: false }) @@ -122,7 +127,8 @@ export class NftController { async getNftCount( @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], - @Query('type') type?: NftType, + @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('collection', ParseCollectionPipe) collection?: string, @Query('collections', ParseCollectionArrayPipe) collections?: string[], @Query('name') name?: string, @@ -142,6 +148,7 @@ export class NftController { search, identifiers, type, + subType, collection, collections, name, @@ -163,7 +170,8 @@ export class NftController { async getNftCountAlternative( @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], - @Query('type') type?: NftType, + @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], + @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @Query('collection', ParseCollectionPipe) collection?: string, @Query('collections', ParseCollectionArrayPipe) collections?: string[], @Query('name') name?: string, @@ -178,7 +186,7 @@ export class NftController { @Query('isScam', new ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, ): Promise { - return await this.nftService.getNftCount(new NftFilter({ search, identifiers, type, collection, collections, name, tags, creator, isWhitelistedStorage, hasUris, isNsfw, traits, before, after, isScam, scamType })); + return await this.nftService.getNftCount(new NftFilter({ search, identifiers, type, subType, collection, collections, name, tags, creator, isWhitelistedStorage, hasUris, isNsfw, traits, before, after, isScam, scamType })); } @Get('/nfts/:identifier') diff --git a/src/endpoints/transactions/entities/transaction.detailed.ts b/src/endpoints/transactions/entities/transaction.detailed.ts index e21475b11..d3cca8434 100644 --- a/src/endpoints/transactions/entities/transaction.detailed.ts +++ b/src/endpoints/transactions/entities/transaction.detailed.ts @@ -66,7 +66,7 @@ export class TransactionDetailed extends Transaction { @ApiProperty({ type: String, nullable: true }) relayedVersion: string | undefined = undefined; - @Field(() => [TransactionInner], { description: 'Inner transactions list details.', nullable: true }) + // @Field(() => [TransactionInner], { description: 'Inner transactions list details.', nullable: true }) @ApiProperty({ type: TransactionInner, isArray: true }) innerTransactions: TransactionInner[] | undefined = undefined; } diff --git a/src/graphql/entities/account.detailed/account.detailed.input.ts b/src/graphql/entities/account.detailed/account.detailed.input.ts index 26cacd046..50ffd82a7 100644 --- a/src/graphql/entities/account.detailed/account.detailed.input.ts +++ b/src/graphql/entities/account.detailed/account.detailed.input.ts @@ -69,8 +69,8 @@ export class GetNftsAccountInput { @Field(() => [ID], { name: "identifiers", description: "NFT comma-separated identifiers list to retrieve for the given result set.", nullable: true }) identifiers: Array | undefined = undefined; - @Field(() => NftType, { name: "type", description: "NFT type to retrieve for the given result set.", nullable: true }) - type: NftType | undefined = undefined; + @Field(() => [NftType], { name: "type", description: "NFT type to retrieve for the given result set.", nullable: true }) + type: Array | undefined = undefined; @Field(() => [String], { name: "collections", description: "Collections to retrieve for the given result set.", nullable: true }) collections: Array | undefined = undefined; diff --git a/src/graphql/entities/nft/nft.input.ts b/src/graphql/entities/nft/nft.input.ts index 9d431fe76..7c2e27ab0 100644 --- a/src/graphql/entities/nft/nft.input.ts +++ b/src/graphql/entities/nft/nft.input.ts @@ -15,8 +15,8 @@ export class GetNftsCountInput { @Field(() => [ID], { name: "identifiers", description: "NFT comma-separated identifiers list to retrieve for the given result set.", nullable: true }) identifiers: Array | undefined = undefined; - @Field(() => NftType, { name: "type", description: "NFT type to retrieve for the given result set.", nullable: true }) - type: NftType | undefined = undefined; + @Field(() => [NftType], { name: "type", description: "NFT type to retrieve for the given result set.", nullable: true }) + type: Array | undefined = undefined; @Field(() => ID, { name: "collection", description: "Collection identifier for the given result set.", nullable: true }) collection: string | undefined = ""; diff --git a/src/graphql/schema/schema.gql b/src/graphql/schema/schema.gql index 31910231f..16071b96d 100644 --- a/src/graphql/schema/schema.gql +++ b/src/graphql/schema/schema.gql @@ -163,15 +163,6 @@ type AccountDetailed { """Code hash for the given detailed account.""" codeHash: String - """Contracts for the given detailed account.""" - contractAccount( - """Input to retrieve the given contracts for.""" - input: GetFromAndSizeInput! - ): [DeployedContract!] - - """Contracts count for the given detailed account.""" - contractAccountCount: Float! - """ Summarizes all delegation positions with staking providers, together with unDelegation positions for the givven detailed account. """ @@ -180,12 +171,21 @@ type AccountDetailed { """Returns staking information related to the legacy delegation pool.""" delegationLegacy: AccountDelegationLegacy! + """Contracts count for the given detailed account.""" + deployAccountCount: Float! + """DeployTxHash for the given detailed account.""" deployTxHash: String """Deployment timestamp for the given detailed account.""" deployedAt: Float + """Deploys for the given detailed account.""" + deploysAccount( + """Input to retrieve the given deploys for.""" + input: GetFromAndSizeInput! + ): [DeployedContract!] + """Developer reward for the given detailed account.""" developerReward: String! @@ -1091,7 +1091,7 @@ input GetNftsAccountInput { tags: [String!] """NFT type to retrieve for the given result set.""" - type: NftType + type: [NftType!] """With supply to retrieve for the given result set.""" withSupply: Boolean @@ -1138,7 +1138,7 @@ input GetNftsCountInput { tags: [String!] """NFT type to retrieve for the given result set.""" - type: NftType + type: [NftType!] } """Input to retrieve the given NFTs for.""" @@ -1188,7 +1188,7 @@ input GetNftsInput { tags: [String!] """NFT type to retrieve for the given result set.""" - type: NftType + type: [NftType!] """With owner to retrieve for the given result set.""" withOwner: Boolean @@ -2106,6 +2106,9 @@ type MexPair { """Mex pair trades count.""" tradesCount: Float + """Mex pair trades count 24h.""" + tradesCount24h: Float + """Mex pair type details.""" type: MexPairType! @@ -2804,8 +2807,17 @@ enum NftSubType { """Dynamic semi-fungible NFT type.""" DynamicSemiFungibleESDT + """Meta ESDT NFT type.""" + MetaESDT + + """Non-fungible ESDT NFT type.""" + NonFungibleESDT + """Non-fungible ESDT v2 NFT type.""" NonFungibleESDTv2 + + """Semi-fungible ESDT NFT type.""" + SemiFungibleESDT } """NFT type.""" @@ -3536,6 +3548,9 @@ type SmartContractResult { """Sender assets for the given smart contract result.""" senderAssets: AccountAssets + """Result status""" + status: String + """Timestamp for the given smart contract result.""" timestamp: Float! @@ -4060,6 +4075,9 @@ type Transaction { """The username of the receiver for the given transaction.""" receiverUsername: String! + """Relayer address for the given transaction.""" + relayer: String + """Round for the given transaction.""" round: Float @@ -4198,6 +4216,9 @@ type TransactionDetailed { """Relayed transaction version.""" relayedVersion: String + """Relayer address for the given transaction.""" + relayer: String + """Smart contract results for the given detailed transaction.""" results: [SmartContractResult!] @@ -4432,6 +4453,9 @@ enum TransactionStatus { """Transaction type object type.""" enum TransactionType { + """InnerTransaction type.""" + InnerTransaction + """Reward type.""" Reward diff --git a/src/main.ts b/src/main.ts index 9f148d904..f2feeeb01 100644 --- a/src/main.ts +++ b/src/main.ts @@ -228,12 +228,14 @@ async function configurePublicApp(publicApp: NestExpressApplication, apiConfigSe globalInterceptors.push(new LoggingInterceptor(metricsService)); const getUseRequestCachingFlag = await settingsService.getUseRequestCachingFlag(); + const cacheDuration = apiConfigService.getCacheDuration(); if (getUseRequestCachingFlag) { const cachingInterceptor = new CachingInterceptor( cachingService, // @ts-ignore httpAdapterHostService, metricsService, + cacheDuration ); // @ts-ignore