From 36dd6bc607925d2855825b5cf95ed1bc691ad682 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu <51945539+bogdan-rosianu@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:20:51 +0200 Subject: [PATCH 01/20] Update load-tests.yml --- .github/workflows/load-tests.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/load-tests.yml b/.github/workflows/load-tests.yml index 4bda65fd4..5f14d7355 100644 --- a/.github/workflows/load-tests.yml +++ b/.github/workflows/load-tests.yml @@ -1,8 +1,6 @@ name: Load Tests on: - push: - branches: [main, development] pull_request: branches: [main, development] From 3f1bb7f90e05beeb1334c0850fec9e8df7f08ec9 Mon Sep 17 00:00:00 2001 From: Rebegea Dragos-Alexandru <42241923+dragos-rebegea@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:30:39 +0200 Subject: [PATCH 02/20] Multi transfer EGLD-000000 support (#1401) * EGLD-000000: add EGLD-000000 support * EGLD-000000: add EGLD-000000 support * filter out egld-000000 from all tokens response + tests fixes * refacotring --------- Co-authored-by: bogdan-rosianu --- src/endpoints/tokens/token.controller.ts | 4 ++-- src/endpoints/tokens/token.service.ts | 19 ++++++++++++++++++- src/test/unit/services/tokens.spec.ts | 22 +++++++++++++++++++++- 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index e3d1e1854..864a8576e 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -341,7 +341,7 @@ export class TokenController { } @Get("/tokens/:identifier/roles/:address") - @ApiOperation({ summary: 'Token address roles', description: 'Returns roles detalils for a specific address of a given token', deprecated: true }) + @ApiOperation({ summary: 'Token address roles', description: 'Returns roles details for a specific address of a given token', deprecated: true }) @ApiOkResponse({ type: TokenRoles }) @ApiNotFoundResponse({ description: 'Token not found' }) async getTokenRolesForAddress( @@ -435,7 +435,7 @@ export class TokenController { } @Get("/tokens/:identifier/transfers/count") - @ApiOperation({ summary: 'Account transfer count', description: 'Return total count of tranfers triggerred by a user account (type = Transaction), as well as transfers triggerred by smart contracts (type = SmartContractResult)' }) + @ApiOperation({ summary: 'Account transfer count', description: 'Return total count of transfers triggerred by a user account (type = Transaction), as well as transfers triggerred by smart contracts (type = SmartContractResult)' }) @ApiOkResponse({ type: Number }) @ApiQuery({ name: 'sender', description: 'Address of the transfer sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) diff --git a/src/endpoints/tokens/token.service.ts b/src/endpoints/tokens/token.service.ts index 0f7782fc8..3ceb5183a 100644 --- a/src/endpoints/tokens/token.service.ts +++ b/src/endpoints/tokens/token.service.ts @@ -48,6 +48,7 @@ import { NftSubType } from "../nfts/entities/nft.sub.type"; export class TokenService { private readonly logger = new OriginLogger(TokenService.name); private readonly nftSubTypes = [NftSubType.DynamicNonFungibleESDT, NftSubType.DynamicMetaESDT, NftSubType.NonFungibleESDTv2, NftSubType.DynamicSemiFungibleESDT]; + private readonly egldIdentifierInMultiTransfer = 'EGLD-000000'; constructor( private readonly esdtService: EsdtService, @@ -126,7 +127,9 @@ export class TokenService { this.applyTickerFromAssets(token); } - return tokens.map(item => ApiUtils.mergeObjects(new TokenDetailed(), item)); + return tokens + .map(item => ApiUtils.mergeObjects(new TokenDetailed(), item)) + .filter(t => t.identifier !== this.egldIdentifierInMultiTransfer); } applyTickerFromAssets(token: Token) { @@ -838,6 +841,20 @@ export class TokenService { token => token.transactions ?? 0, ); + const egldToken = new TokenDetailed({ + identifier: this.egldIdentifierInMultiTransfer, + name: 'EGLD', + type: TokenType.FungibleESDT, + assets: await this.assetsService.getTokenAssets(this.egldIdentifierInMultiTransfer), + decimals: 18, + isLowLiquidity: false, + price: await this.dataApiService.getEgldPrice(), + supply: '0', + circulatingSupply: '0', + marketCap: 0, + }); + tokens = [...tokens, egldToken]; + return tokens; } diff --git a/src/test/unit/services/tokens.spec.ts b/src/test/unit/services/tokens.spec.ts index 222e5ae15..f025eaddb 100644 --- a/src/test/unit/services/tokens.spec.ts +++ b/src/test/unit/services/tokens.spec.ts @@ -134,6 +134,7 @@ describe('Token Service', () => { provide: DataApiService, useValue: { getEsdtTokenPrice: jest.fn(), + getEgldPrice: jest.fn(), }, }, { @@ -690,6 +691,7 @@ describe('Token Service', () => { jest.spyOn(tokenService as any, 'applyMexPairTradesCount').mockImplementation(() => Promise.resolve()); jest.spyOn(cacheService as any, 'batchApplyAll').mockImplementation(() => Promise.resolve()); jest.spyOn(dataApiService, 'getEsdtTokenPrice').mockResolvedValue(100); + jest.spyOn(dataApiService, 'getEgldPrice').mockResolvedValue(100); jest.spyOn(tokenService as any, 'fetchTokenDataFromUrl').mockResolvedValue(100); jest.spyOn(esdtService, 'getTokenSupply').mockResolvedValue(mockTokenSupply as EsdtSupply); @@ -710,7 +712,7 @@ describe('Token Service', () => { expect(assetsService.getTokenAssets).toHaveBeenCalledWith(mockToken.identifier); mockToken.name = mockTokenAssets.name; }); - expect(assetsService.getTokenAssets).toHaveBeenCalledTimes(mockTokens.length); + expect(assetsService.getTokenAssets).toHaveBeenCalledTimes(mockTokens.length + 1); // add 1 for EGLD-000000 expect((collectionService as any).getNftCollections).toHaveBeenCalledWith(expect.anything(), { type: [TokenType.MetaESDT] }); @@ -763,6 +765,24 @@ describe('Token Service', () => { token => token.isLowLiquidity ? 0 : (token.marketCap ?? 0), token => token.transactions ?? 0, ); + + mockTokens.push(new TokenDetailed({ + identifier: 'EGLD-000000', + name: 'EGLD', + canPause: false, + canUpgrade: false, + canWipe: false, + price: 100, + decimals: 18, + isLowLiquidity: false, + marketCap: 0, + circulatingSupply: '0', + supply: '0', + assets: { + name: 'mockName', + } as TokenAssets, + })); + expect(result).toEqual(mockTokens); }); }); From 866a0a1efaeb6f4713fa2eaee017159817a4ac83 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 9 Dec 2024 12:06:41 +0200 Subject: [PATCH 03/20] API-334: add option to include relayed sc results --- .../indexer/elastic/elastic.indexer.helper.ts | 26 ++++++++++++++----- src/common/indexer/elastic/script.query.ts | 14 ++++++++++ .../entities/transaction.filter.ts | 1 + .../transactions/transaction.controller.ts | 9 ++++++- 4 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 src/common/indexer/elastic/script.query.ts diff --git a/src/common/indexer/elastic/elastic.indexer.helper.ts b/src/common/indexer/elastic/elastic.indexer.helper.ts index be10ff0a9..b9165a6b5 100644 --- a/src/common/indexer/elastic/elastic.indexer.helper.ts +++ b/src/common/indexer/elastic/elastic.indexer.helper.ts @@ -18,6 +18,7 @@ import { SmartContractResultFilter } from "src/endpoints/sc-results/entities/sma import { ApplicationFilter } from "src/endpoints/applications/entities/application.filter"; import { NftType } from "../entities/nft.type"; import { EventsFilter } from "src/endpoints/events/entities/events.filter"; +import { ScriptQuery } from "./script.query"; @Injectable() export class ElasticIndexerHelper { @@ -93,7 +94,7 @@ export class ElasticIndexerHelper { QueryType.Nested('roles', [new MatchQuery('roles.ESDTRoleNFTUpdateAttributes', address)]), QueryType.Nested('roles', [new MatchQuery('roles.ESDTRoleNFTAddURI', address)]), QueryType.Nested('roles', [new MatchQuery('roles.ESDTTransferRole', address)]), - ] + ], )); } @@ -253,7 +254,7 @@ export class ElasticIndexerHelper { QueryType.Should([ QueryType.Match('nft_scamInfoType', 'scam'), QueryType.Match('nft_scamInfoType', 'potentialScam'), - ]) + ]), ); } @@ -429,7 +430,7 @@ export class ElasticIndexerHelper { [ QueryType.Match('currentOwner', address), ...rolesConditions, - ] + ], )) .withMustMatchCondition('token', filter.identifier) .withMustMatchCondition('currentOwner', filter.owner); @@ -507,9 +508,22 @@ export class ElasticIndexerHelper { } public buildTransactionFilterQuery(filter: TransactionFilter, address?: string): ElasticQuery { - let elasticQuery = ElasticQuery.create() - .withMustMatchCondition('type', 'normal') - .withMustMatchCondition('senderShard', filter.senderShard) + let elasticQuery = ElasticQuery.create(); + + if (!filter.withRelayedScresults) { + elasticQuery = elasticQuery.withMustMatchCondition('type', 'normal'); + } else { + elasticQuery = elasticQuery.withShouldCondition([ + QueryType.Match('type', 'normal'), + QueryType.Must([ + QueryType.Exists('relayerAddr'), + QueryType.Match('type', 'unsigned'), + new ScriptQuery(`doc['originalTxHash'].size() > 0 && doc['prevTxHash'].size() > 0 && doc['originalTxHash'].value == doc['prevTxHash'].value`), + ]), + ]); + } + + elasticQuery = elasticQuery.withMustMatchCondition('senderShard', filter.senderShard) .withMustMatchCondition('receiverShard', filter.receiverShard) .withMustMatchCondition('miniBlockHash', filter.miniBlockHash) .withMustMultiShouldCondition(filter.hashes, hash => QueryType.Match('_id', hash)) diff --git a/src/common/indexer/elastic/script.query.ts b/src/common/indexer/elastic/script.query.ts new file mode 100644 index 000000000..fa0b75ac3 --- /dev/null +++ b/src/common/indexer/elastic/script.query.ts @@ -0,0 +1,14 @@ +import { AbstractQuery } from "@multiversx/sdk-nestjs-elastic"; + +// TODO: remove this and use ScriptQuery from sdk-nestjs when PR #247 is merged +export class ScriptQuery extends AbstractQuery { + constructor( + private readonly source: string | undefined, + ) { + super(); + } + + getQuery(): any { + return { script: { script: { source: this.source, lang: 'painless' } } }; + } +} diff --git a/src/endpoints/transactions/entities/transaction.filter.ts b/src/endpoints/transactions/entities/transaction.filter.ts index b39779eb0..094f77eb4 100644 --- a/src/endpoints/transactions/entities/transaction.filter.ts +++ b/src/endpoints/transactions/entities/transaction.filter.ts @@ -29,4 +29,5 @@ export class TransactionFilter { isRelayed?: boolean; relayer?: string; round?: number; + withRelayedScresults?: boolean; } diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index 4e2c415cf..21cced481 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -49,6 +49,7 @@ export class TransactionController { @ApiQuery({ name: 'withBlockInfo', description: 'Returns sender / receiver block details', required: false, type: Boolean }) @ApiQuery({ name: 'isRelayed', description: 'Returns relayed transactions details', required: false, type: Boolean }) @ApiQuery({ name: 'withActionTransferValue', description: 'Returns value in USD and EGLD for transferred tokens within the action attribute', required: false }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) getTransactions( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @@ -75,6 +76,7 @@ export class TransactionController { @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername, withBlockInfo, withActionTransferValue }); @@ -94,6 +96,7 @@ export class TransactionController { order, isRelayed, round, + withRelayedScresults: withRelayedScresults, }), new QueryPagination({ from, size }), options, @@ -119,6 +122,7 @@ export class TransactionController { @ApiQuery({ name: 'after', description: 'After timestamp', required: false }) @ApiQuery({ name: 'round', description: 'Round number', required: false }) @ApiQuery({ name: 'isRelayed', description: 'Returns relayed transactions details', required: false, type: Boolean }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) getTransactionCount( @Query('sender', ParseAddressAndMetachainPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @@ -134,6 +138,7 @@ export class TransactionController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ): Promise { return this.transactionService.getTransactionCount(new TransactionFilter({ sender, @@ -150,6 +155,7 @@ export class TransactionController { condition, isRelayed, round, + withRelayedScresults: withRelayedScresults, })); } @@ -170,6 +176,7 @@ export class TransactionController { @Query('after', ParseIntPipe) after?: number, @Query('round', new ParseIntPipe) round?: number, @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ): Promise { return this.transactionService.getTransactionCount(new TransactionFilter({ sender, @@ -186,6 +193,7 @@ export class TransactionController { condition, isRelayed, round, + withRelayedScresults: withRelayedScresults, })); } @@ -249,4 +257,3 @@ export class TransactionController { return await this.transactionService.decodeTransaction(transaction); } } - From 5cc61895a6c01e21ffe9d5faa6674a2b91e38604 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 9 Dec 2024 13:22:26 +0200 Subject: [PATCH 04/20] API-334: use new filter for multiples endpoints --- src/endpoints/accounts/account.controller.ts | 12 ++++++++++-- src/endpoints/collections/collection.controller.ts | 13 +++++++++++-- src/endpoints/nfts/nft.controller.ts | 13 +++++++++++-- src/endpoints/tokens/token.controller.ts | 14 ++++++++++++-- .../transactions/entities/transaction.filter.ts | 7 +++++++ .../transactions/transaction.controller.ts | 6 ++++-- 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 9af332110..43ee286ca 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -865,6 +865,7 @@ export class AccountController { @ApiQuery({ name: 'senderOrReceiver', description: 'One address that current address interacted with', required: false }) @ApiQuery({ name: 'isRelayed', description: 'Returns isRelayed transactions details', required: false, type: Boolean }) @ApiQuery({ name: 'withActionTransferValue', description: 'Returns value in USD and EGLD for transferred tokens within the action attribute', required: false }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getAccountTransactions( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -892,10 +893,11 @@ export class AccountController { @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername, withBlockInfo, withActionTransferValue }); - return await this.transactionService.getTransactions(new TransactionFilter({ + const transactionFilter = new TransactionFilter({ sender, receivers: receiver, token, @@ -911,7 +913,10 @@ export class AccountController { senderOrReceiver, isRelayed, round, - }), new QueryPagination({ from, size }), options, address, fields); + withRelayedScresults, + }); + transactionFilter.validate(size); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options, address, fields); } @Get("/accounts/:address/transactions/count") @@ -931,6 +936,7 @@ export class AccountController { @ApiQuery({ name: 'round', description: 'Round number', required: false }) @ApiQuery({ name: 'senderOrReceiver', description: 'One address that current address interacted with', required: false }) @ApiQuery({ name: 'isRelayed', description: 'Returns isRelayed transactions details', required: false, type: Boolean }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getAccountTransactionsCount( @Param('address', ParseAddressPipe) address: string, @Query('sender', ParseAddressPipe) sender?: string, @@ -947,6 +953,7 @@ export class AccountController { @Query('round', ParseIntPipe) round?: number, @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ): Promise { return await this.transactionService.getTransactionCount(new TransactionFilter({ @@ -964,6 +971,7 @@ export class AccountController { senderOrReceiver, isRelayed, round, + withRelayedScresults, }), address); } diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index 039ccd434..80a1d0e2d 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -336,6 +336,7 @@ export class CollectionController { @ApiQuery({ name: 'withLogs', description: 'Return logs for transactions', required: false, type: Boolean }) @ApiQuery({ name: 'withScamInfo', description: 'Returns scam information', required: false, type: Boolean }) @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transactions', required: false, type: Boolean }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getCollectionTransactions( @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -357,6 +358,7 @@ export class CollectionController { @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername }); @@ -365,7 +367,7 @@ export class CollectionController { throw new HttpException('Collection not found', HttpStatus.NOT_FOUND); } - return await this.transactionService.getTransactions(new TransactionFilter({ + const transactionFilter = new TransactionFilter({ sender, receivers: receiver, token: identifier, @@ -379,7 +381,11 @@ export class CollectionController { after, order, round, - }), new QueryPagination({ from, size }), options); + withRelayedScresults, + }); + transactionFilter.validate(size); + + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); } @Get("/collections/:collection/transactions/count") @@ -396,6 +402,7 @@ export class CollectionController { @ApiQuery({ name: 'before', description: 'Before timestamp', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp', required: false }) @ApiQuery({ name: 'round', description: 'Filter by round number', required: false }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getCollectionTransactionsCount( @Param('collection', ParseCollectionPipe) identifier: string, @Query('sender', ParseAddressPipe) sender?: string, @@ -408,6 +415,7 @@ export class CollectionController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const isCollection = await this.collectionService.isCollection(identifier); if (!isCollection) { @@ -426,6 +434,7 @@ export class CollectionController { before, after, round, + withRelayedScresults, })); } diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index 539d1797e..f86c8ac08 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -297,6 +297,7 @@ export class NftController { @ApiQuery({ name: 'withLogs', description: 'Return logs for transactions', required: false, type: Boolean }) @ApiQuery({ name: 'withScamInfo', description: 'Returns scam information', required: false, type: Boolean }) @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transactions', required: false, type: Boolean }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getNftTransactions( @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -317,10 +318,11 @@ export class NftController { @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername }); - return await this.transactionService.getTransactions(new TransactionFilter({ + const transactionFilter = new TransactionFilter({ sender, receivers: receiver, token: identifier, @@ -333,7 +335,11 @@ export class NftController { before, after, order, - }), new QueryPagination({ from, size }), options); + withRelayedScresults, + }); + transactionFilter.validate(size); + + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); } @Get("/nfts/:identifier/transactions/count") @@ -349,6 +355,7 @@ export class NftController { @ApiQuery({ name: 'status', description: 'Status of the transaction (success / pending / invalid / fail)', required: false, enum: TransactionStatus }) @ApiQuery({ name: 'before', description: 'Before timestamp', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp', required: false }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getNftTransactionsCount( @Param('identifier', ParseNftPipe) identifier: string, @Query('sender', ParseAddressPipe) sender?: string, @@ -360,6 +367,7 @@ export class NftController { @Query('status', new ParseEnumPipe(TransactionStatus)) status?: TransactionStatus, @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { return await this.transactionService.getTransactionCount(new TransactionFilter({ @@ -373,6 +381,7 @@ export class NftController { status, before, after, + withRelayedScresults, })); } diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index 864a8576e..61343c0cd 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -219,6 +219,7 @@ export class TokenController { @ApiQuery({ name: 'withUsername', description: 'Integrates username in assets for all addresses present in the transactions', required: false, type: Boolean }) @ApiQuery({ name: 'withBlockInfo', description: 'Returns sender / receiver block details', required: false, type: Boolean }) @ApiQuery({ name: 'withActionTransferValue', description: 'Returns value in USD and EGLD for transferred tokens within the action attribute', required: false }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getTokenTransactions( @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -243,6 +244,7 @@ export class TokenController { @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername, withBlockInfo, withActionTransferValue }); @@ -251,7 +253,7 @@ export class TokenController { throw new NotFoundException('Token not found'); } - return await this.transactionService.getTransactions(new TransactionFilter({ + const transactionFilter = new TransactionFilter({ sender, receivers: receiver, token: identifier, @@ -265,7 +267,12 @@ export class TokenController { after, order, round, - }), + withRelayedScresults, + }); + transactionFilter.validate(size); + + return await this.transactionService.getTransactions( + transactionFilter, new QueryPagination({ from, size }), options, undefined, @@ -287,6 +294,7 @@ export class TokenController { @ApiQuery({ name: 'before', description: 'Before timestamp', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp', required: false }) @ApiQuery({ name: 'round', description: 'Filter by round number', required: false }) + @ApiQuery({ name: 'withRelayedScresults', description: 'If set to true, will include smart contract results that resemble relayed transactions', required: false, type: Boolean }) async getTokenTransactionsCount( @Param('identifier', ParseTokenPipe) identifier: string, @Query('sender', ParseAddressPipe) sender?: string, @@ -299,6 +307,7 @@ export class TokenController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, + @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const isToken = await this.tokenService.isToken(identifier); if (!isToken) { @@ -317,6 +326,7 @@ export class TokenController { before, after, round, + withRelayedScresults, })); } diff --git a/src/endpoints/transactions/entities/transaction.filter.ts b/src/endpoints/transactions/entities/transaction.filter.ts index 094f77eb4..ff09aec94 100644 --- a/src/endpoints/transactions/entities/transaction.filter.ts +++ b/src/endpoints/transactions/entities/transaction.filter.ts @@ -2,6 +2,7 @@ import { QueryConditionOptions } from "@multiversx/sdk-nestjs-elastic"; import { SortOrder } from "src/common/entities/sort.order"; import { TransactionStatus } from "./transaction.status"; import { TransactionType } from "./transaction.type"; +import { BadRequestException } from "@nestjs/common"; export class TransactionFilter { constructor(init?: Partial) { @@ -30,4 +31,10 @@ export class TransactionFilter { relayer?: string; round?: number; withRelayedScresults?: boolean; + + validate(size: number) { + if (this.withRelayedScresults && size > 50) { + throw new BadRequestException('Size must be less than or equal to 50 when withRelayedScresults is set'); + } + } } diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index 21cced481..eed01139d 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -80,7 +80,7 @@ export class TransactionController { ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername, withBlockInfo, withActionTransferValue }); - return this.transactionService.getTransactions(new TransactionFilter({ + const transactionFilter = new TransactionFilter({ sender, receivers: receiver, token, @@ -97,7 +97,9 @@ export class TransactionController { isRelayed, round, withRelayedScresults: withRelayedScresults, - }), + }); + transactionFilter.validate(size); + return this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options, undefined, From 5d3afca19a283d77a845ed9641ea2a02e81b86a3 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 9 Dec 2024 13:28:53 +0200 Subject: [PATCH 05/20] API-334: fix --- src/endpoints/transactions/transaction.utils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/endpoints/transactions/transaction.utils.ts b/src/endpoints/transactions/transaction.utils.ts index cff9e8332..071c6b683 100644 --- a/src/endpoints/transactions/transaction.utils.ts +++ b/src/endpoints/transactions/transaction.utils.ts @@ -28,6 +28,7 @@ export class TransactionUtils { sender: filter.sender, receivers: filter.receivers, condition: QueryConditionOptions.should, + validate: (_size: number) => {}, }; return JSON.stringify(filter) === JSON.stringify(filterToCompareWith); From f349f359b375a6a63681fa3f6967607803946ebc Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 9 Dec 2024 13:33:49 +0200 Subject: [PATCH 06/20] tests fixes --- src/test/unit/controllers/collections.controller.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/unit/controllers/collections.controller.spec.ts b/src/test/unit/controllers/collections.controller.spec.ts index 2ffef1f26..caf558c89 100644 --- a/src/test/unit/controllers/collections.controller.spec.ts +++ b/src/test/unit/controllers/collections.controller.spec.ts @@ -1286,7 +1286,7 @@ function createNftFilter(options: NftFilter = {}) { }); } -function createTransactionFilter(options: TransactionFilter = {}) { +function createTransactionFilter(options: any) { return new TransactionFilter({ sender: options.sender, receivers: options.receivers, @@ -1299,5 +1299,6 @@ function createTransactionFilter(options: TransactionFilter = {}) { before: options.before, after: options.after, functions: options.functions, + validate: (_size: number) => {}, }); } From 81e0672a8a9723013b6589041006ce778304b12a Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 9 Dec 2024 14:22:01 +0200 Subject: [PATCH 07/20] change validate function --- src/endpoints/accounts/account.controller.ts | 2 +- src/endpoints/collections/collection.controller.ts | 2 +- src/endpoints/nfts/nft.controller.ts | 2 +- src/endpoints/tokens/token.controller.ts | 2 +- .../transactions/entities/transaction.filter.ts | 12 ++++++------ src/endpoints/transactions/transaction.controller.ts | 2 +- src/endpoints/transactions/transaction.utils.ts | 1 - .../unit/controllers/collections.controller.spec.ts | 3 +-- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 43ee286ca..94b274a78 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -915,7 +915,7 @@ export class AccountController { round, withRelayedScresults, }); - transactionFilter.validate(size); + TransactionFilter.validate(transactionFilter, size); return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options, address, fields); } diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index 80a1d0e2d..2e48bf386 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -383,7 +383,7 @@ export class CollectionController { round, withRelayedScresults, }); - transactionFilter.validate(size); + TransactionFilter.validate(transactionFilter, size); return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); } diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index f86c8ac08..c30cfacca 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -337,7 +337,7 @@ export class NftController { order, withRelayedScresults, }); - transactionFilter.validate(size); + TransactionFilter.validate(transactionFilter, size); return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); } diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index 61343c0cd..95529bb63 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -269,7 +269,7 @@ export class TokenController { round, withRelayedScresults, }); - transactionFilter.validate(size); + TransactionFilter.validate(transactionFilter, size); return await this.transactionService.getTransactions( transactionFilter, diff --git a/src/endpoints/transactions/entities/transaction.filter.ts b/src/endpoints/transactions/entities/transaction.filter.ts index ff09aec94..106110eae 100644 --- a/src/endpoints/transactions/entities/transaction.filter.ts +++ b/src/endpoints/transactions/entities/transaction.filter.ts @@ -5,10 +5,6 @@ import { TransactionType } from "./transaction.type"; import { BadRequestException } from "@nestjs/common"; export class TransactionFilter { - constructor(init?: Partial) { - Object.assign(this, init); - } - address?: string; sender?: string; senders?: string[] = []; @@ -32,8 +28,12 @@ export class TransactionFilter { round?: number; withRelayedScresults?: boolean; - validate(size: number) { - if (this.withRelayedScresults && size > 50) { + constructor(init?: Partial) { + Object.assign(this, init); + } + + static validate(filter: TransactionFilter, size: number) { + if (filter.withRelayedScresults && size > 50) { throw new BadRequestException('Size must be less than or equal to 50 when withRelayedScresults is set'); } } diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index eed01139d..3f36b5053 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -98,7 +98,7 @@ export class TransactionController { round, withRelayedScresults: withRelayedScresults, }); - transactionFilter.validate(size); + TransactionFilter.validate(transactionFilter, size); return this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options, diff --git a/src/endpoints/transactions/transaction.utils.ts b/src/endpoints/transactions/transaction.utils.ts index 071c6b683..cff9e8332 100644 --- a/src/endpoints/transactions/transaction.utils.ts +++ b/src/endpoints/transactions/transaction.utils.ts @@ -28,7 +28,6 @@ export class TransactionUtils { sender: filter.sender, receivers: filter.receivers, condition: QueryConditionOptions.should, - validate: (_size: number) => {}, }; return JSON.stringify(filter) === JSON.stringify(filterToCompareWith); diff --git a/src/test/unit/controllers/collections.controller.spec.ts b/src/test/unit/controllers/collections.controller.spec.ts index caf558c89..2ffef1f26 100644 --- a/src/test/unit/controllers/collections.controller.spec.ts +++ b/src/test/unit/controllers/collections.controller.spec.ts @@ -1286,7 +1286,7 @@ function createNftFilter(options: NftFilter = {}) { }); } -function createTransactionFilter(options: any) { +function createTransactionFilter(options: TransactionFilter = {}) { return new TransactionFilter({ sender: options.sender, receivers: options.receivers, @@ -1299,6 +1299,5 @@ function createTransactionFilter(options: any) { before: options.before, after: options.after, functions: options.functions, - validate: (_size: number) => {}, }); } From e6ae65b95a1165046e2aad3295a1f6a8ff9a8813 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Mon, 9 Dec 2024 15:19:02 +0200 Subject: [PATCH 08/20] API-339: include transactions marked as canBeIgnored --- src/common/indexer/elastic/elastic.indexer.helper.ts | 10 +++++++--- src/endpoints/accounts/account.controller.ts | 8 ++++++++ .../transactions/entities/transaction.filter.ts | 1 + src/endpoints/transfers/transfer.controller.ts | 8 ++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/common/indexer/elastic/elastic.indexer.helper.ts b/src/common/indexer/elastic/elastic.indexer.helper.ts index be10ff0a9..50c9553d2 100644 --- a/src/common/indexer/elastic/elastic.indexer.helper.ts +++ b/src/common/indexer/elastic/elastic.indexer.helper.ts @@ -309,12 +309,16 @@ export class ElasticIndexerHelper { smartContractResultConditions.push(QueryType.Match('sender', filter.address)); } + let mustNotQueries: AbstractQuery[] = [ + QueryType.Exists('canBeIgnored'), + ]; + if (filter.withRefunds) { + mustNotQueries = []; + } elasticQuery = elasticQuery.withCondition(QueryConditionOptions.should, QueryType.Must([ QueryType.Match('type', 'unsigned'), QueryType.Should(smartContractResultConditions), - ], [ - QueryType.Exists('canBeIgnored'), - ])) + ], mustNotQueries)) .withCondition(QueryConditionOptions.should, QueryType.Must([ QueryType.Should([QueryType.Match('type', 'normal')]), QueryType.Should([ diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 9af332110..bb8fbb1eb 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -995,6 +995,7 @@ export class AccountController { @ApiQuery({ name: 'withLogs', description: 'Return logs for transfers. When "withLogs" parameter is applied, complexity estimation is 200', required: false }) @ApiQuery({ name: 'withOperations', description: 'Return operations for transfers. When "withOperations" parameter is applied, complexity estimation is 200', required: false }) @ApiQuery({ name: 'withActionTransferValue', description: 'Returns value in USD and EGLD for transferred tokens within the action attribute', required: false }) + @ApiQuery({ name: 'withRefunds', description: 'Include refund transactions', required: false }) async getAccountTransfers( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @@ -1021,6 +1022,7 @@ export class AccountController { @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, + @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { const options = TransactionQueryOptions.applyDefaultOptions( size, { withScamInfo, withUsername, withBlockInfo, withOperations, withLogs, withActionTransferValue }); @@ -1042,6 +1044,7 @@ export class AccountController { senderOrReceiver, relayer, round, + withRefunds, }), new QueryPagination({ from, size }), options, @@ -1065,6 +1068,7 @@ export class AccountController { @ApiQuery({ name: 'after', description: 'After timestamp', required: false }) @ApiQuery({ name: 'round', description: 'Round number', required: false }) @ApiQuery({ name: 'senderOrReceiver', description: 'One address that current address interacted with', required: false }) + @ApiQuery({ name: 'withRefunds', description: 'Include refund transactions', required: false }) async getAccountTransfersCount( @Param('address', ParseAddressPipe) address: string, @Query('sender', ParseAddressArrayPipe) sender?: string[], @@ -1080,6 +1084,7 @@ export class AccountController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, + @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { return await this.transferService.getTransfersCount(new TransactionFilter({ address, @@ -1096,6 +1101,7 @@ export class AccountController { after, senderOrReceiver, round, + withRefunds, })); } @@ -1116,6 +1122,7 @@ export class AccountController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, + @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { return await this.transferService.getTransfersCount(new TransactionFilter({ address, @@ -1132,6 +1139,7 @@ export class AccountController { after, senderOrReceiver, round, + withRefunds, })); } diff --git a/src/endpoints/transactions/entities/transaction.filter.ts b/src/endpoints/transactions/entities/transaction.filter.ts index b39779eb0..821982ab3 100644 --- a/src/endpoints/transactions/entities/transaction.filter.ts +++ b/src/endpoints/transactions/entities/transaction.filter.ts @@ -29,4 +29,5 @@ export class TransactionFilter { isRelayed?: boolean; relayer?: string; round?: number; + withRefunds?: boolean; } diff --git a/src/endpoints/transfers/transfer.controller.ts b/src/endpoints/transfers/transfer.controller.ts index 117be703e..8617a0cf7 100644 --- a/src/endpoints/transfers/transfer.controller.ts +++ b/src/endpoints/transfers/transfer.controller.ts @@ -46,6 +46,7 @@ export class TransferController { @ApiQuery({ name: 'withLogs', description: 'Return logs for transfers. When "withLogs" parameter is applied, complexity estimation is 200', required: false }) @ApiQuery({ name: 'withOperations', description: 'Return operations for transfers. When "withOperations" parameter is applied, complexity estimation is 200', required: false }) @ApiQuery({ name: 'withActionTransferValue', description: 'Returns value in USD and EGLD for transferred tokens within the action attribute', required: false }) + @ApiQuery({ name: 'withRefunds', description: 'Include refund transactions', required: false }) async getAccountTransfers( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @@ -71,6 +72,7 @@ export class TransferController { @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, + @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { const options = TransactionQueryOptions.applyDefaultOptions( size, new TransactionQueryOptions({ withScamInfo, withUsername, withBlockInfo, withLogs, withOperations, withActionTransferValue }), @@ -92,6 +94,7 @@ export class TransferController { relayer, isRelayed, round, + withRefunds, }), new QueryPagination({ from, size }), options, @@ -115,6 +118,7 @@ export class TransferController { @ApiQuery({ name: 'after', description: 'After timestamp', required: false }) @ApiQuery({ name: 'round', description: 'Round number', required: false }) @ApiQuery({ name: 'isRelayed', description: 'Returns relayed transactions details', required: false, type: Boolean }) + @ApiQuery({ name: 'withRefunds', description: 'Include refund transactions', required: false }) async getAccountTransfersCount( @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @@ -129,6 +133,7 @@ export class TransferController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { return await this.transferService.getTransfersCount(new TransactionFilter({ senders: sender, @@ -144,6 +149,7 @@ export class TransferController { after, isRelayed, round, + withRefunds, })); } @@ -162,6 +168,7 @@ export class TransferController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, + @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { return await this.transferService.getTransfersCount(new TransactionFilter({ senders: sender, @@ -176,6 +183,7 @@ export class TransferController { before, after, round, + withRefunds, })); } } From 2ce86d9bedac547f04921913c60e8e519496f701 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu Date: Wed, 11 Dec 2024 13:57:26 +0200 Subject: [PATCH 09/20] forward checkSignature param for tx simulation --- src/endpoints/proxy/gateway.proxy.controller.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/endpoints/proxy/gateway.proxy.controller.ts b/src/endpoints/proxy/gateway.proxy.controller.ts index 48f9fb434..3b7e12c37 100644 --- a/src/endpoints/proxy/gateway.proxy.controller.ts +++ b/src/endpoints/proxy/gateway.proxy.controller.ts @@ -6,7 +6,7 @@ import { GatewayService } from "src/common/gateway/gateway.service"; import { Response, Request } from "express"; import { GatewayComponentRequest } from "src/common/gateway/entities/gateway.component.request"; import { PluginService } from "src/common/plugins/plugin.service"; -import { Constants, ParseAddressPipe, ParseBlockHashPipe, ParseBlsHashPipe, ParseIntPipe, ParseTransactionHashPipe } from "@multiversx/sdk-nestjs-common"; +import { Constants, ParseAddressPipe, ParseBlockHashPipe, ParseBlsHashPipe, ParseIntPipe, ParseTransactionHashPipe, ParseBoolPipe } from "@multiversx/sdk-nestjs-common"; import { CacheService, NoCache } from "@multiversx/sdk-nestjs-cache"; import { OriginLogger } from "@multiversx/sdk-nestjs-common"; import { DeepHistoryInterceptor } from "src/interceptors/deep-history.interceptor"; @@ -118,8 +118,12 @@ export class GatewayProxyController { } @Post('/transaction/simulate') - async transactionSimulate(@Body() body: any) { - return await this.gatewayPost('transaction/simulate', GatewayComponentRequest.simulateTransaction, body); + async transactionSimulate(@Query('checkSignature', ParseBoolPipe) checkSignature: boolean, @Body() body: any) { + let url = 'transaction/simulate'; + if (checkSignature !== undefined) { + url += `?checkSignature=${checkSignature}`; + } + return await this.gatewayPost(url, GatewayComponentRequest.simulateTransaction, body); } @Post('/transaction/send-multiple') From aae3354d87c16dac413f6ede37374463cab2daff Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:42:54 +0200 Subject: [PATCH 10/20] Added a check to ensure providersDelegationData is an array (#1420) --- src/endpoints/providers/provider.service.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/endpoints/providers/provider.service.ts b/src/endpoints/providers/provider.service.ts index 7c76bcd6c..0cd9d3396 100644 --- a/src/endpoints/providers/provider.service.ts +++ b/src/endpoints/providers/provider.service.ts @@ -106,13 +106,19 @@ export class ProviderService { const nodesGroupedByProvider: { [key: string]: any[] } = nodes.groupBy(x => x.provider); - const providersDelegationData: DelegationData[] = await this.getDelegationProviders(); + const providersDelegationData = await this.getDelegationProviders(); + if (!Array.isArray(providersDelegationData)) { + return providers; + } providers.forEach((element) => { const providerAddress = element.provider; // Delegation details for provider - const delegationData: DelegationData | undefined = providersDelegationData.find((providerDelegationInfo: any) => providerDelegationInfo !== null && providerAddress === providerDelegationInfo.contract); + const delegationData = providersDelegationData.find((providerDelegationInfo: any) => + providerDelegationInfo !== null && providerAddress === providerDelegationInfo.contract + ); + if (delegationData) { if (delegationData.aprValue) { element.apr = parseFloat(delegationData.aprValue.toFixed(2)); From 5079d6b4b321649926ba44f7a9802d9c9092621a Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Thu, 12 Dec 2024 09:47:58 +0200 Subject: [PATCH 11/20] Add 'addresses' query parameter to filter accounts by a list of addresses (#1374) * Add 'addresses' query parameter to filter accounts by a list of addresses * Add bulk account retrieval method and integrate with account service * Update request type in getAccountsBulk method to use addressesBulk * Add 'addressesBulk' request type to GatewayComponentRequest enum --- .../entities/gateway.component.request.ts | 1 + src/common/gateway/gateway.service.ts | 5 +++++ src/endpoints/accounts/account.controller.ts | 3 +++ src/endpoints/accounts/account.service.ts | 20 +++++++++++++++++-- .../entities/account.query.options.ts | 7 ++++++- 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/common/gateway/entities/gateway.component.request.ts b/src/common/gateway/entities/gateway.component.request.ts index a3821082c..066b06904 100644 --- a/src/common/gateway/entities/gateway.component.request.ts +++ b/src/common/gateway/entities/gateway.component.request.ts @@ -5,6 +5,7 @@ export enum GatewayComponentRequest { networkEconomics = 'networkEconomics', networkTotalStaked = 'networkTotalStaked', addressDetails = 'addressDetails', + addressesBulk = 'addressesBulk', addressEsdt = 'addressEsdt', addressEsdtHistorical = 'addressEsdtHistorical', addressEsdtBalance = 'addressEsdtBalance', diff --git a/src/common/gateway/gateway.service.ts b/src/common/gateway/gateway.service.ts index 6888fbfb3..597ced989 100644 --- a/src/common/gateway/gateway.service.ts +++ b/src/common/gateway/gateway.service.ts @@ -95,6 +95,11 @@ export class GatewayService { return result; } + async getAccountsBulk(addresses: string[]): Promise { + const result = await this.create('address/bulk', GatewayComponentRequest.addressesBulk, addresses); + return result.accounts; + } + async getEsdtSupply(identifier: string): Promise { const result = await this.get(`network/esdt/supply/${identifier}`, GatewayComponentRequest.esdtSupply); return result; diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 2dff4dda6..599cc862d 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -96,6 +96,7 @@ export class AccountController { @ApiQuery({ name: 'excludeTags', description: 'Exclude specific tags from result', required: false }) @ApiQuery({ name: 'hasAssets', description: 'Returns a list of accounts that have assets', required: false }) @ApiQuery({ name: 'search', description: 'Search by account address', required: false }) + @ApiQuery({ name: 'addresses', description: 'A comma-separated list of addresses to filter by', required: false, type: String }) getAccounts( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, @@ -112,10 +113,12 @@ export class AccountController { @Query("excludeTags", new ParseArrayPipe) excludeTags?: string[], @Query("hasAssets", new ParseBoolPipe) hasAssets?: boolean, @Query("search") search?: string, + @Query("addresses", ParseAddressArrayPipe) addresses?: string[], ): Promise { const queryOptions = new AccountQueryOptions( { ownerAddress, + addresses, sort, order, isSmartContract, diff --git a/src/endpoints/accounts/account.service.ts b/src/endpoints/accounts/account.service.ts index 26b3e7bad..b2fcd6462 100644 --- a/src/endpoints/accounts/account.service.ts +++ b/src/endpoints/accounts/account.service.ts @@ -328,6 +328,24 @@ export class AccountService { const verifiedAccounts = await this.cachingService.get(CacheInfo.VerifiedAccounts.key); + if (options.addresses && options.addresses.length > 0) { + const gatewayResponse: any = await this.gatewayService.getAccountsBulk(options.addresses); + const finalAccounts: Record = {}; + + for (const address in gatewayResponse) { + if (gatewayResponse.hasOwnProperty(address)) { + finalAccounts[address] = gatewayResponse[address] as AccountDetailed; + } + } + + for (const account of accounts) { + const gatewayAccount = finalAccounts[account.address]; + if (gatewayAccount) { + account.balance = gatewayAccount.balance; + } + } + } + for (const account of accounts) { account.shard = AddressUtils.computeShard(AddressUtils.bech32Decode(account.address), shardCount); account.assets = assets[account.address]; @@ -357,8 +375,6 @@ export class AccountService { if (verifiedAccounts && verifiedAccounts.includes(account.address)) { account.isVerified = true; } - - } return accounts; diff --git a/src/endpoints/accounts/entities/account.query.options.ts b/src/endpoints/accounts/entities/account.query.options.ts index 35b32bca7..91649d3c1 100644 --- a/src/endpoints/accounts/entities/account.query.options.ts +++ b/src/endpoints/accounts/entities/account.query.options.ts @@ -35,6 +35,10 @@ export class AccountQueryOptions { if (this.withScrCount && size > 25) { throw new BadRequestException('Size must be less than or equal to 25 when withScrCount is set'); } + + if (this.addresses && this.addresses.length > 25) { + throw new BadRequestException('Addresses array must contain 25 or fewer elements'); + } } isSet(): boolean { @@ -48,6 +52,7 @@ export class AccountQueryOptions { this.tags !== undefined || this.excludeTags !== undefined || this.hasAssets !== undefined || - this.search !== undefined; + this.search !== undefined || + this.addresses !== undefined; } } From dfc82d662f7d46f05243d1f9f523b998f1c17171 Mon Sep 17 00:00:00 2001 From: Gutica Stefan <123564494+GuticaStefan@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:07:50 +0200 Subject: [PATCH 12/20] remove new keyword for query parsers without specific parameter (#1373) * remove new keyword for query parsers without specific parameter * remove left new keywords * remove new keyword for ParseArrayPipe s that have no parameter --- src/endpoints/accounts/account.controller.ts | 147 +++++++++--------- .../collections/collection.controller.ts | 100 ++++++------ src/endpoints/nfts/nft.controller.ts | 67 ++++---- src/endpoints/nodes/node.controller.ts | 2 +- .../providers/provider.controller.ts | 4 +- src/endpoints/rounds/round.controller.ts | 12 +- src/endpoints/tokens/token.controller.ts | 28 ++-- .../transactions/transaction.controller.ts | 20 +-- .../transfers/transfer.controller.ts | 14 +- .../usernames/username.controller.ts | 2 +- 10 files changed, 199 insertions(+), 197 deletions(-) diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 599cc862d..e1387bb56 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -102,16 +102,16 @@ export class AccountController { @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, @Query("ownerAddress", ParseAddressPipe) ownerAddress?: string, @Query("name") name?: string, - @Query("tags", new ParseArrayPipe()) tags?: string[], + @Query("tags", ParseArrayPipe) tags?: string[], @Query('sort', new ParseEnumPipe(AccountSort)) sort?: AccountSort, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query("isSmartContract", new ParseBoolPipe) isSmartContract?: boolean, - @Query("withOwnerAssets", new ParseBoolPipe) withOwnerAssets?: boolean, - @Query("withDeployInfo", new ParseBoolPipe) withDeployInfo?: boolean, - @Query("withTxCount", new ParseBoolPipe) withTxCount?: boolean, - @Query("withScrCount", new ParseBoolPipe) withScrCount?: boolean, - @Query("excludeTags", new ParseArrayPipe) excludeTags?: string[], - @Query("hasAssets", new ParseBoolPipe) hasAssets?: boolean, + @Query("isSmartContract", ParseBoolPipe) isSmartContract?: boolean, + @Query("withOwnerAssets", ParseBoolPipe) withOwnerAssets?: boolean, + @Query("withDeployInfo", ParseBoolPipe) withDeployInfo?: boolean, + @Query("withTxCount", ParseBoolPipe) withTxCount?: boolean, + @Query("withScrCount", ParseBoolPipe) withScrCount?: boolean, + @Query("excludeTags", ParseArrayPipe) excludeTags?: string[], + @Query("hasAssets", ParseBoolPipe) hasAssets?: boolean, @Query("search") search?: string, @Query("addresses", ParseAddressArrayPipe) addresses?: string[], ): Promise { @@ -150,11 +150,11 @@ export class AccountController { @ApiQuery({ name: 'hasAssets', description: 'Returns a list of accounts that have assets', required: false }) async getAccountsCount( @Query("ownerAddress", ParseAddressPipe) ownerAddress?: string, - @Query("isSmartContract", new ParseBoolPipe) isSmartContract?: boolean, + @Query("isSmartContract", ParseBoolPipe) isSmartContract?: boolean, @Query("name") name?: string, - @Query("tags", new ParseArrayPipe()) tags?: string[], - @Query("excludeTags", new ParseArrayPipe) excludeTags?: string[], - @Query("hasAssets", new ParseBoolPipe) hasAssets?: boolean, + @Query("tags", ParseArrayPipe) tags?: string[], + @Query("excludeTags", ParseArrayPipe) excludeTags?: string[], + @Query("hasAssets", ParseBoolPipe) hasAssets?: boolean, ): Promise { return await this.accountService.getAccountsCount( new AccountQueryOptions( @@ -172,11 +172,11 @@ export class AccountController { @ApiExcludeEndpoint() async getAccountsCountAlternative( @Query("ownerAddress", ParseAddressPipe) ownerAddress?: string, - @Query("isSmartContract", new ParseBoolPipe) isSmartContract?: boolean, + @Query("isSmartContract", ParseBoolPipe) isSmartContract?: boolean, @Query("name") name?: string, - @Query("tags", new ParseArrayPipe()) tags?: string[], - @Query("excludeTags", new ParseArrayPipe) excludeTags?: string[], - @Query("hasAssets", new ParseBoolPipe) hasAssets?: boolean, + @Query("tags", ParseArrayPipe) tags?: string[], + @Query("excludeTags", ParseArrayPipe) excludeTags?: string[], + @Query("hasAssets", ParseBoolPipe) hasAssets?: boolean, ): Promise { return await this.accountService.getAccountsCount( new AccountQueryOptions( @@ -199,7 +199,7 @@ export class AccountController { @ApiOkResponse({ type: AccountDetailed }) async getAccountDetails( @Param('address', ParseAddressPipe) address: string, - @Query('withGuardianInfo', new ParseBoolPipe) withGuardianInfo?: boolean, + @Query('withGuardianInfo', ParseBoolPipe) withGuardianInfo?: boolean, @Query('fields', ParseArrayPipe) fields?: string[], @Query('timestamp', ParseIntPipe) _timestamp?: number, ): Promise { @@ -262,7 +262,7 @@ export class AccountController { @Query('name') name?: string, @Query('identifier') identifier?: string, @Query('identifiers', ParseArrayPipe) identifiers?: string[], - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, @Query('timestamp', ParseIntPipe) _timestamp?: number, @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], ): Promise { @@ -295,7 +295,7 @@ export class AccountController { @Query('name') name?: string, @Query('identifier') identifier?: string, @Query('identifiers', ParseArrayPipe) identifiers?: string[], - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, @Query('timestamp', ParseIntPipe) _timestamp?: number, @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], ): Promise { @@ -319,7 +319,7 @@ export class AccountController { @Query('name') name?: string, @Query('identifier') identifier?: string, @Query('identifiers', ParseArrayPipe) identifiers?: string[], - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, @Query('timestamp', ParseIntPipe) _timestamp?: number, @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], ): Promise { @@ -375,13 +375,13 @@ export class AccountController { @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('canUpdateAttributes', new ParseBoolPipe) canUpdateAttributes?: boolean, - @Query('canAddUri', new ParseBoolPipe) canAddUri?: boolean, - @Query('canTransferRole', new ParseBoolPipe) canTransferRole?: boolean, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('canCreate', ParseBoolPipe) canCreate?: boolean, + @Query('canBurn', ParseBoolPipe) canBurn?: boolean, + @Query('canAddQuantity', ParseBoolPipe) canAddQuantity?: boolean, + @Query('canUpdateAttributes', ParseBoolPipe) canUpdateAttributes?: boolean, + @Query('canAddUri', ParseBoolPipe) canAddUri?: boolean, + @Query('canTransferRole', ParseBoolPipe) canTransferRole?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionsWithRolesForAddress( address, @@ -417,10 +417,10 @@ export class AccountController { @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, + @Query('canCreate', ParseBoolPipe) canCreate?: boolean, + @Query('canBurn', ParseBoolPipe) canBurn?: boolean, + @Query('canAddQuantity', ParseBoolPipe) canAddQuantity?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionCountForAddressWithRoles(address, new CollectionFilter({ search, type, subType, owner, canCreate, canBurn, canAddQuantity, excludeMetaESDT })); } @@ -433,10 +433,10 @@ export class AccountController { @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, + @Query('canCreate', ParseBoolPipe) canCreate?: boolean, + @Query('canBurn', ParseBoolPipe) canBurn?: boolean, + @Query('canAddQuantity', ParseBoolPipe) canAddQuantity?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionCountForAddressWithRoles(address, new CollectionFilter({ search, type, subType, owner, canCreate, canBurn, canAddQuantity, excludeMetaESDT, @@ -474,9 +474,9 @@ export class AccountController { @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @Query('search') search?: string, @Query('owner', ParseAddressPipe) owner?: string, - @Query('canMint', new ParseBoolPipe) canMint?: boolean, - @Query('canBurn', new ParseBoolPipe) canBurn?: boolean, - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('canMint', ParseBoolPipe) canMint?: boolean, + @Query('canBurn', ParseBoolPipe) canBurn?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, ): Promise { return await this.tokenService.getTokensWithRolesForAddress(address, new TokenWithRolesFilter({ search, owner, canMint, canBurn, includeMetaESDT }), new QueryPagination({ from, size })); } @@ -493,9 +493,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('owner', ParseAddressPipe) owner?: string, - @Query('canMint', new ParseBoolPipe) canMint?: boolean, - @Query('canBurn', new ParseBoolPipe) canBurn?: boolean, - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('canMint', ParseBoolPipe) canMint?: boolean, + @Query('canBurn', ParseBoolPipe) canBurn?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, ): Promise { return await this.tokenService.getTokensWithRolesForAddressCount(address, new TokenWithRolesFilter({ search, owner, canMint, canBurn, includeMetaESDT })); } @@ -506,9 +506,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('search') search?: string, @Query('owner', ParseAddressPipe) owner?: string, - @Query('canMint', new ParseBoolPipe) canMint?: boolean, - @Query('canBurn', new ParseBoolPipe) canBurn?: boolean, - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('canMint', ParseBoolPipe) canMint?: boolean, + @Query('canBurn', ParseBoolPipe) canBurn?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, ): Promise { return await this.tokenService.getTokensWithRolesForAddressCount(address, new TokenWithRolesFilter({ search, owner, canMint, canBurn, includeMetaESDT })); } @@ -544,7 +544,7 @@ export class AccountController { @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionsForAddress( address, @@ -564,7 +564,7 @@ export class AccountController { @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionCountForAddress(address, new CollectionFilter({ search, type, subType, excludeMetaESDT })); } @@ -576,7 +576,7 @@ export class AccountController { @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getCollectionCountForAddress(address, new CollectionFilter({ search, type, subType, excludeMetaESDT })); } @@ -635,13 +635,13 @@ export class AccountController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('includeFlagged', new ParseBoolPipe) includeFlagged?: boolean, - @Query('withSupply', new ParseBoolPipe) withSupply?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('includeFlagged', ParseBoolPipe) includeFlagged?: boolean, + @Query('withSupply', ParseBoolPipe) withSupply?: boolean, @Query('source', new ParseEnumPipe(EsdtDataSource)) source?: EsdtDataSource, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, @Query('fields', ParseArrayPipe) fields?: string[], - @Query('isScam', new ParseBoolPipe) isScam?: boolean, + @Query('isScam', ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, @Query('timestamp', ParseIntPipe) _timestamp?: number, ): Promise { @@ -700,10 +700,10 @@ export class AccountController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('includeFlagged', new ParseBoolPipe) includeFlagged?: boolean, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, - @Query('isScam', new ParseBoolPipe) isScam?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('includeFlagged', ParseBoolPipe) includeFlagged?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('isScam', ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, @Query('timestamp', ParseIntPipe) _timestamp?: number, ): Promise { @@ -739,10 +739,10 @@ export class AccountController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('includeFlagged', new ParseBoolPipe) includeFlagged?: boolean, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, - @Query('isScam', new ParseBoolPipe) isScam?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('includeFlagged', ParseBoolPipe) includeFlagged?: boolean, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('isScam', ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, @Query('timestamp', ParseIntPipe) _timestamp?: number, ): Promise { @@ -887,14 +887,14 @@ export class AccountController { @Query('round', ParseIntPipe) round?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, @Query('fields', ParseArrayPipe) fields?: string[], - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, - @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, + @Query('withBlockInfo', ParseBoolPipe) withBlockInfo?: boolean, @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { @@ -955,8 +955,9 @@ export class AccountController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, + ): Promise { return await this.transactionService.getTransactionCount(new TransactionFilter({ @@ -1026,12 +1027,12 @@ export class AccountController { @Query('fields', ParseArrayPipe) fields?: string[], @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, @Query('relayer', ParseAddressPipe) relayer?: string, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, - @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, + @Query('withBlockInfo', ParseBoolPipe) withBlockInfo?: boolean, @Query('senderOrReceiver', ParseAddressPipe) senderOrReceiver?: string, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index 2e48bf386..df5f2fa24 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -66,15 +66,15 @@ export class CollectionController { @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, - @Query('canCreate', new ParseAddressPipe) canCreate?: string, - @Query('canBurn', new ParseAddressPipe) canBurn?: string, - @Query('canAddQuantity', new ParseAddressPipe) canAddQuantity?: string, - @Query('canUpdateAttributes', new ParseAddressPipe) canUpdateAttributes?: string, - @Query('canAddUri', new ParseAddressPipe) canAddUri?: string, - @Query('canTransferRole', new ParseAddressPipe) canTransferRole?: string, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('before', ParseIntPipe) before?: number, + @Query('after', ParseIntPipe) after?: number, + @Query('canCreate', ParseAddressPipe) canCreate?: string, + @Query('canBurn', ParseAddressPipe) canBurn?: string, + @Query('canAddQuantity', ParseAddressPipe) canAddQuantity?: string, + @Query('canUpdateAttributes', ParseAddressPipe) canUpdateAttributes?: string, + @Query('canAddUri', ParseAddressPipe) canAddUri?: string, + @Query('canTransferRole', ParseAddressPipe) canTransferRole?: string, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, @Query('sort', new ParseEnumPipe(SortCollections)) sort?: SortCollections, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, ): Promise { @@ -117,15 +117,15 @@ export class CollectionController { @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, - @Query('canCreate', new ParseAddressPipe) canCreate?: string, - @Query('canBurn', new ParseAddressPipe) canBurn?: string, - @Query('canAddQuantity', new ParseAddressPipe) canAddQuantity?: string, - @Query('canUpdateAttributes', new ParseAddressPipe) canUpdateAttributes?: string, - @Query('canAddUri', new ParseAddressPipe) canAddUri?: string, - @Query('canTransferRole', new ParseAddressPipe) canTransferRole?: string, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('before', ParseIntPipe) before?: number, + @Query('after', ParseIntPipe) after?: number, + @Query('canCreate', ParseAddressPipe) canCreate?: string, + @Query('canBurn', ParseAddressPipe) canBurn?: string, + @Query('canAddQuantity', ParseAddressPipe) canAddQuantity?: string, + @Query('canUpdateAttributes', ParseAddressPipe) canUpdateAttributes?: string, + @Query('canAddUri', ParseAddressPipe) canAddUri?: string, + @Query('canTransferRole', ParseAddressPipe) canTransferRole?: string, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getNftCollectionCount(new CollectionFilter({ search, @@ -149,15 +149,15 @@ export class CollectionController { @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('before', new ParseIntPipe) before?: number, - @Query('after', new ParseIntPipe) after?: number, - @Query('canCreate', new ParseAddressPipe) canCreate?: string, - @Query('canBurn', new ParseAddressPipe) canBurn?: string, - @Query('canAddQuantity', new ParseAddressPipe) canAddQuantity?: string, - @Query('canUpdateAttributes', new ParseAddressPipe) canUpdateAttributes?: string, - @Query('canAddUri', new ParseAddressPipe) canAddUri?: string, - @Query('canTransferRole', new ParseAddressPipe) canTransferRole?: string, - @Query('excludeMetaESDT', new ParseBoolPipe) excludeMetaESDT?: boolean, + @Query('before', ParseIntPipe) before?: number, + @Query('after', ParseIntPipe) after?: number, + @Query('canCreate', ParseAddressPipe) canCreate?: string, + @Query('canBurn', ParseAddressPipe) canBurn?: string, + @Query('canAddQuantity', ParseAddressPipe) canAddQuantity?: string, + @Query('canUpdateAttributes', ParseAddressPipe) canUpdateAttributes?: string, + @Query('canAddUri', ParseAddressPipe) canAddUri?: string, + @Query('canTransferRole', ParseAddressPipe) canTransferRole?: string, + @Query('excludeMetaESDT', ParseBoolPipe) excludeMetaESDT?: boolean, ): Promise { return await this.collectionService.getNftCollectionCount(new CollectionFilter({ search, @@ -235,14 +235,14 @@ export class CollectionController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('isWhitelistedStorage', new ParseBoolPipe) isWhitelistedStorage?: boolean, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('isNsfw', new ParseBoolPipe) isNsfw?: boolean, - @Query('traits', new ParseRecordPipe) traits?: Record, - @Query('nonceBefore', new ParseIntPipe) nonceBefore?: number, - @Query('nonceAfter', new ParseIntPipe) nonceAfter?: number, - @Query('withOwner', new ParseBoolPipe) withOwner?: boolean, - @Query('withSupply', new ParseBoolPipe) withSupply?: boolean, + @Query('isWhitelistedStorage', ParseBoolPipe) isWhitelistedStorage?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('isNsfw', ParseBoolPipe) isNsfw?: boolean, + @Query('traits', ParseRecordPipe) traits?: Record, + @Query('nonceBefore', ParseIntPipe) nonceBefore?: number, + @Query('nonceAfter', ParseIntPipe) nonceAfter?: number, + @Query('withOwner', ParseBoolPipe) withOwner?: boolean, + @Query('withSupply', ParseBoolPipe) withSupply?: boolean, @Query('sort', new ParseEnumPipe(SortCollectionNfts)) sort?: SortCollectionNfts, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, ): Promise { @@ -279,11 +279,11 @@ export class CollectionController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('isWhitelistedStorage', new ParseBoolPipe) isWhitelistedStorage?: boolean, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('traits', new ParseRecordPipe) traits?: Record, - @Query('nonceBefore', new ParseIntPipe) nonceBefore?: number, - @Query('nonceAfter', new ParseIntPipe) nonceAfter?: number, + @Query('isWhitelistedStorage', ParseBoolPipe) isWhitelistedStorage?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('traits', ParseRecordPipe) traits?: Record, + @Query('nonceBefore', ParseIntPipe) nonceBefore?: number, + @Query('nonceAfter', ParseIntPipe) nonceAfter?: number, ): Promise { const isCollection = await this.collectionService.isCollection(collection); if (!isCollection) { @@ -353,11 +353,11 @@ export class CollectionController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername }); @@ -478,11 +478,11 @@ export class CollectionController { @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername }); diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index c30cfacca..50a495d24 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -68,16 +68,16 @@ export class NftController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('isWhitelistedStorage', new ParseBoolPipe) isWhitelistedStorage?: boolean, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('isNsfw', new ParseBoolPipe) isNsfw?: boolean, - @Query('isScam', new ParseBoolPipe) isScam?: boolean, + @Query('isWhitelistedStorage', ParseBoolPipe) isWhitelistedStorage?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('isNsfw', ParseBoolPipe) isNsfw?: boolean, + @Query('isScam', ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, - @Query('traits', new ParseRecordPipe) traits?: Record, - @Query('before', new ParseIntPipe) before?: number, - @Query('after', new ParseIntPipe) after?: number, - @Query('withOwner', new ParseBoolPipe) withOwner?: boolean, - @Query('withSupply', new ParseBoolPipe) withSupply?: boolean, + @Query('traits', ParseRecordPipe) traits?: Record, + @Query('before', ParseIntPipe) before?: number, + @Query('after', ParseIntPipe) after?: number, + @Query('withOwner', ParseBoolPipe) withOwner?: boolean, + @Query('withSupply', ParseBoolPipe) withSupply?: boolean, ): Promise { return await this.nftService.getNfts( new QueryPagination({ from, size }), @@ -134,13 +134,13 @@ export class NftController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('isWhitelistedStorage', new ParseBoolPipe) isWhitelistedStorage?: boolean, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('isNsfw', new ParseBoolPipe) isNsfw?: boolean, - @Query('traits', new ParseRecordPipe) traits?: Record, - @Query('before', new ParseIntPipe) before?: number, - @Query('after', new ParseIntPipe) after?: number, - @Query('isScam', new ParseBoolPipe) isScam?: boolean, + @Query('isWhitelistedStorage', ParseBoolPipe) isWhitelistedStorage?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('isNsfw', ParseBoolPipe) isNsfw?: boolean, + @Query('traits', ParseRecordPipe) traits?: Record, + @Query('before', ParseIntPipe) before?: number, + @Query('after', ParseIntPipe) after?: number, + @Query('isScam', ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, ): Promise { return await this.nftService.getNftCount( @@ -177,13 +177,13 @@ export class NftController { @Query('name') name?: string, @Query('tags', ParseArrayPipe) tags?: string[], @Query('creator', ParseAddressPipe) creator?: string, - @Query('isWhitelistedStorage', new ParseBoolPipe) isWhitelistedStorage?: boolean, - @Query('hasUris', new ParseBoolPipe) hasUris?: boolean, - @Query('isNsfw', new ParseBoolPipe) isNsfw?: boolean, - @Query('traits', new ParseRecordPipe) traits?: Record, - @Query('before', new ParseIntPipe) before?: number, - @Query('after', new ParseIntPipe) after?: number, - @Query('isScam', new ParseBoolPipe) isScam?: boolean, + @Query('isWhitelistedStorage', ParseBoolPipe) isWhitelistedStorage?: boolean, + @Query('hasUris', ParseBoolPipe) hasUris?: boolean, + @Query('isNsfw', ParseBoolPipe) isNsfw?: boolean, + @Query('traits', ParseRecordPipe) traits?: Record, + @Query('before', ParseIntPipe) before?: number, + @Query('after', ParseIntPipe) after?: number, + @Query('isScam', ParseBoolPipe) isScam?: boolean, @Query('scamType', new ParseEnumPipe(ScamType)) scamType?: ScamType, ): Promise { return await this.nftService.getNftCount(new NftFilter({ search, identifiers, type, subType, collection, collections, name, tags, creator, isWhitelistedStorage, hasUris, isNsfw, traits, before, after, isScam, scamType })); @@ -313,11 +313,12 @@ export class NftController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, + + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername }); @@ -423,11 +424,11 @@ export class NftController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, ) { const options = TransactionQueryOptions.applyDefaultOptions(size, { withScResults, withOperations, withLogs, withScamInfo, withUsername }); diff --git a/src/endpoints/nodes/node.controller.ts b/src/endpoints/nodes/node.controller.ts index f00bea6bb..eb478981d 100644 --- a/src/endpoints/nodes/node.controller.ts +++ b/src/endpoints/nodes/node.controller.ts @@ -59,7 +59,7 @@ export class NodeController { @Query('fullHistory', ParseBoolPipe) fullHistory?: boolean, @Query('sort', new ParseEnumPipe(NodeSort)) sort?: NodeSort, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('withIdentityInfo', new ParseBoolPipe) withIdentityInfo?: boolean, + @Query('withIdentityInfo', ParseBoolPipe) withIdentityInfo?: boolean, @Query('isQualified', ParseBoolPipe) isQualified?: boolean, @Query('isAuctioned', ParseBoolPipe) isAuctioned?: boolean, @Query('isAuctionDangerZone', ParseBoolPipe) isAuctionDangerZone?: boolean, diff --git a/src/endpoints/providers/provider.controller.ts b/src/endpoints/providers/provider.controller.ts index 03bc999ef..eb444c9ee 100644 --- a/src/endpoints/providers/provider.controller.ts +++ b/src/endpoints/providers/provider.controller.ts @@ -26,8 +26,8 @@ export class ProviderController { @Query('identity') identity?: string, @Query('owner', ParseAddressPipe) owner?: string, @Query('providers', ParseAddressArrayPipe) providers?: string[], - @Query('withIdentityInfo', new ParseBoolPipe) withIdentityInfo?: boolean, - @Query('withLatestInfo', new ParseBoolPipe) withLatestInfo?: boolean, + @Query('withIdentityInfo', ParseBoolPipe) withIdentityInfo?: boolean, + @Query('withLatestInfo', ParseBoolPipe) withLatestInfo?: boolean, ): Promise { const options = ProviderQueryOptions.applyDefaultOptions(owner, { withIdentityInfo, withLatestInfo }); diff --git a/src/endpoints/rounds/round.controller.ts b/src/endpoints/rounds/round.controller.ts index 783e0eaf3..9fefac936 100644 --- a/src/endpoints/rounds/round.controller.ts +++ b/src/endpoints/rounds/round.controller.ts @@ -26,8 +26,8 @@ export class RoundController { @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, @Query("validator", ParseBlsHashPipe) validator?: string, @Query('condition', new ParseEnumPipe(QueryConditionOptions)) condition?: QueryConditionOptions, - @Query("shard", new ParseIntPipe) shard?: number, - @Query("epoch", new ParseIntPipe) epoch?: number, + @Query("shard", ParseIntPipe) shard?: number, + @Query("epoch", ParseIntPipe) epoch?: number, ): Promise { return this.roundService.getRounds(new RoundFilter({ from, size, condition, validator, shard, epoch })); } @@ -42,8 +42,8 @@ export class RoundController { getRoundCount( @Query("validator", ParseBlsHashPipe) validator?: string, @Query('condition', new ParseEnumPipe(QueryConditionOptions)) condition?: QueryConditionOptions, - @Query("shard", new ParseIntPipe) shard?: number, - @Query("epoch", new ParseIntPipe) epoch?: number, + @Query("shard", ParseIntPipe) shard?: number, + @Query("epoch", ParseIntPipe) epoch?: number, ): Promise { return this.roundService.getRoundCount(new RoundFilter({ condition, validator, shard, epoch })); } @@ -53,8 +53,8 @@ export class RoundController { getRoundCountAlternative( @Query("validator", ParseBlsHashPipe) validator?: string, @Query('condition', new ParseEnumPipe(QueryConditionOptions)) condition?: QueryConditionOptions, - @Query("shard", new ParseIntPipe) shard?: number, - @Query("epoch", new ParseIntPipe) epoch?: number, + @Query("shard", ParseIntPipe) shard?: number, + @Query("epoch", ParseIntPipe) epoch?: number, ): Promise { return this.roundService.getRoundCount(new RoundFilter({ condition, validator, shard, epoch })); } diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index 95529bb63..757a29db6 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -59,7 +59,7 @@ export class TokenController { @Query('identifiers', ParseArrayPipe) identifiers?: string[], @Query('sort', new ParseEnumPipe(TokenSort)) sort?: TokenSort, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], @Query('priceSource', new ParseEnumPipe(TokenAssetsPriceSourceType)) priceSource?: TokenAssetsPriceSourceType, ): Promise { @@ -87,7 +87,7 @@ export class TokenController { @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('identifier', ParseTokenPipe) identifier?: string, @Query('identifiers', ParseArrayPipe) identifiers?: string[], - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], @Query('priceSource', new ParseEnumPipe(TokenAssetsPriceSourceType)) priceSource?: TokenAssetsPriceSourceType, ): Promise { @@ -102,7 +102,7 @@ export class TokenController { @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('identifier', ParseTokenPipe) identifier?: string, @Query('identifiers', ParseArrayPipe) identifiers?: string[], - @Query('includeMetaESDT', new ParseBoolPipe) includeMetaESDT?: boolean, + @Query('includeMetaESDT', ParseBoolPipe) includeMetaESDT?: boolean, @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], @Query('priceSource', new ParseEnumPipe(TokenAssetsPriceSourceType)) priceSource?: TokenAssetsPriceSourceType, ): Promise { @@ -116,7 +116,7 @@ export class TokenController { @ApiNotFoundResponse({ description: 'Token not found' }) async getToken( @Param('identifier', ParseTokenPipe) identifier: string, - @Query('denominated', new ParseBoolPipe) denominated?: boolean, + @Query('denominated', ParseBoolPipe) denominated?: boolean, ): Promise { const supplyOptions = { denominated }; const token = await this.tokenService.getToken(identifier, supplyOptions); @@ -134,7 +134,7 @@ export class TokenController { @ApiNotFoundResponse({ description: 'Token not found' }) async getTokenSupply( @Param('identifier', ParseTokenPipe) identifier: string, - @Query('denominated', new ParseBoolPipe) denominated?: boolean, + @Query('denominated', ParseBoolPipe) denominated?: boolean, ): Promise { const isToken = await this.tokenService.isToken(identifier); if (!isToken) { @@ -237,12 +237,12 @@ export class TokenController { @Query('round', ParseIntPipe) round?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, @Query('fields', ParseArrayPipe) fields?: string[], - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, - @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, + @Query('withBlockInfo', ParseBoolPipe) withBlockInfo?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { @@ -411,9 +411,9 @@ export class TokenController { @Query('round', ParseIntPipe) round?: number, @Query('fields', ParseArrayPipe) fields?: string[], @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, - @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, + @Query('withBlockInfo', ParseBoolPipe) withBlockInfo?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, ): Promise { const isToken = await this.tokenService.isToken(identifier); diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index 3f36b5053..90858a346 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -68,13 +68,13 @@ export class TransactionController { @Query('round', ParseIntPipe) round?: number, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, @Query('fields', ParseArrayPipe) fields?: string[], - @Query('withScResults', new ParseBoolPipe) withScResults?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, - @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('withScResults', ParseBoolPipe) withScResults?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, + @Query('withBlockInfo', ParseBoolPipe) withBlockInfo?: boolean, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ) { @@ -139,7 +139,7 @@ export class TransactionController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ): Promise { return this.transactionService.getTransactionCount(new TransactionFilter({ @@ -176,8 +176,8 @@ export class TransactionController { @Query('condition') condition?: QueryConditionOptions, @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, - @Query('round', new ParseIntPipe) round?: number, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('round', ParseIntPipe) round?: number, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, @Query('withRelayedScresults', ParseBoolPipe) withRelayedScresults?: boolean, ): Promise { return this.transactionService.getTransactionCount(new TransactionFilter({ diff --git a/src/endpoints/transfers/transfer.controller.ts b/src/endpoints/transfers/transfer.controller.ts index 8617a0cf7..1e723c463 100644 --- a/src/endpoints/transfers/transfer.controller.ts +++ b/src/endpoints/transfers/transfer.controller.ts @@ -65,12 +65,12 @@ export class TransferController { @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, @Query('fields', ParseArrayPipe) fields?: string[], @Query('relayer', ParseAddressPipe) relayer?: string, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, - @Query('withScamInfo', new ParseBoolPipe) withScamInfo?: boolean, - @Query('withUsername', new ParseBoolPipe) withUsername?: boolean, - @Query('withBlockInfo', new ParseBoolPipe) withBlockInfo?: boolean, - @Query('withLogs', new ParseBoolPipe) withLogs?: boolean, - @Query('withOperations', new ParseBoolPipe) withOperations?: boolean, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, + @Query('withScamInfo', ParseBoolPipe) withScamInfo?: boolean, + @Query('withUsername', ParseBoolPipe) withUsername?: boolean, + @Query('withBlockInfo', ParseBoolPipe) withBlockInfo?: boolean, + @Query('withLogs', ParseBoolPipe) withLogs?: boolean, + @Query('withOperations', ParseBoolPipe) withOperations?: boolean, @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { @@ -132,7 +132,7 @@ export class TransferController { @Query('before', ParseIntPipe) before?: number, @Query('after', ParseIntPipe) after?: number, @Query('round', ParseIntPipe) round?: number, - @Query('isRelayed', new ParseBoolPipe) isRelayed?: boolean, + @Query('isRelayed', ParseBoolPipe) isRelayed?: boolean, @Query('withRefunds', ParseBoolPipe) withRefunds?: boolean, ): Promise { return await this.transferService.getTransfersCount(new TransactionFilter({ diff --git a/src/endpoints/usernames/username.controller.ts b/src/endpoints/usernames/username.controller.ts index b0aeadb05..3a1f242e4 100644 --- a/src/endpoints/usernames/username.controller.ts +++ b/src/endpoints/usernames/username.controller.ts @@ -22,7 +22,7 @@ export class UsernameController { async getUsernameDetails( @Res() res: any, @Param('username') username: string, - @Query('withGuardianInfo', new ParseBoolPipe) withGuardianInfo: boolean + @Query('withGuardianInfo', ParseBoolPipe) withGuardianInfo: boolean ): Promise { const address = await this.usernameService.getAddressForUsername(username); if (!address) { From c0515214d4a7f8a2fe9d9687d181132afc2a741a Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:11:08 +0200 Subject: [PATCH 13/20] check if nft.type exist and add NftSubType filter (#1394) --- src/endpoints/nfts/nft.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/endpoints/nfts/nft.service.ts b/src/endpoints/nfts/nft.service.ts index 1c357d8ef..e0af78f6b 100644 --- a/src/endpoints/nfts/nft.service.ts +++ b/src/endpoints/nfts/nft.service.ts @@ -31,6 +31,7 @@ import { NftRarities } from "./entities/nft.rarities"; import { SortCollectionNfts } from "../collections/entities/sort.collection.nfts"; import { TokenAssets } from "src/common/assets/entities/token.assets"; import { ScamInfo } from "src/common/entities/scam-info.dto"; +import { NftSubType } from "./entities/nft.sub.type"; @Injectable() export class NftService { @@ -217,7 +218,10 @@ export class NftService { return undefined; } - if (nft.type.in(NftType.SemiFungibleESDT, NftType.MetaESDT)) { + if (nft.type && nft.type.in( + NftType.SemiFungibleESDT, NftType.MetaESDT, + NftSubType.DynamicSemiFungibleESDT, NftSubType.DynamicMetaESDT + )) { await this.applySupply(nft); } From 8135bf83dcb057a12db46bacbd037492e0407a0b Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Fri, 20 Dec 2024 15:33:43 +0200 Subject: [PATCH 14/20] Added a check for encoded data in KeysService to handle cases where no data is returned, ensuring a default response of remainingUnBondPeriod as 0. (#1427) --- src/endpoints/keys/keys.service.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/endpoints/keys/keys.service.ts b/src/endpoints/keys/keys.service.ts index 149196bf3..b4887e654 100644 --- a/src/endpoints/keys/keys.service.ts +++ b/src/endpoints/keys/keys.service.ts @@ -27,6 +27,10 @@ export class KeysService { [key] ); + if (!encoded || !encoded[0]) { + return { remainingUnBondPeriod: 0 }; + } + let remainingUnBondPeriod = parseInt(Buffer.from(encoded[0], 'base64').toString('ascii')); if (isNaN(remainingUnBondPeriod)) { From 0937b6570d73d9fcce18d0dc48f7884c586cdc1e Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:56:24 +0200 Subject: [PATCH 15/20] websockets transactions events metrics (#1429) * Enhance ApiMetricsService with new transaction and batch update counters. * Updated WebSocketPublisherController to emit metrics events after processing transactions and batch updates. * Update web.socket controller spec --- src/common/metrics/api.metrics.service.ts | 41 ++++++++++++++++++- .../web-socket-publisher-controller.ts | 13 ++++++ .../web.socket.publiser.controller.spec.ts | 13 ++++++ src/utils/metrics-events.constants.ts | 3 ++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/src/common/metrics/api.metrics.service.ts b/src/common/metrics/api.metrics.service.ts index 5cd064536..eea34676a 100644 --- a/src/common/metrics/api.metrics.service.ts +++ b/src/common/metrics/api.metrics.service.ts @@ -1,7 +1,7 @@ import { MetricsService } from '@multiversx/sdk-nestjs-monitoring'; import { forwardRef, Inject, Injectable } from "@nestjs/common"; import { OnEvent } from '@nestjs/event-emitter'; -import { register, Histogram, Gauge } from 'prom-client'; +import { register, Histogram, Gauge, Counter } from 'prom-client'; import { ApiConfigService } from "src/common/api-config/api.config.service"; import { GatewayService } from "../gateway/gateway.service"; import { ProtocolService } from "../protocol/protocol.service"; @@ -19,6 +19,9 @@ export class ApiMetricsService { private static lastProcessedNonceGauge: Gauge; private static lastProcessedBatchProcessorNonce: Gauge; private static lastProcessedTransactionCompletedProcessorNonce: Gauge; + private static transactionsCompletedCounter: Counter; + private static transactionsPendingResultsCounter: Counter; + private static batchUpdatesCounter: Counter; constructor( private readonly apiConfigService: ApiConfigService, @@ -105,6 +108,27 @@ export class ApiMetricsService { labelNames: ['shardId'], }); } + + if (!ApiMetricsService.transactionsCompletedCounter) { + ApiMetricsService.transactionsCompletedCounter = new Counter({ + name: 'websocket_transactions_completed_total', + help: 'Total number of completed transactions processed via websocket', + }); + } + + if (!ApiMetricsService.transactionsPendingResultsCounter) { + ApiMetricsService.transactionsPendingResultsCounter = new Counter({ + name: 'websocket_transactions_pending_results_total', + help: 'Total number of transactions with pending results processed via websocket', + }); + } + + if (!ApiMetricsService.batchUpdatesCounter) { + ApiMetricsService.batchUpdatesCounter = new Counter({ + name: 'websocket_batch_updates_total', + help: 'Total number of batch updates processed via websocket', + }); + } } @OnEvent(MetricsEvents.SetVmQuery) @@ -158,6 +182,21 @@ export class ApiMetricsService { ApiMetricsService.lastProcessedTransactionCompletedProcessorNonce.set({ shardId }, nonce); } + @OnEvent(MetricsEvents.SetTransactionsCompleted) + recordTransactionsCompleted(payload: { transactions: any[] }) { + ApiMetricsService.transactionsCompletedCounter.inc(payload.transactions.length); + } + + @OnEvent(MetricsEvents.SetTransactionsPendingResults) + recordTransactionsPendingResults(payload: { transactions: any[] }) { + ApiMetricsService.transactionsPendingResultsCounter.inc(payload.transactions.length); + } + + @OnEvent(MetricsEvents.SetBatchUpdated) + recordBatchUpdated() { + ApiMetricsService.batchUpdatesCounter.inc(); + } + async getMetrics(): Promise { const shardIds = await this.protocolService.getShardIds(); if (this.apiConfigService.getIsTransactionProcessorCronActive()) { diff --git a/src/common/websockets/web-socket-publisher-controller.ts b/src/common/websockets/web-socket-publisher-controller.ts index 7fe669a4d..17a9addc0 100644 --- a/src/common/websockets/web-socket-publisher-controller.ts +++ b/src/common/websockets/web-socket-publisher-controller.ts @@ -3,6 +3,8 @@ import { ShardTransaction } from "@elrondnetwork/transaction-processor"; import { Controller } from "@nestjs/common"; import { EventPattern } from "@nestjs/microservices"; import { WebSocketPublisherService } from "src/common/websockets/web-socket-publisher-service"; +import { EventEmitter2 } from "@nestjs/event-emitter"; +import { MetricsEvents } from "src/utils/metrics-events.constants"; @Controller() export class WebSocketPublisherController { @@ -10,6 +12,7 @@ export class WebSocketPublisherController { constructor( private readonly webSocketPublisherService: WebSocketPublisherService, + private readonly eventEmitter: EventEmitter2, ) { } @EventPattern('transactionsCompleted') @@ -17,6 +20,10 @@ export class WebSocketPublisherController { for (const transaction of transactions) { await this.webSocketPublisherService.onTransactionCompleted(transaction); } + + this.eventEmitter.emit(MetricsEvents.SetTransactionsCompleted, { + transactions, + }); } @EventPattern('transactionsPendingResults') @@ -24,11 +31,17 @@ export class WebSocketPublisherController { for (const transaction of transactions) { await this.webSocketPublisherService.onTransactionPendingResults(transaction); } + + this.eventEmitter.emit(MetricsEvents.SetTransactionsPendingResults, { + transactions, + }); } @EventPattern('onBatchUpdated') onBatchUpdated(payload: { address: string, batchId: string, txHashes: string[] }) { this.logger.log(`Notifying batch updated for address ${payload.address}, batch id '${payload.batchId}', hashes ${payload.txHashes} `); this.webSocketPublisherService.onBatchUpdated(payload.address, payload.batchId, payload.txHashes); + + this.eventEmitter.emit(MetricsEvents.SetBatchUpdated); } } diff --git a/src/test/unit/controllers/web.socket.publiser.controller.spec.ts b/src/test/unit/controllers/web.socket.publiser.controller.spec.ts index bec012c47..91fc3bb9c 100644 --- a/src/test/unit/controllers/web.socket.publiser.controller.spec.ts +++ b/src/test/unit/controllers/web.socket.publiser.controller.spec.ts @@ -1,11 +1,14 @@ import { ShardTransaction } from "@elrondnetwork/transaction-processor"; import { TestingModule, Test } from "@nestjs/testing"; +import { EventEmitter2 } from "@nestjs/event-emitter"; import { WebSocketPublisherController } from "src/common/websockets/web-socket-publisher-controller"; import { WebSocketPublisherService } from "src/common/websockets/web-socket-publisher-service"; +import { MetricsEvents } from "src/utils/metrics-events.constants"; describe('WebSocketPublisherController', () => { let controller: WebSocketPublisherController; let webSocketPublisherService: WebSocketPublisherService; + let eventEmitter: EventEmitter2; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -19,23 +22,32 @@ describe('WebSocketPublisherController', () => { onBatchUpdated: jest.fn(), }, }, + { + provide: EventEmitter2, + useValue: { + emit: jest.fn(), + }, + }, ], }).compile(); controller = module.get(WebSocketPublisherController); webSocketPublisherService = module.get(WebSocketPublisherService); + eventEmitter = module.get(EventEmitter2); }); it('should handle transactionsCompleted event', async () => { const mockTransactions = [{}, {}] as ShardTransaction[]; await controller.transactionsCompleted(mockTransactions); expect(webSocketPublisherService.onTransactionCompleted).toHaveBeenCalledTimes(mockTransactions.length); + expect(eventEmitter.emit).toHaveBeenCalledWith(MetricsEvents.SetTransactionsCompleted, { transactions: mockTransactions }); }); it('should handle transactionsPendingResults event', async () => { const mockTransactions = [{}, {}] as ShardTransaction[]; await controller.transactionsPendingResults(mockTransactions); expect(webSocketPublisherService.onTransactionPendingResults).toHaveBeenCalledTimes(mockTransactions.length); + expect(eventEmitter.emit).toHaveBeenCalledWith(MetricsEvents.SetTransactionsPendingResults, { transactions: mockTransactions }); }); it('should handle onBatchUpdated event', () => { @@ -43,5 +55,6 @@ describe('WebSocketPublisherController', () => { controller.onBatchUpdated(mockPayload); expect(webSocketPublisherService.onBatchUpdated).toHaveBeenCalledWith(mockPayload.address, mockPayload.batchId, mockPayload.txHashes); expect(webSocketPublisherService.onBatchUpdated).toHaveBeenCalledTimes(1); + expect(eventEmitter.emit).toHaveBeenCalledWith(MetricsEvents.SetBatchUpdated); }); }); diff --git a/src/utils/metrics-events.constants.ts b/src/utils/metrics-events.constants.ts index 41337fd23..a2e480638 100644 --- a/src/utils/metrics-events.constants.ts +++ b/src/utils/metrics-events.constants.ts @@ -7,4 +7,7 @@ export enum MetricsEvents { SetLastProcessedNonce = "setLastProcessedNonce", SetLastProcessedBatchProcessorNonce = "setLastProcessedBatchProcessorNonce", SetLastProcessedTransactionCompletedProcessorNonce = "setLastProcessedTransactionCompletedProcessorNonce", + SetTransactionsCompleted = "setTransactionsCompleted", + SetTransactionsPendingResults = "setTransactionsPendingResults", + SetBatchUpdated = "setBatchUpdated", } From 1f340f7d22c3f888c05f517ff7a4eaed877dabf9 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu <51945539+bogdan-rosianu@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:56:35 +0200 Subject: [PATCH 16/20] added function name for pool transactions (#1425) * added function name for pool transactions * fix modules imports * fix unit tests * add function filter --- src/endpoints/pool/entities/pool.filter.ts | 1 + .../pool/entities/transaction.in.pool.dto.ts | 3 +++ src/endpoints/pool/pool.controller.ts | 7 ++++++- src/endpoints/pool/pool.module.ts | 4 ++++ src/endpoints/pool/pool.service.ts | 16 +++++++++++++++- src/test/unit/services/pool.spec.ts | 7 +++++++ 6 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/endpoints/pool/entities/pool.filter.ts b/src/endpoints/pool/entities/pool.filter.ts index d1bd4c8e8..539c422a4 100644 --- a/src/endpoints/pool/entities/pool.filter.ts +++ b/src/endpoints/pool/entities/pool.filter.ts @@ -10,4 +10,5 @@ export class PoolFilter { senderShard?: number; receiverShard?: number; type?: TransactionType; + functions?: string[]; } diff --git a/src/endpoints/pool/entities/transaction.in.pool.dto.ts b/src/endpoints/pool/entities/transaction.in.pool.dto.ts index a18830d54..d6aa725a9 100644 --- a/src/endpoints/pool/entities/transaction.in.pool.dto.ts +++ b/src/endpoints/pool/entities/transaction.in.pool.dto.ts @@ -48,6 +48,9 @@ export class TransactionInPool { @ApiProperty({ type: String, example: "0228618b6339c5eaf71ed1a8cd71df010ccd0369a29d957c37d53b0409408161726dd97e10ac7836996f666ffd636a797b9b9abecbd276971376fb3479b48203" }) signature: string = ''; + @ApiProperty({ type: String, nullable: true, example: 'composeTasks', required: false }) + function: string = ''; + @ApiProperty({ type: String, example: "SmartContractResult" }) type: TransactionType = TransactionType.Transaction; } diff --git a/src/endpoints/pool/pool.controller.ts b/src/endpoints/pool/pool.controller.ts index 18aa91a32..f6948e4f9 100644 --- a/src/endpoints/pool/pool.controller.ts +++ b/src/endpoints/pool/pool.controller.ts @@ -1,4 +1,4 @@ -import { ParseAddressAndMetachainPipe, ParseAddressPipe, ParseEnumPipe, ParseIntPipe, ParseTransactionHashPipe } from "@multiversx/sdk-nestjs-common"; +import { ParseAddressAndMetachainPipe, ParseAddressPipe, ParseEnumPipe, ParseIntPipe, ParseTransactionHashPipe, ParseArrayPipe } from "@multiversx/sdk-nestjs-common"; import { Controller, DefaultValuePipe, Get, NotFoundException, Param, Query } from "@nestjs/common"; import { ApiExcludeEndpoint, ApiNotFoundResponse, ApiOkResponse, ApiOperation, ApiQuery, ApiTags } from "@nestjs/swagger"; import { PoolService } from "./pool.service"; @@ -6,6 +6,7 @@ import { QueryPagination } from "src/common/entities/query.pagination"; import { TransactionInPool } from "./entities/transaction.in.pool.dto"; import { TransactionType } from "../transactions/entities/transaction.type"; import { PoolFilter } from "./entities/pool.filter"; +import { ParseArrayPipeOptions } from "@multiversx/sdk-nestjs-common/lib/pipes/entities/parse.array.options"; @Controller() @ApiTags('pool') @@ -24,6 +25,8 @@ export class PoolController { @ApiQuery({ name: 'senderShard', description: 'The shard of the sender', required: false }) @ApiQuery({ name: 'receiverShard', description: 'The shard of the receiver', required: false }) @ApiQuery({ name: 'type', description: 'Search in transaction pool by type', required: false }) + @ApiQuery({ name: 'function', description: 'Filter transactions by function name', required: false }) + async getTransactionPool( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, @@ -32,6 +35,7 @@ export class PoolController { @Query('senderShard', ParseIntPipe) senderShard?: number, @Query('receiverShard', ParseIntPipe) receiverShard?: number, @Query('type', new ParseEnumPipe(TransactionType)) type?: TransactionType, + @Query('function', new ParseArrayPipe(new ParseArrayPipeOptions({ allowEmptyString: true }))) functions?: string[], ): Promise { return await this.poolService.getPool(new QueryPagination({ from, size }), new PoolFilter({ sender: sender, @@ -39,6 +43,7 @@ export class PoolController { senderShard: senderShard, receiverShard: receiverShard, type: type, + functions: functions, })); } diff --git a/src/endpoints/pool/pool.module.ts b/src/endpoints/pool/pool.module.ts index 11ff8ec83..88c7a780e 100644 --- a/src/endpoints/pool/pool.module.ts +++ b/src/endpoints/pool/pool.module.ts @@ -1,7 +1,11 @@ import { Module } from "@nestjs/common"; import { PoolService } from "./pool.service"; +import { TransactionActionModule } from "../transactions/transaction-action/transaction.action.module"; @Module({ + imports: [ + TransactionActionModule, + ], providers: [ PoolService, ], diff --git a/src/endpoints/pool/pool.service.ts b/src/endpoints/pool/pool.service.ts index b45e9efc3..4bfd15eb1 100644 --- a/src/endpoints/pool/pool.service.ts +++ b/src/endpoints/pool/pool.service.ts @@ -11,6 +11,9 @@ import { PoolFilter } from "./entities/pool.filter"; import { TxInPoolFields } from "src/common/gateway/entities/tx.in.pool.fields"; import { AddressUtils } from "@multiversx/sdk-nestjs-common"; import { ProtocolService } from "../../common/protocol/protocol.service"; +import { TransactionActionService } from "../transactions/transaction-action/transaction.action.service"; +import { Transaction } from "../transactions/entities/transaction"; +import { ApiUtils } from "@multiversx/sdk-nestjs-http"; @Injectable() export class PoolService { @@ -19,6 +22,7 @@ export class PoolService { private readonly apiConfigService: ApiConfigService, private readonly cacheService: CacheService, private readonly protocolService: ProtocolService, + private readonly transactionActionService: TransactionActionService, ) { } async getTransactionFromPool(txHash: string): Promise { @@ -105,6 +109,11 @@ export class PoolService { transaction.receiverShard = AddressUtils.computeShard(AddressUtils.bech32Decode(transaction.receiver), shardCount); } + const metadata = await this.transactionActionService.getTransactionMetadata(this.poolTransactionToTransaction(transaction), false); + if (metadata && metadata.functionName) { + transaction.function = metadata.functionName; + } + return transaction; } @@ -115,8 +124,13 @@ export class PoolService { (!filters.receiver || transaction.receiver === filters.receiver) && (!filters.type || transaction.type === filters.type) && (filters.senderShard === undefined || transaction.senderShard === filters.senderShard) && - (filters.receiverShard === undefined || transaction.receiverShard === filters.receiverShard) + (filters.receiverShard === undefined || transaction.receiverShard === filters.receiverShard) && + (filters.functions === undefined || transaction.function === undefined || filters.functions.indexOf(transaction.function) > -1) ); }); } + + private poolTransactionToTransaction(transaction: TransactionInPool): Transaction { + return ApiUtils.mergeObjects(new Transaction(), transaction); + } } diff --git a/src/test/unit/services/pool.spec.ts b/src/test/unit/services/pool.spec.ts index a8505f257..a276ca2e3 100644 --- a/src/test/unit/services/pool.spec.ts +++ b/src/test/unit/services/pool.spec.ts @@ -7,6 +7,7 @@ import { PoolFilter } from "src/endpoints/pool/entities/pool.filter"; import { PoolService } from "src/endpoints/pool/pool.service"; import { TransactionType } from "src/endpoints/transactions/entities/transaction.type"; import { ProtocolService } from "../../../common/protocol/protocol.service"; +import { TransactionActionService } from "../../../endpoints/transactions/transaction-action/transaction.action.service"; describe('PoolService', () => { let service: PoolService; @@ -41,6 +42,12 @@ describe('PoolService', () => { isTransactionPoolEnabled: jest.fn().mockResolvedValue(true), }, }, + { + provide: TransactionActionService, + useValue: { + getTransactionMetadata: jest.fn(), + }, + }, ], }).compile(); From 4ac6654161b65fa92aa7dc47ca10bba017f175ce Mon Sep 17 00:00:00 2001 From: Rebegea Dragos-Alexandru <42241923+dragos-rebegea@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:56:48 +0200 Subject: [PATCH 17/20] accounts endpoint make es heavy fiealds optional (#1426) * accounts endpoint make es heavy fiealds optional * optimize provider query for simple accounts * early return for null account * use AccountFetchOptions * fixes after review * fixes after review * Enhance AccountController tests to validate optional parameters in account retrieval. Added tests for withTxCount, withScrCount, withTimestamp, and withAssets parameters, ensuring correct behavior and response structure. Updated existing tests to reflect changes in expected account details when optional parameters are used. Improved overall test coverage for account details retrieval. --------- Co-authored-by: bogdan-rosianu Co-authored-by: cfaur09 --- src/endpoints/accounts/account.controller.ts | 16 +- src/endpoints/accounts/account.service.ts | 50 ++-- .../entities/account.fetch.options.ts | 11 + .../account.optional.field.options.ts | 4 - .../controllers/accounts.controller.spec.ts | 235 +++++++++++++++++- 5 files changed, 279 insertions(+), 37 deletions(-) create mode 100644 src/endpoints/accounts/entities/account.fetch.options.ts delete mode 100644 src/endpoints/accounts/entities/account.optional.field.options.ts diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index e1387bb56..38fd1b17d 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -58,6 +58,7 @@ import { DeepHistoryInterceptor } from 'src/interceptors/deep-history.intercepto import { MexPairType } from '../mex/entities/mex.pair.type'; import { NftSubType } from '../nfts/entities/nft.sub.type'; import { AccountContract } from './entities/account.contract'; +import { AccountFetchOptions } from './entities/account.fetch.options'; @Controller() @ApiTags('accounts') @@ -194,16 +195,25 @@ export class AccountController { @UseInterceptors(DeepHistoryInterceptor) @ApiOperation({ summary: 'Account details', description: 'Returns account details for a given address' }) @ApiQuery({ name: 'withGuardianInfo', description: 'Returns guardian data for a given address', required: false }) - @ApiQuery({ name: 'fields', description: 'List of fields to filter by', required: false, isArray: true, style: 'form', explode: false }) + @ApiQuery({ name: 'withTxCount', description: 'Returns the count of the transactions for a given address', required: false }) + @ApiQuery({ name: 'withScrCount', description: 'Returns the sc results count for a given address', required: false }) + @ApiQuery({ name: 'withTimestamp', description: 'Returns the timestamp of the last activity for a given address', required: false }) + @ApiQuery({ name: 'withAssets', description: 'Returns the assets for a given address', required: false }) @ApiQuery({ name: 'timestamp', description: 'Retrieve entry from timestamp', required: false, type: Number }) @ApiOkResponse({ type: AccountDetailed }) async getAccountDetails( @Param('address', ParseAddressPipe) address: string, @Query('withGuardianInfo', ParseBoolPipe) withGuardianInfo?: boolean, - @Query('fields', ParseArrayPipe) fields?: string[], + @Query('withTxCount', ParseBoolPipe) withTxCount?: boolean, + @Query('withScrCount', ParseBoolPipe) withScrCount?: boolean, + @Query('withTimestamp', ParseBoolPipe) withTimestamp?: boolean, + @Query('withAssets', ParseBoolPipe) withAssets?: boolean, @Query('timestamp', ParseIntPipe) _timestamp?: number, ): Promise { - const account = await this.accountService.getAccount(address, fields, withGuardianInfo); + const account = await this.accountService.getAccount( + address, + new AccountFetchOptions({ withGuardianInfo, withTxCount, withScrCount, withTimestamp, withAssets }), + ); if (!account) { throw new NotFoundException('Account not found'); } diff --git a/src/endpoints/accounts/account.service.ts b/src/endpoints/accounts/account.service.ts index b2fcd6462..96f647f8f 100644 --- a/src/endpoints/accounts/account.service.ts +++ b/src/endpoints/accounts/account.service.ts @@ -20,7 +20,6 @@ import { AddressUtils, BinaryUtils, OriginLogger } from '@multiversx/sdk-nestjs- import { ApiService, ApiUtils } from "@multiversx/sdk-nestjs-http"; import { GatewayService } from 'src/common/gateway/gateway.service'; import { IndexerService } from "src/common/indexer/indexer.service"; -import { AccountOptionalFieldOption } from './entities/account.optional.field.options'; import { AccountAssets } from 'src/common/assets/entities/account.assets'; import { CacheInfo } from 'src/utils/cache.info'; import { UsernameService } from '../usernames/username.service'; @@ -33,9 +32,10 @@ import { ProviderService } from '../providers/provider.service'; import { KeysService } from '../keys/keys.service'; import { NodeStatusRaw } from '../nodes/entities/node.status'; import { AccountKeyFilter } from './entities/account.key.filter'; -import { Provider } from '../providers/entities/provider'; import { ApplicationMostUsed } from './entities/application.most.used'; import { AccountContract } from './entities/account.contract'; +import { AccountFetchOptions } from './entities/account.fetch.options'; +import { Provider } from '../providers/entities/provider'; @Injectable() export class AccountService { @@ -74,39 +74,38 @@ export class AccountService { return await this.indexerService.getAccountsCount(filter); } - async getAccount(address: string, fields?: string[], withGuardianInfo?: boolean): Promise { + async getAccount(address: string, options?: AccountFetchOptions): Promise { if (!AddressUtils.isAddressValid(address)) { return null; } - const provider: Provider | undefined = await this.providerService.getProvider(address); - - let txCount: number = 0; - let scrCount: number = 0; - - if (!fields || fields.length === 0 || fields.includes(AccountOptionalFieldOption.txCount)) { - txCount = await this.getAccountTxCount(address); + const account = await this.getAccountRaw(address, options?.withAssets); + if (!account) { + return null; } - if (!fields || fields.length === 0 || fields.includes(AccountOptionalFieldOption.scrCount)) { - scrCount = await this.getAccountScResults(address); + if (options?.withTxCount === true) { + account.txCount = await this.getAccountTxCount(address); } - const [account, elasticSearchAccount] = await Promise.all([ - this.getAccountRaw(address, txCount, scrCount), - this.indexerService.getAccount(address), - ]); + if (options?.withScrCount === true) { + account.scrCount = await this.getAccountScResults(address); + } - if (account && withGuardianInfo === true) { + if (options?.withGuardianInfo === true) { await this.applyGuardianInfo(account); } - if (account && elasticSearchAccount) { + if (options?.withTimestamp) { + const elasticSearchAccount = await this.indexerService.getAccount(address); account.timestamp = elasticSearchAccount.timestamp; } - if (account && provider && provider.owner) { - account.ownerAddress = provider.owner; + if (AddressUtils.isSmartContractAddress(address)) { + const provider: Provider | undefined = await this.providerService.getProvider(address); + if (provider && provider.owner) { + account.ownerAddress = provider.owner; + } } return account; @@ -161,8 +160,7 @@ export class AccountService { return await this.getAccountRaw(address); } - async getAccountRaw(address: string, txCount: number = 0, scrCount: number = 0): Promise { - const assets = await this.assetsService.getAllAccountAssets(); + async getAccountRaw(address: string, withAssets?: boolean): Promise { try { const { account: { nonce, balance, code, codeHash, rootHash, developerReward, ownerAddress, codeMetadata }, @@ -170,7 +168,13 @@ export class AccountService { const shardCount = await this.protocolService.getShardCount(); const shard = AddressUtils.computeShard(AddressUtils.bech32Decode(address), shardCount); - let account = new AccountDetailed({ address, nonce, balance, code, codeHash, rootHash, txCount, scrCount, shard, developerReward, ownerAddress, scamInfo: undefined, assets: assets[address], ownerAssets: assets[ownerAddress], nftCollections: undefined, nfts: undefined }); + let account = new AccountDetailed({ address, nonce, balance, code, codeHash, rootHash, shard, developerReward, ownerAddress, scamInfo: undefined, nftCollections: undefined, nfts: undefined }); + + if (withAssets === true) { + const assets = await this.assetsService.getAllAccountAssets(); + account.assets = assets[address]; + account.ownerAssets = assets[ownerAddress]; + } const codeAttributes = AddressUtils.decodeCodeMetadata(codeMetadata); if (codeAttributes) { diff --git a/src/endpoints/accounts/entities/account.fetch.options.ts b/src/endpoints/accounts/entities/account.fetch.options.ts new file mode 100644 index 000000000..367686761 --- /dev/null +++ b/src/endpoints/accounts/entities/account.fetch.options.ts @@ -0,0 +1,11 @@ +export class AccountFetchOptions { + constructor(init?: Partial) { + Object.assign(this, init); + } + + withGuardianInfo?: boolean; + withTxCount?: boolean; + withScrCount?: boolean; + withTimestamp?: boolean; + withAssets?: boolean; +} diff --git a/src/endpoints/accounts/entities/account.optional.field.options.ts b/src/endpoints/accounts/entities/account.optional.field.options.ts deleted file mode 100644 index fb11f731e..000000000 --- a/src/endpoints/accounts/entities/account.optional.field.options.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum AccountOptionalFieldOption { - scrCount = "scrCount", - txCount = "txCount", -} diff --git a/src/test/unit/controllers/accounts.controller.spec.ts b/src/test/unit/controllers/accounts.controller.spec.ts index 0ba47fec3..ab99de595 100644 --- a/src/test/unit/controllers/accounts.controller.spec.ts +++ b/src/test/unit/controllers/accounts.controller.spec.ts @@ -30,6 +30,7 @@ import { ConfigModule } from "@nestjs/config"; import { AccountDeferred } from "src/endpoints/accounts/entities/account.deferred"; import request = require('supertest'); import { mockAccountService, mockTokenService, mockNftService, mockDelegationLegacyService, mockWaitingListService, mockStakeService, mockTransactionService, mockSmartContractResultService, mockCollectionService, mockTransferService, mockApiConfigService, mockDelegationService } from "./services.mock/account.services.mock"; +import { AccountFetchOptions } from "src/endpoints/accounts/entities/account.fetch.options"; describe('AccountController', () => { let app: INestApplication; @@ -228,6 +229,16 @@ describe('AccountController', () => { .expect(200) .expect(response => { expect(response.body).toEqual(mockAccount); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + mockAccount.address, + new AccountFetchOptions({ + withGuardianInfo: undefined, + withTxCount: undefined, + withScrCount: undefined, + withTimestamp: undefined, + withAssets: undefined, + }) + ); }); }); @@ -240,28 +251,153 @@ describe('AccountController', () => { }); }); - it('should return account details including guardian info when withGuardianInfo is true', async () => { + it('should return account details with withTxCount parameter', async () => { + const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; + const mockAccountWithTxCount = { + ...mockAccount, + txCount: 100, + }; + + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithTxCount); + + await request(app.getHttpServer()) + .get(`/accounts/${address}?withTxCount=true`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(mockAccountWithTxCount); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: undefined, + withTxCount: true, + withScrCount: undefined, + withTimestamp: undefined, + withAssets: undefined, + }) + ); + }); + }); + + it('should return account details with withScrCount parameter', async () => { + const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; + const mockAccountWithScrCount = { + ...mockAccount, + scrCount: 50, + }; + + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithScrCount); + + await request(app.getHttpServer()) + .get(`/accounts/${address}?withScrCount=true`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(mockAccountWithScrCount); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: undefined, + withTxCount: undefined, + withScrCount: true, + withTimestamp: undefined, + withAssets: undefined, + }) + ); + }); + }); + + it('should return account details with withTimestamp parameter', async () => { + const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; + const mockAccountWithTimestamp = { + ...mockAccount, + timestamp: 1708946805, + }; + + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithTimestamp); + + await request(app.getHttpServer()) + .get(`/accounts/${address}?withTimestamp=true`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(mockAccountWithTimestamp); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: undefined, + withTxCount: undefined, + withScrCount: undefined, + withTimestamp: true, + withAssets: undefined, + }) + ); + }); + }); + + it('should return account details with withAssets parameter', async () => { + const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; + const mockAccountWithAssets = { + ...mockAccount, + assets: { + name: "Test Asset", + description: "Test Description", + }, + }; + + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithAssets); + + await request(app.getHttpServer()) + .get(`/accounts/${address}?withAssets=true`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(mockAccountWithAssets); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: undefined, + withTxCount: undefined, + withScrCount: undefined, + withTimestamp: undefined, + withAssets: true, + }) + ); + }); + }); + + it('should return account details with all optional parameters set to true', async () => { const mockAddressList = createMockAccountsList(1); const accountDetails = mockAddressList[0]; const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; - const mockAccountWithGuardianInfo = { + const mockAccountWithAllParams = { ...accountDetails, isGuarded: true, activeGuardianActivationEpoch: 496, activeGuardianAddress: "erd1x5d4p63uwcns8cvyrl4g3qgvwwa2nkt5jdp0vwetc7csqzpjzz0qec58k0", activeGuardianServiceUid: "MultiversXTCSService", + txCount: 100, + scrCount: 50, + timestamp: 1708946805, + assets: { + name: "Test Asset", + description: "Test Description", + }, }; - accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithGuardianInfo); + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithAllParams); await request(app.getHttpServer()) - .get(`/accounts/${address}?withGuardianInfo=true`) + .get(`/accounts/${address}?withGuardianInfo=true&withTxCount=true&withScrCount=true&withTimestamp=true&withAssets=true`) .expect(200) .expect(response => { - expect(response.body).toEqual(mockAccountWithGuardianInfo); - expect(response.body.isGuarded).toStrictEqual(true); + expect(response.body).toEqual(mockAccountWithAllParams); expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( - expect.any(String), undefined, true); + address, + new AccountFetchOptions({ + withGuardianInfo: true, + withTxCount: true, + withScrCount: true, + withTimestamp: true, + withAssets: true, + }) + ); }); }); @@ -281,6 +417,91 @@ describe('AccountController', () => { .expect(response => { expect(response.body).toEqual(mockAccountFilteredFields); expect(Object.keys(response.body).sort()).toEqual(fields.sort()); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: undefined, + withTxCount: undefined, + withScrCount: undefined, + withTimestamp: undefined, + withAssets: undefined, + }) + ); + }); + }); + + it('should return account details with all filters set to false', async () => { + const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; + const mockAccountWithAllParamsFalse = { + ...mockAccount, + }; + + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithAllParamsFalse); + + await request(app.getHttpServer()) + .get(`/accounts/${address}?withGuardianInfo=false&withTxCount=false&withScrCount=false&withTimestamp=false&withAssets=false`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(mockAccountWithAllParamsFalse); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: false, + withTxCount: false, + withScrCount: false, + withTimestamp: false, + withAssets: false, + }) + ); + }); + }); + + it('should return account details with all filters set to true', async () => { + const address = "erd1rf4hv70arudgzus0ymnnsnc4pml0jkywg2xjvzslg0mz4nn2tg7q7k0t6p"; + const mockAccountWithAllParamsTrue = { + ...mockAccount, + guardianInfo: { + guarded: true, + activeGuardian: "erd1guardianaddress", + }, + txCount: 100, + scrCount: 50, + timestamp: 1708946805, + assets: { + name: "Test Asset", + description: "Test Description", + }, + }; + + accountServiceMocks.getAccount.mockResolvedValue(mockAccountWithAllParamsTrue); + + await request(app.getHttpServer()) + .get(`/accounts/${address}?withGuardianInfo=true&withTxCount=true&withScrCount=true&withTimestamp=true&withAssets=true`) + .expect(200) + .expect(response => { + expect(response.body).toEqual(mockAccountWithAllParamsTrue); + expect(accountServiceMocks.getAccount).toHaveBeenCalledWith( + address, + new AccountFetchOptions({ + withGuardianInfo: true, + withTxCount: true, + withScrCount: true, + withTimestamp: true, + withAssets: true, + }) + ); + }); + }); + + it('should throw 404 Not Found when account does not exist', async () => { + const address = "erd1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq6gq4hu"; + accountServiceMocks.getAccount.mockResolvedValue(null); + + await request(app.getHttpServer()) + .get(`${path}/${address}`) + .expect(404) + .expect(response => { + expect(response.body.message).toEqual('Account not found'); }); }); }); From 3f7a0b69fa6ee97fa2d700bb1de2b37727d580b4 Mon Sep 17 00:00:00 2001 From: bogdan-rosianu <51945539+bogdan-rosianu@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:56:57 +0200 Subject: [PATCH 18/20] better treat invalid transactions (#1428) --- .../transactions.batch/transactions.batch.service.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/endpoints/transactions.batch/transactions.batch.service.ts b/src/endpoints/transactions.batch/transactions.batch.service.ts index 2771e72e0..78bea9c89 100644 --- a/src/endpoints/transactions.batch/transactions.batch.service.ts +++ b/src/endpoints/transactions.batch/transactions.batch.service.ts @@ -146,7 +146,11 @@ export class TransactionsBatchService { return transactionBatchItem; } - transaction.hash = result.txHash; + if (result.txHash) { + transaction.hash = result.txHash; + } else { + transactionBatchItem.status = BatchTransactionStatus.invalid; + } return transactionBatchItem; } From 9aa2e7bb8c099246a9d81051fffb7d2bdd9d5b9e Mon Sep 17 00:00:00 2001 From: Liviu Ancas <83750585+liviuf-ancas@users.noreply.github.com> Date: Mon, 13 Jan 2025 19:00:26 +0200 Subject: [PATCH 19/20] Ft/multiple entry/docker file2 (#1434) * added sovereign * added placeholder config * added placeholder config * added placeholder config --------- Co-authored-by: liviuancas-elrond --- config/config.placeholder.yaml | 171 ++++++++++++++++++++++++++++ config/dapp.config.placeholder.json | 17 +++ entrypoint.sh | 101 +++++++++------- 3 files changed, 248 insertions(+), 41 deletions(-) create mode 100644 config/config.placeholder.yaml create mode 100644 config/dapp.config.placeholder.json mode change 100644 => 100755 entrypoint.sh diff --git a/config/config.placeholder.yaml b/config/config.placeholder.yaml new file mode 100644 index 000000000..e764432d9 --- /dev/null +++ b/config/config.placeholder.yaml @@ -0,0 +1,171 @@ +network: 'DAPP_CONFIG' +metaChainShardId: 4294967295 +api: + public: true + publicPort: 3001 + private: true + privatePort: 4001 + websocket: true +cron: + cacheWarmer: true + fastWarm: true + queueWorker: true + elasticUpdater: false +flags: + useRequestCaching: true + useKeepAliveAgent: true + useTracing: false + useRequestLogging: false + useVmQueryTracing: false + processNfts: true + collectionPropertiesFromGateway: false +features: + eventsNotifier: + enabled: false + port: 5674 + url: 'amqp://guest:guest@127.0.0.1:5673' + exchange: 'all_events' + queue: 'api-process-logs-and-events' + guestCaching: + enabled: false + hitsThreshold: 100 + ttl: 12 + transactionPool: + enabled: false + transactionPoolWarmer: + enabled: false + cronExpression: '*/5 * * * * *' + ttlInSeconds: 60 + updateCollectionExtraDetails: + enabled: false + updateAccountExtraDetails: + enabled: false + marketplace: + enabled: false + serviceUrl: 'MARKETPLACE_URL' + exchange: + enabled: false + serviceUrl: 'EXCHANGE_URL' + dataApi: + enabled: false + serviceUrl: 'DATAAPI_URL' + assetsFetch: + enabled: true + assetesUrl: 'ASSETSFETCH_URL' + auth: + enabled: false + maxExpirySeconds: 86400 + acceptedOrigins: + - '' + admins: + - '' + jwtSecret: '' + stakingV4: + enabled: false + cronExpression: '*/5 * * * * *' + activationEpoch: 1043 + nodeEpochsLeft: + enabled: false + transactionProcessor: + enabled: false + maxLookBehind: 100 + transactionCompleted: + enabled: false + maxLookBehind: 100 + logLevel: 'Error' + transactionBatch: + enabled: true + maxLookBehind: 100 + statusChecker: + enabled: false + thresholds: + tokens: 500 + nodes: 3000 + providers: 10 + tokenSupplyCount: 20 + tokenAssets: 20 + tokenAccounts: 500 + tokenTransactions: 500 + nodeValidators: 300 + nftScamInfo: + enabled: false + processNfts: + enabled: false + nftQueueName: 'api-process-nfts' + deadLetterQueueName: 'api-process-nfts-dlq' + tps: + enabled: false + maxLookBehindNonces: 100 + nodesFetch: + enabled: true + serviceUrl: 'NODESFETCH_URL' + tokensFetch: + enabled: true + serviceUrl: 'TOKENSFETCH_URL' + providersFetch: + enabled: true + serviceUrl: 'PROVIDERSFETCH_URL' +image: + width: 600 + height: 600 + type: 'png' +aws: + s3KeyId: '' + s3Secret: '' + s3Bucket: 'devnet-media.elrond.com' + s3Region: '' +urls: + self: 'https://devnet-api.multiversx.com' + elastic: + - 'ELASTICSEARCH_URL' + gateway: + - 'GATEWAY_URL' + verifier: 'https://play-api.multiversx.com' + redis: 'REDIS_IP' + rabbitmq: 'RABBITMQ_URL' + providers: 'PROVIDERS_URL' + delegation: 'DELEGATION_URL' + media: 'https://devnet-media.elrond.com' + nftThumbnails: 'https://devnet-media.elrond.com/nfts/thumbnail' + tmp: '/tmp' + ipfs: 'https://ipfs.io/ipfs' + socket: 'SOCKET_URL' + maiarId: 'https://devnet-id-api.multiversx.com' +indexer: + type: 'elastic' + maxPagination: 10000 +database: + enabled: false + url: 'mongodb://127.0.0.1:27017/api?authSource=admin' + type: 'mysql' + host: 'localhost' + port: 3306 + username: 'root' + password: 'root' + database: 'api' +caching: + cacheTtl: 6 + processTtl: 600 + poolLimit: 50 + cacheDuration: 3 +keepAliveTimeout: + downstream: 61000 + upstream: 60000 +contracts: + esdt: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqzllls8a5w6u' + auction: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqplllst77y4l' + staking: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqllls0lczs7' + delegationManager: 'erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqylllslmq6y6' + delegation: 'erd1qqqqqqqqqqqqqpgq97wezxw6l7lgg7k9rxvycrz66vn92ksh2tssxwf7ep' + metabonding: 'erd1qqqqqqqqqqqqqpgqkg7we73j769ew5we4yyx7uyvnn0nefqgd8ssm6vjc2' +inflation: + - 1952123 + - 1746637 + - 1541150 + - 1335663 + - 1130177 + - 924690 + - 719203 +nftProcess: + parallelism: 1 + maxRetries: 3 diff --git a/config/dapp.config.placeholder.json b/config/dapp.config.placeholder.json new file mode 100644 index 000000000..b042458a2 --- /dev/null +++ b/config/dapp.config.placeholder.json @@ -0,0 +1,17 @@ +{ + "id": "PLACEHOLDER_DAPP_id", + "name": "PLACEHOLDER_DAPP_name", + "egldLabel": "PLACEHOLDER_DAPP_egldLabel", + "decimals": "4", + "egldDenomination": "18", + "gasPerDataByte": "1500", + "apiTimeout": "4000", + "walletConnectDeepLink": "https://maiar.page.link/?apn=com.multiversx.maiar.wallet&isi=1519405832&ibi=com.multiversx.maiar.wallet&link=https://maiar.com/", + "walletConnectBridgeAddresses": [ + "https://bridge.walletconnect.org" + ], + "walletAddress": "PLACEHOLDER_DAPP_walletAddress", + "apiAddress": "PLACEHOLDER_DAPP_apiAddress", + "explorerAddress": "PLACEHOLDER_DAPP_explorerAddress", + "chainId": "PLACEHOLDER_DAPP_chainId" +} \ No newline at end of file diff --git a/entrypoint.sh b/entrypoint.sh old mode 100644 new mode 100755 index 4d199798f..6f380f09a --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,46 +1,65 @@ #!/bin/sh # ENV VARIABLES - # MVX_ENV - defines what config to copy (default devnet) - # ELASTICSEARCH_URL - defines custom elasticsearch url - eg https://devnet-index.multiversx.com - # GATEWAY_URL - defines custom gateway url - eg https://devnet-gateway.multiversx.com - # REDIS_IP - defines redis ip - default 127.0.0.1 - -# CHECK IF ENV IS DEFINED -if [ -n "$MVX_ENV" ] && [ "$MVX_ENV" = "devnet" ]; then - # Copy config file - cp ./config/config.${MVX_ENV}.yaml /app/dist/config/config.yaml - - if [ $? -eq 0 ]; then - echo "Config file copied successfully from config/config.${MVX_ENV}.yaml /app/dist/config/config.yaml" - else - echo "Failed to copy the file." - fi - -else - cp ./config/config.devnet.yaml /app/dist/config/config.yaml - - if [ $? -eq 0 ]; then - echo "Default config file copied successfully from config/config.devnet.yaml /app/dist/config/config.yaml" - else - echo "Failed to copy the file." - fi -fi - -# Replaces urls if defined -if [ -n "$REDIS_IP" ]; then - echo "Redis IP defined: ${REDIS_IP}, replacing in config" - sed -i "s|redis: '127.0.0.1'|redis: '${REDIS_IP}'|g" /app/dist/config/config.yaml -fi - -if [ -n "$ELASTICSEARCH_URL" ]; then - echo "Elasticsearch url defined: ${ELASTICSEARCH_URL}, replacing in config" - sed -i "/^ elastic:/!b; n; s|.*| - '${ELASTICSEARCH_URL}'|" /app/dist/config/config.yaml -fi - -if [ -n "$GATEWAY_URL" ]; then - echo "Gateway url defined: ${GATEWAY_URL}, replacing in config" - sed -i "/^ gateway:/!b; n; s|.*| - '${GATEWAY_URL}'|" /app/dist/config/config.yaml -fi + # MVX_ENV=devnet + # DAPP_CONFIG=devnet + # REDIS_IP=127.0.0.1 + # ELASTICSEARCH_URL=https://devnet-index.multiversx.com + # GATEWAY_URL=https://devnet-gateway.multiversx.com + # RABBITMQ_URL=amqp://127.0.0.1:5672 + # PROVIDERS_URL=https://devnet-delegation-api.multiversx.com/providers + # DELEGATION_URL=https://devnet-delegation-api.multiversx.com + # SOCKET_URL=devnet-socket-api.multiversx.com + # NODESFETCH_URL= https://devnet-api.multiversx.com + # TOKENSFETCH_URL= https://devnet-api.multiversx.com + # PROVIDERSFETCH_URL= https://devnet-api.multiversx.com + # DATAAPI_URL=https://devnet-data-api.multiversx.com + # EXCHANGE_URL=https://devnet-graph.xexchange.com/graphql + # MARKETPLACE_URL=https://devnet-nfts-graph.multiversx.com/graphql + # ASSETSFETCH_URL=https://tools.multiversx.com/assets-cdn + # PLACEHOLDER_DAPP_id=devnet + # PLACEHOLDER_DAPP_name=Devnet + # PLACEHOLDER_DAPP_egldLabel=xEGLD + # PLACEHOLDER_DAPP_walletAddress=https://devnet-wallet.multiversx.com + # PLACEHOLDER_DAPP_apiAddress=https://devnet-api.multiversx.com + # PLACEHOLDER_DAPP_explorerAddress=http://devnet-explorer.multiversx.com + # PLACEHOLDER_DAPP_chainId=D +env_vars_with_defaults="MVX_ENV=devnet DAPP_CONFIG=devnet REDIS_IP=127.0.0.1 ELASTICSEARCH_URL=https://devnet-index.multiversx.com GATEWAY_URL=https://devnet-gateway.multiversx.com RABBITMQ_URL=amqp://127.0.0.1:5672 PROVIDERS_URL=https://devnet-delegation-api.multiversx.com/providers DATAAPI_URL=https://devnet-data-api.multiversx.com EXCHANGE_URL=https://devnet-graph.xexchange.com/graphql MARKETPLACE_URL=https://devnet-nfts-graph.multiversx.com/graphql ASSETSFETCH_URL=https://tools.multiversx.com/assets-cdn DELEGATION_URL=https://devnet-delegation-api.multiversx.com SOCKET_URL=devnet-socket-api.multiversx.com NODESFETCH_URL=https://devnet-api.multiversx.com TOKENSFETCH_URL=https://devnet-api.multiversx.com PROVIDERSFETCH_URL=https://devnet-api.multiversx.com PLACEHOLDER_DAPP_id=devnet PLACEHOLDER_DAPP_name=Devnet PLACEHOLDER_DAPP_egldLabel=xEGLD PLACEHOLDER_DAPP_walletAddress=https://devnet-wallet.multiversx.com PLACEHOLDER_DAPP_apiAddress=https://devnet-api.multiversx.com PLACEHOLDER_DAPP_explorerAddress=http://devnet-explorer.multiversx.com PLACEHOLDER_DAPP_chainId=D" + +replace_placeholder() { + local var_name=$1 + local var_value=$2 + + case $var_name in + PLACEHOLDER_DAPP*) + echo "Var ${var_name} defined, replacing ${var_value} in /app/config/dapp.config.placeholder.json" + sed -i "s|${var_name}|${var_value}|g" /app/config/dapp.config.placeholder.json + ;; + *) + echo "Var ${var_name} defined, replacing ${var_value} in /app/dist/config/config.yaml" + sed -i "s|${var_name}|${var_value}|g" /app/dist/config/config.yaml + ;; + esac + +} + +# Loop through each environment variable +for entry in $env_vars_with_defaults; do + # Split the entry into name and value + var_name=$(echo $entry | cut -d= -f1) + default_value=$(echo $entry | cut -d= -f2) + + # Use the environment variable value if defined; otherwise, use the default + eval "value=\${$var_name:-$default_value}" + + cp ./config/config.placeholder.yaml /app/dist/config/config.yaml + if [ $? -eq 0 ]; then + echo "Config file copied successfully from config/config.placeholder.yaml /app/dist/config/config.yaml" + fi + + # Execute the function with the variable name and value + replace_placeholder "$var_name" "$value" + +done exec /usr/local/bin/node dist/src/main.js From c38c4facd9968060889c126299804d1502cf6414 Mon Sep 17 00:00:00 2001 From: Catalin Faur <52102171+cfaur09@users.noreply.github.com> Date: Tue, 14 Jan 2025 17:04:20 +0200 Subject: [PATCH 20/20] add support for xoxno data API provider (#1437) --- src/common/data-api/data-api.service.ts | 7 ++++--- src/common/data-api/entities/data-api.token.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/common/data-api/data-api.service.ts b/src/common/data-api/data-api.service.ts index bf0d50180..2b43b1304 100644 --- a/src/common/data-api/data-api.service.ts +++ b/src/common/data-api/data-api.service.ts @@ -72,17 +72,18 @@ export class DataApiService { } try { - const [cexTokensRaw, xExchangeTokensRaw, hatomTokensRaw] = await Promise.all([ + const [cexTokensRaw, xExchangeTokensRaw, hatomTokensRaw, xoxnoTokensRaw] = await Promise.all([ this.apiService.get(`${this.apiConfigService.getDataApiServiceUrl()}/v1/tokens/cex?fields=identifier`), this.apiService.get(`${this.apiConfigService.getDataApiServiceUrl()}/v1/tokens/xexchange?fields=identifier`), this.apiService.get(`${this.apiConfigService.getDataApiServiceUrl()}/v1/tokens/hatom?fields=identifier`), + this.apiService.get(`${this.apiConfigService.getDataApiServiceUrl()}/v1/tokens/xoxno?fields=identifier`), ]); const cexTokens: DataApiToken[] = cexTokensRaw.data.map((token: any) => new DataApiToken({ identifier: token.identifier, market: 'cex' })); const xExchangeTokens: DataApiToken[] = xExchangeTokensRaw.data.map((token: any) => new DataApiToken({ identifier: token.identifier, market: 'xexchange' })); const hatomTokens: DataApiToken[] = hatomTokensRaw.data.map((token: any) => new DataApiToken({ identifier: token.identifier, market: 'hatom' })); - - const tokens = [...cexTokens, ...xExchangeTokens, ...hatomTokens].toRecord(x => x.identifier); + const xoxnoTokens: DataApiToken[] = xoxnoTokensRaw.data.map((token: any) => new DataApiToken({ identifier: token.identifier, market: 'xoxno' })); + const tokens = [...cexTokens, ...xExchangeTokens, ...hatomTokens, ...xoxnoTokens].toRecord(x => x.identifier); return tokens; } catch (error) { this.logger.error(`An unexpected error occurred while fetching tokens from Data API.`); diff --git a/src/common/data-api/entities/data-api.token.ts b/src/common/data-api/entities/data-api.token.ts index 8695f31fb..bc2b273c3 100644 --- a/src/common/data-api/entities/data-api.token.ts +++ b/src/common/data-api/entities/data-api.token.ts @@ -4,5 +4,5 @@ export class DataApiToken { } identifier: string = ''; - market: 'cex' | 'xexchange' | 'hatom' = 'cex'; + market: 'cex' | 'xexchange' | 'hatom' | 'xoxno' = 'cex'; }