From e8a933af25438cd7db7ba8f65dce1e3107c0060e Mon Sep 17 00:00:00 2001 From: GuticaStefan Date: Tue, 7 Apr 2026 16:13:30 +0300 Subject: [PATCH] search after support --- src/common/entities/query.pagination.ts | 1 + .../circuit.breaker.proxy.service.ts | 6 ++- .../elastic/elastic.indexer.service.ts | 6 +-- .../indexer/entities/account.history.ts | 4 +- .../indexer/entities/account.token.history.ts | 4 +- src/common/indexer/entities/account.ts | 4 +- src/common/indexer/entities/block.ts | 4 +- src/common/indexer/entities/collection.ts | 4 +- .../indexer/entities/elastic.sortable.ts | 3 ++ src/common/indexer/entities/events.ts | 1 + src/common/indexer/entities/index.ts | 1 + src/common/indexer/entities/miniblock.ts | 4 +- .../indexer/entities/provider.delegators.ts | 1 + src/common/indexer/entities/round.ts | 4 +- src/common/indexer/entities/sc.deploy.ts | 4 +- src/common/indexer/entities/sc.result.ts | 4 +- src/common/indexer/entities/tag.ts | 4 +- src/common/indexer/entities/token.account.ts | 3 +- .../indexer/entities/transaction.log.ts | 6 ++- .../indexer/entities/transaction.receipt.ts | 4 +- src/common/indexer/entities/transaction.ts | 4 +- src/endpoints/accounts/account.controller.ts | 41 +++++++++++++------ .../applications/application.controller.ts | 4 +- src/endpoints/blocks/block.controller.ts | 4 +- .../collections/collection.controller.ts | 15 ++++--- src/endpoints/events/events.controller.ts | 4 +- .../marketplace/nft.marketplace.controller.ts | 12 ++++-- src/endpoints/mex/mex.controller.ts | 2 +- .../miniblocks/mini.block.controller.ts | 4 +- src/endpoints/nfts/nft.controller.ts | 12 ++++-- src/endpoints/nfts/nft.service.ts | 4 +- src/endpoints/nfttags/tag.controller.ts | 4 +- .../providers/provider.controller.ts | 7 +++- .../sc-results/scresult.controller.ts | 4 +- src/endpoints/tokens/token.controller.ts | 15 ++++--- .../transactions/transaction.controller.ts | 4 +- .../transfers/transfer.controller.ts | 4 +- 37 files changed, 152 insertions(+), 64 deletions(-) create mode 100644 src/common/indexer/entities/elastic.sortable.ts diff --git a/src/common/entities/query.pagination.ts b/src/common/entities/query.pagination.ts index 65950b1de..047764ad8 100644 --- a/src/common/entities/query.pagination.ts +++ b/src/common/entities/query.pagination.ts @@ -8,4 +8,5 @@ export class QueryPagination { before?: number; after?: number; + searchAfter?: string; } diff --git a/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts b/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts index 7cff25aea..ea79a21bd 100644 --- a/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts +++ b/src/common/indexer/elastic/circuit-breaker/circuit.breaker.proxy.service.ts @@ -68,8 +68,10 @@ export class EsCircuitBreakerProxy { } // eslint-disable-next-line require-await - async getList(index: string, id: string, query: ElasticQuery): Promise { - return this.withCircuitBreaker(() => this.elasticService.getList(index, id, query)); + async getList(index: string, id: string, query: ElasticQuery, searchAfter?: string): Promise { + //TODO: update package + // @ts-ignore + return this.withCircuitBreaker(() => this.elasticService.getList(index, id, query, undefined, searchAfter)); } // eslint-disable-next-line require-await diff --git a/src/common/indexer/elastic/elastic.indexer.service.ts b/src/common/indexer/elastic/elastic.indexer.service.ts index c3189b334..cc06c47ea 100644 --- a/src/common/indexer/elastic/elastic.indexer.service.ts +++ b/src/common/indexer/elastic/elastic.indexer.service.ts @@ -136,7 +136,7 @@ export class ElasticIndexerService implements IndexerInterface { query = this.buildTokenFilter(query, filter); - return await this.elasticService.getList('accountsesdt', 'token', query); + return await this.elasticService.getList('accountsesdt', 'token', query, queryPagination.searchAfter); } async getTokenAccountsCount(identifier: string): Promise { @@ -294,7 +294,7 @@ export class ElasticIndexerService implements IndexerInterface { .withPagination({ from: pagination.from, size: pagination.size }) .withSort([timestamp, nonce]); - const elasticOperations = await this.elasticService.getList('operations', 'txHash', elasticQuery); + const elasticOperations = await this.elasticService.getList('operations', 'txHash', elasticQuery, pagination.searchAfter); this.bulkProcessTransactions(elasticOperations); @@ -559,7 +559,7 @@ export class ElasticIndexerService implements IndexerInterface { .withPagination({ from: pagination.from, size: pagination.size }) .withSort([timestamp, nonce]); - const transactions = await this.elasticService.getList('operations', 'txHash', elasticQuery); + const transactions = await this.elasticService.getList('operations', 'txHash', elasticQuery, pagination.searchAfter); this.bulkProcessTransactions(transactions); diff --git a/src/common/indexer/entities/account.history.ts b/src/common/indexer/entities/account.history.ts index 80ba09b75..e3b8ca794 100644 --- a/src/common/indexer/entities/account.history.ts +++ b/src/common/indexer/entities/account.history.ts @@ -1,4 +1,6 @@ -export interface AccountHistory { +import { ElasticSortable } from "./elastic.sortable"; + +export interface AccountHistory extends ElasticSortable { address: string; timestamp: number; balance: string; diff --git a/src/common/indexer/entities/account.token.history.ts b/src/common/indexer/entities/account.token.history.ts index 4cbc4a5a5..1e6f384cf 100644 --- a/src/common/indexer/entities/account.token.history.ts +++ b/src/common/indexer/entities/account.token.history.ts @@ -1,4 +1,6 @@ -export interface AccountTokenHistory { +import { ElasticSortable } from "./elastic.sortable"; + +export interface AccountTokenHistory extends ElasticSortable { address: string; timestamp: number; balance: string; diff --git a/src/common/indexer/entities/account.ts b/src/common/indexer/entities/account.ts index fc5ff3296..d46241b14 100644 --- a/src/common/indexer/entities/account.ts +++ b/src/common/indexer/entities/account.ts @@ -1,4 +1,6 @@ -export interface Account { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Account extends ElasticSortable { address: string; nonce: number; timestampMs: number; diff --git a/src/common/indexer/entities/block.ts b/src/common/indexer/entities/block.ts index ebfd4aef1..eb9c7bbe0 100644 --- a/src/common/indexer/entities/block.ts +++ b/src/common/indexer/entities/block.ts @@ -1,4 +1,6 @@ -export interface Block { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Block extends ElasticSortable { hash: string; nonce: number; round: number; diff --git a/src/common/indexer/entities/collection.ts b/src/common/indexer/entities/collection.ts index 5190e17f6..b5ce61a03 100644 --- a/src/common/indexer/entities/collection.ts +++ b/src/common/indexer/entities/collection.ts @@ -1,3 +1,5 @@ +import { ElasticSortable } from "./elastic.sortable"; + export interface CollectionProperties { canMint?: boolean; canBurn?: boolean; @@ -11,7 +13,7 @@ export interface CollectionProperties { canCreateMultiShard?: boolean; } -export interface Collection { +export interface Collection extends ElasticSortable { _id: string; name: string; ticker: string; diff --git a/src/common/indexer/entities/elastic.sortable.ts b/src/common/indexer/entities/elastic.sortable.ts new file mode 100644 index 000000000..f5847fb47 --- /dev/null +++ b/src/common/indexer/entities/elastic.sortable.ts @@ -0,0 +1,3 @@ +export interface ElasticSortable { + searchAfter?: string; +} \ No newline at end of file diff --git a/src/common/indexer/entities/events.ts b/src/common/indexer/entities/events.ts index add007142..82c26262d 100644 --- a/src/common/indexer/entities/events.ts +++ b/src/common/indexer/entities/events.ts @@ -1,4 +1,5 @@ export class Events { + searchAfter?: string; _id: string = ''; logAddress: string = ''; identifier: string = ''; diff --git a/src/common/indexer/entities/index.ts b/src/common/indexer/entities/index.ts index 2cc7597c3..1c8d7045a 100644 --- a/src/common/indexer/entities/index.ts +++ b/src/common/indexer/entities/index.ts @@ -14,3 +14,4 @@ export { TokenAccount, TokenType } from './token.account'; export { Transaction } from './transaction'; export { TransactionLog, TransactionLogEvent, ElasticTransactionLogEvent } from './transaction.log'; export { TransactionReceipt } from './transaction.receipt'; +export { ElasticSortable } from './elastic.sortable'; diff --git a/src/common/indexer/entities/miniblock.ts b/src/common/indexer/entities/miniblock.ts index 71e086355..fafaa082e 100644 --- a/src/common/indexer/entities/miniblock.ts +++ b/src/common/indexer/entities/miniblock.ts @@ -1,4 +1,6 @@ -export interface MiniBlock { +import { ElasticSortable } from "./elastic.sortable"; + +export interface MiniBlock extends ElasticSortable { miniBlockHash: string; senderShard: number; receiverShard: number; diff --git a/src/common/indexer/entities/provider.delegators.ts b/src/common/indexer/entities/provider.delegators.ts index b4f27bbe5..c81c106a0 100644 --- a/src/common/indexer/entities/provider.delegators.ts +++ b/src/common/indexer/entities/provider.delegators.ts @@ -1,4 +1,5 @@ export class ProviderDelegators { + searchAfter?: string; contract: string = ''; address: string = ''; activeStake: string = ''; diff --git a/src/common/indexer/entities/round.ts b/src/common/indexer/entities/round.ts index 867c67d47..5a248665f 100644 --- a/src/common/indexer/entities/round.ts +++ b/src/common/indexer/entities/round.ts @@ -1,4 +1,6 @@ -export interface Round { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Round extends ElasticSortable { round: number, signersIndexes: number[], blockWasProposed: boolean, diff --git a/src/common/indexer/entities/sc.deploy.ts b/src/common/indexer/entities/sc.deploy.ts index 0233d0e72..942130871 100644 --- a/src/common/indexer/entities/sc.deploy.ts +++ b/src/common/indexer/entities/sc.deploy.ts @@ -1,4 +1,6 @@ -export interface ScDeploy { +import { ElasticSortable } from "./elastic.sortable"; + +export interface ScDeploy extends ElasticSortable { address: string; contract: string; deployTxHash: string; diff --git a/src/common/indexer/entities/sc.result.ts b/src/common/indexer/entities/sc.result.ts index 882898b59..104acafe4 100644 --- a/src/common/indexer/entities/sc.result.ts +++ b/src/common/indexer/entities/sc.result.ts @@ -1,4 +1,6 @@ -export interface ScResult { +import { ElasticSortable } from "./elastic.sortable"; + +export interface ScResult extends ElasticSortable { scHash: string nonce: number; gasLimit: string; diff --git a/src/common/indexer/entities/tag.ts b/src/common/indexer/entities/tag.ts index c56f9077f..b7e5a91a7 100644 --- a/src/common/indexer/entities/tag.ts +++ b/src/common/indexer/entities/tag.ts @@ -1,4 +1,6 @@ -export interface Tag { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Tag extends ElasticSortable { count: number; tag: string; } diff --git a/src/common/indexer/entities/token.account.ts b/src/common/indexer/entities/token.account.ts index 0a05b0332..d14dbbec9 100644 --- a/src/common/indexer/entities/token.account.ts +++ b/src/common/indexer/entities/token.account.ts @@ -1,6 +1,7 @@ import { registerEnumType } from "@nestjs/graphql"; +import { ElasticSortable } from "./elastic.sortable"; -export interface TokenAccount { +export interface TokenAccount extends ElasticSortable { identifier: string; address: string; balance: string; diff --git a/src/common/indexer/entities/transaction.log.ts b/src/common/indexer/entities/transaction.log.ts index 3a71225a3..79f048902 100644 --- a/src/common/indexer/entities/transaction.log.ts +++ b/src/common/indexer/entities/transaction.log.ts @@ -1,4 +1,6 @@ -export interface TransactionLog { +import { ElasticSortable } from "./elastic.sortable"; + +export interface TransactionLog extends ElasticSortable { id: string; originalTxHash: string; address: string; @@ -14,7 +16,7 @@ export interface TransactionLogEvent { order: number; } -export interface ElasticTransactionLogEvent { +export interface ElasticTransactionLogEvent extends ElasticSortable { address: string; identifier: string; topics: string[]; diff --git a/src/common/indexer/entities/transaction.receipt.ts b/src/common/indexer/entities/transaction.receipt.ts index 61a91d87c..043402ed2 100644 --- a/src/common/indexer/entities/transaction.receipt.ts +++ b/src/common/indexer/entities/transaction.receipt.ts @@ -1,4 +1,6 @@ -export interface TransactionReceipt { +import { ElasticSortable } from "./elastic.sortable"; + +export interface TransactionReceipt extends ElasticSortable { receiptHash: string; value: string; sender: string; diff --git a/src/common/indexer/entities/transaction.ts b/src/common/indexer/entities/transaction.ts index a65abd524..92f7e59cd 100644 --- a/src/common/indexer/entities/transaction.ts +++ b/src/common/indexer/entities/transaction.ts @@ -1,4 +1,6 @@ -export interface Transaction { +import { ElasticSortable } from "./elastic.sortable"; + +export interface Transaction extends ElasticSortable { hash: string; miniBlockHash: string; nonce: number; diff --git a/src/endpoints/accounts/account.controller.ts b/src/endpoints/accounts/account.controller.ts index 40910e8a7..768fedd26 100644 --- a/src/endpoints/accounts/account.controller.ts +++ b/src/endpoints/accounts/account.controller.ts @@ -85,6 +85,7 @@ export class AccountController { @ApiOkResponse({ type: [Account] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'ownerAddress', description: 'Search by owner address', required: false }) @ApiQuery({ name: 'sort', description: 'Sort criteria (balance / timestamp)', required: false, enum: AccountSort }) @ApiQuery({ name: 'order', description: 'Sort order (asc/desc)', required: false, enum: SortOrder }) @@ -102,6 +103,7 @@ export class AccountController { getAccounts( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query("ownerAddress", ParseAddressPipe) ownerAddress?: string, @Query("name") name?: string, @Query("tags", ParseArrayPipe) tags?: string[], @@ -136,7 +138,7 @@ export class AccountController { }); queryOptions.validate(size); return this.accountService.getAccounts( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), queryOptions, ); } @@ -277,6 +279,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('subType', new ParseEnumPipe(NftSubType)) subType?: NftSubType, @Query('search') search?: string, @@ -288,7 +291,7 @@ export class AccountController { @Query('mexPairType', new ParseEnumArrayPipe(MexPairType)) mexPairType?: MexPairType[], ): Promise { try { - return await this.tokenService.getTokensForAddress(address, new QueryPagination({ from, size }), new TokenFilter({ type, subType, search, name, identifier, identifiers, includeMetaESDT, mexPairType })); + return await this.tokenService.getTokensForAddress(address, new QueryPagination({ from, size, searchAfter }), new TokenFilter({ type, subType, search, name, identifier, identifiers, includeMetaESDT, mexPairType })); } catch (error) { this.logger.error(`Error in getAccountTokens for address ${address}`); this.logger.error(error); @@ -392,6 +395,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @Query('subType', new ParseEnumArrayPipe(NftSubType)) subType?: NftSubType[], @@ -418,7 +422,7 @@ export class AccountController { canAddUri, canTransferRole, excludeMetaESDT, - }), new QueryPagination({ from, size })); + }), new QueryPagination({ from, size, searchAfter })); } @Get("/accounts/:address/roles/collections/count") @@ -493,13 +497,14 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('owner', ParseAddressPipe) owner?: string, @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 })); + return await this.tokenService.getTokensWithRolesForAddress(address, new TokenWithRolesFilter({ search, owner, canMint, canBurn, includeMetaESDT }), new QueryPagination({ from, size, searchAfter })); } @Get("/accounts/:address/roles/tokens/count") @@ -674,7 +679,7 @@ export class AccountController { return await this.nftService.getNftsForAddress( address, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new NftFilter({ search, identifiers, @@ -871,6 +876,7 @@ export class AccountController { @ApiOkResponse({ type: [Transaction] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'sender', description: 'Address of the transaction sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -901,6 +907,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('token') token?: string, @@ -949,7 +956,7 @@ export class AccountController { withRelayedScresults, }); TransactionFilter.validate(transactionFilter, size); - return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options, address, fields); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size, searchAfter }), options, address, fields); } @Get("/accounts/:address/transactions/count") @@ -1018,6 +1025,7 @@ export class AccountController { @ApplyComplexity({ target: TransactionDetailed }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'sender', description: 'Address of the transfer sender', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -1047,6 +1055,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('token') token?: string, @@ -1097,7 +1106,7 @@ export class AccountController { withTxsRelayedByAddress, isScCall, }), - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, fields ); @@ -1213,8 +1222,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.accountService.getAccountDeploys(new QueryPagination({ from, size }), address); + return this.accountService.getAccountDeploys(new QueryPagination({ from, size, searchAfter }), address); } @Get("/accounts/:address/deploys/count") @@ -1239,8 +1249,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.accountService.getAccountContracts(new QueryPagination({ from, size }), address); + return this.accountService.getAccountContracts(new QueryPagination({ from, size, searchAfter }), address); } @Get("/accounts/:address/contracts/count") @@ -1278,8 +1289,9 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return this.scResultService.getAccountScResults(address, new QueryPagination({ from, size })); + return this.scResultService.getAccountScResults(address, new QueryPagination({ from, size, searchAfter })); } @Get("/accounts/:address/results/count") @@ -1317,12 +1329,13 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, ): Promise { return this.accountService.getAccountHistory( address, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new AccountHistoryFilter({ before, after })); } @@ -1375,6 +1388,7 @@ export class AccountController { @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, @Query('identifier', ParseArrayPipe) identifier?: string[], @@ -1382,7 +1396,7 @@ export class AccountController { ): Promise { return await this.accountService.getAccountEsdtHistory( address, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new AccountHistoryFilter({ before, after, identifiers: identifier, token })); } @@ -1416,6 +1430,7 @@ export class AccountController { @Param('tokenIdentifier', ParseTokenOrNftPipe) tokenIdentifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, ): Promise { @@ -1426,7 +1441,7 @@ export class AccountController { return await this.accountService.getAccountTokenHistory( address, tokenIdentifier, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new AccountHistoryFilter({ before, after })); } } diff --git a/src/endpoints/applications/application.controller.ts b/src/endpoints/applications/application.controller.ts index e9fcbd0bc..085062acb 100644 --- a/src/endpoints/applications/application.controller.ts +++ b/src/endpoints/applications/application.controller.ts @@ -19,19 +19,21 @@ export class ApplicationController { @ApiOkResponse({ type: [Application] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'before', description: 'Before timestamp or timestampMs', required: false }) @ApiQuery({ name: 'after', description: 'After timestamp or timestampMs', required: false }) @ApiQuery({ name: 'withTxCount', description: 'Include transaction count', required: false, type: Boolean }) async getApplications( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('before', TimestampParsePipe) before?: number, @Query('after', TimestampParsePipe) after?: number, @Query('withTxCount', new ParseBoolPipe()) withTxCount?: boolean, ): Promise { const applicationFilter = new ApplicationFilter({ before, after, withTxCount }); return await this.applicationService.getApplications( - new QueryPagination({ size, from }), + new QueryPagination({ size, from, searchAfter }), applicationFilter ); } diff --git a/src/endpoints/blocks/block.controller.ts b/src/endpoints/blocks/block.controller.ts index 71e832222..8541dd078 100644 --- a/src/endpoints/blocks/block.controller.ts +++ b/src/endpoints/blocks/block.controller.ts @@ -22,6 +22,7 @@ export class BlockController { @ApiQuery({ name: 'epoch', description: 'Filter by epoch', required: false }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'nonce', description: 'Filter by nonce', required: false }) @ApiQuery({ name: 'hashes', description: 'Search by blocks hashes, comma-separated', required: false }) @ApiQuery({ name: 'order', description: 'Order blocks (asc/desc) by timestamp', required: false, enum: SortOrder }) @@ -29,6 +30,7 @@ export class BlockController { getBlocks( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('shard', ParseIntPipe) shard?: number, @Query('proposer', ParseBlsHashPipe) proposer?: string, @Query('validator', ParseBlsHashPipe) validator?: string, @@ -42,7 +44,7 @@ export class BlockController { new BlockFilter( { shard, proposer, validator, epoch, nonce, hashes, order }), new QueryPagination( - { from, size }), withProposerIdentity); + { from, size, searchAfter }), withProposerIdentity); } @Get("/blocks/count") diff --git a/src/endpoints/collections/collection.controller.ts b/src/endpoints/collections/collection.controller.ts index a49f6483c..af7166e43 100644 --- a/src/endpoints/collections/collection.controller.ts +++ b/src/endpoints/collections/collection.controller.ts @@ -62,6 +62,7 @@ export class CollectionController { async getNftCollections( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseCollectionArrayPipe) identifiers?: string[], @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @@ -79,7 +80,7 @@ export class CollectionController { @Query('sort', new ParseEnumPipe(SortCollections)) sort?: SortCollections, @Query('order', new ParseEnumPipe(SortOrder)) order?: SortOrder, ): Promise { - return await this.collectionService.getNftCollections(new QueryPagination({ from, size }), new CollectionFilter({ + return await this.collectionService.getNftCollections(new QueryPagination({ from, size, searchAfter }), new CollectionFilter({ search, type, subType, @@ -233,6 +234,7 @@ export class CollectionController { @Param('collection', ParseCollectionPipe) collection: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], @Query('name') name?: string, @@ -256,7 +258,7 @@ export class CollectionController { } return await this.nftService.getNfts( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new NftFilter({ search, identifiers, collection, name, tags, creator, hasUris, isWhitelistedStorage, isNsfw, traits, nonceBefore, nonceAfter, sort, order }), new NftQueryOptions({ withOwner, withSupply, withAssets }), ); @@ -307,8 +309,9 @@ export class CollectionController { @Param('identifier', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - const owners = await this.nftService.getCollectionOwners(identifier, new QueryPagination({ from, size })); + const owners = await this.nftService.getCollectionOwners(identifier, new QueryPagination({ from, size, searchAfter })); if (!owners) { throw new HttpException('Collection not found', HttpStatus.NOT_FOUND); } @@ -345,6 +348,7 @@ export class CollectionController { @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -389,7 +393,7 @@ export class CollectionController { }); TransactionFilter.validate(transactionFilter, size); - return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size, searchAfter }), options); } @Get("/collections/:collection/transactions/count") @@ -470,6 +474,7 @@ export class CollectionController { @Param('collection', ParseCollectionPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -509,7 +514,7 @@ export class CollectionController { after, order, round, - }), new QueryPagination({ from, size }), options); + }), new QueryPagination({ from, size, searchAfter }), options); } @Get("/collections/:collection/transfers/count") diff --git a/src/endpoints/events/events.controller.ts b/src/endpoints/events/events.controller.ts index dc7ce3a0a..437561e8a 100644 --- a/src/endpoints/events/events.controller.ts +++ b/src/endpoints/events/events.controller.ts @@ -20,6 +20,7 @@ export class EventsController { @ApiOkResponse({ type: [Events] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'address', description: 'Event address', required: false }) @ApiQuery({ name: 'logAddress', description: 'Event log address', required: false }) @ApiQuery({ name: 'identifier', description: 'Event identifier', required: false }) @@ -32,6 +33,7 @@ export class EventsController { async getEvents( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('address', ParseAddressPipe) address: string, @Query('logAddress', ParseAddressPipe) logAddress: string, @Query('identifier') identifier: string, @@ -44,7 +46,7 @@ export class EventsController { ): Promise { const topicsArray = topics ? (Array.isArray(topics) ? topics : [topics]) : []; return await this.eventsService.getEvents( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new EventsFilter({ address, logAddress, identifier, txHash, shard, after, before, order, topics: topicsArray })); } diff --git a/src/endpoints/marketplace/nft.marketplace.controller.ts b/src/endpoints/marketplace/nft.marketplace.controller.ts index 8a07a7374..b529f55ce 100644 --- a/src/endpoints/marketplace/nft.marketplace.controller.ts +++ b/src/endpoints/marketplace/nft.marketplace.controller.ts @@ -20,11 +20,13 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Explore auctions', description: 'Returns auctions available in marketplaces ' }) @ApiOkResponse({ type: [Auctions] }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) async getAuctions( @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { return await this.nftMarketplaceService.getAuctions( - new QueryPagination({ size }), + new QueryPagination({ size, searchAfter }), ); } @@ -77,15 +79,17 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Account auctions', description: 'Returns account auctions for a given address' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'status', description: 'Returns auctions with specified status', required: false }) @ApiOkResponse({ type: Auction }) async getAccountAuctions( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('status', new ParseEnumPipe(AuctionStatus)) status?: AuctionStatus, ): Promise { - const account = await this.nftMarketplaceService.getAccountAuctions(new QueryPagination({ from, size }), address, status); + const account = await this.nftMarketplaceService.getAccountAuctions(new QueryPagination({ from, size, searchAfter }), address, status); if (!account) { throw new NotFoundException('Account not found'); } @@ -116,12 +120,14 @@ export class NftMarketplaceController { @ApiOperation({ summary: 'Collection auctions', description: 'Returns all auctions for a specific collection ' }) @ApiOkResponse({ type: [Auctions] }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'collection', description: 'Collection identifier', required: true }) async getCollectionAuctions( @Param('collection', ParseCollectionPipe) collection: string, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - return await this.nftMarketplaceService.getCollectionAuctions(new QueryPagination({ size }), collection); + return await this.nftMarketplaceService.getCollectionAuctions(new QueryPagination({ size, searchAfter }), collection); } @Get('/collections/:collection/auctions/count') diff --git a/src/endpoints/mex/mex.controller.ts b/src/endpoints/mex/mex.controller.ts index b520de22a..1da343e90 100644 --- a/src/endpoints/mex/mex.controller.ts +++ b/src/endpoints/mex/mex.controller.ts @@ -133,7 +133,7 @@ export class MexController { @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) async getMexFarms( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, - @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number + @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, ): Promise { return await this.mexFarmsService.getMexFarms(new QueryPagination({ from, size })); } diff --git a/src/endpoints/miniblocks/mini.block.controller.ts b/src/endpoints/miniblocks/mini.block.controller.ts index d530f598d..864b26b00 100644 --- a/src/endpoints/miniblocks/mini.block.controller.ts +++ b/src/endpoints/miniblocks/mini.block.controller.ts @@ -18,15 +18,17 @@ export class MiniBlockController { @ApiOkResponse({ type: [MiniBlockDetailed] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'hashes', description: 'Filter by a comma-separated list of miniblocks hashes', required: false }) @ApiQuery({ name: 'type', description: 'Sorting criteria by type', required: false, enum: MiniBlockType }) async getMiniBlocks( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('hashes', ParseArrayPipe) hashes?: string[], @Query('type', new ParseEnumPipe(MiniBlockType)) type?: MiniBlockType, ): Promise { - return await this.miniBlockService.getMiniBlocks(new QueryPagination({ from, size }), new MiniBlockFilter({ hashes, type })); + return await this.miniBlockService.getMiniBlocks(new QueryPagination({ from, size, searchAfter }), new MiniBlockFilter({ hashes, type })); } @Get("/miniblocks/:miniBlockHash") diff --git a/src/endpoints/nfts/nft.controller.ts b/src/endpoints/nfts/nft.controller.ts index 3c04ea1e8..374f631cd 100644 --- a/src/endpoints/nfts/nft.controller.ts +++ b/src/endpoints/nfts/nft.controller.ts @@ -60,6 +60,7 @@ export class NftController { async getNfts( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search?: string, @Query('identifiers', ParseNftArrayPipe) identifiers?: string[], @Query('type', new ParseEnumArrayPipe(NftType)) type?: NftType[], @@ -81,7 +82,7 @@ export class NftController { @Query('withSupply', ParseBoolPipe) withSupply?: boolean, ): Promise { return await this.nftService.getNfts( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new NftFilter({ search, identifiers, @@ -253,8 +254,9 @@ export class NftController { @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { - const owners = await this.nftService.getNftOwners(identifier, new QueryPagination({ from, size })); + const owners = await this.nftService.getNftOwners(identifier, new QueryPagination({ from, size, searchAfter })); if (owners === undefined) { throw new HttpException('NFT not found', HttpStatus.NOT_FOUND); } @@ -303,6 +305,7 @@ export class NftController { @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -341,7 +344,7 @@ export class NftController { }); TransactionFilter.validate(transactionFilter, size); - return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size }), options); + return await this.transactionService.getTransactions(transactionFilter, new QueryPagination({ from, size, searchAfter }), options); } @Get("/nfts/:identifier/transactions/count") @@ -414,6 +417,7 @@ export class NftController { @Param('identifier', ParseNftPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -446,7 +450,7 @@ export class NftController { before, after, order, - }), new QueryPagination({ from, size }), options); + }), new QueryPagination({ from, size, searchAfter }), options); } @Get("/nfts/:identifier/transfers/count") diff --git a/src/endpoints/nfts/nft.service.ts b/src/endpoints/nfts/nft.service.ts index 6c2f0cf9d..9d5b7fc6a 100644 --- a/src/endpoints/nfts/nft.service.ts +++ b/src/endpoints/nfts/nft.service.ts @@ -79,9 +79,7 @@ export class NftService { } private async fetchAndProcessNfts(queryPagination: QueryPagination, filter: NftFilter, queryOptions?: NftQueryOptions): Promise { - const { from, size } = queryPagination; - - const nfts = await this.getNftsInternal({ from, size }, filter); + const nfts = await this.getNftsInternal(queryPagination, filter); await Promise.all([ this.conditionallyApplyAssetsAndTicker(nfts, undefined, queryOptions), diff --git a/src/endpoints/nfttags/tag.controller.ts b/src/endpoints/nfttags/tag.controller.ts index 62a91db38..9a97ba262 100644 --- a/src/endpoints/nfttags/tag.controller.ts +++ b/src/endpoints/nfttags/tag.controller.ts @@ -17,13 +17,15 @@ export class TagController { @ApiOkResponse({ type: [Tag] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'search', description: 'Search by tag name', required: false }) async getTags( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('search') search: string | undefined, ): Promise { - return await this.nftTagsService.getNftTags(new QueryPagination({ from, size }), search); + return await this.nftTagsService.getNftTags(new QueryPagination({ from, size, searchAfter }), search); } @Get("/tags/count") diff --git a/src/endpoints/providers/provider.controller.ts b/src/endpoints/providers/provider.controller.ts index eb444c9ee..67673c245 100644 --- a/src/endpoints/providers/provider.controller.ts +++ b/src/endpoints/providers/provider.controller.ts @@ -41,11 +41,14 @@ export class ProviderController { @ApiNotFoundResponse({ description: 'Provider not found' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) async getProviderAccounts( @Param('address', ParseAddressPipe) address: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, - @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number,): Promise { - const provider = await this.providerService.getProviderAccounts(address, new QueryPagination({ from, size })); + @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, + ): Promise { + const provider = await this.providerService.getProviderAccounts(address, new QueryPagination({ from, size, searchAfter })); if (provider === undefined) { throw new HttpException(`Provider '${address}' not found`, HttpStatus.NOT_FOUND); diff --git a/src/endpoints/sc-results/scresult.controller.ts b/src/endpoints/sc-results/scresult.controller.ts index 1111f5648..5c80da032 100644 --- a/src/endpoints/sc-results/scresult.controller.ts +++ b/src/endpoints/sc-results/scresult.controller.ts @@ -17,6 +17,7 @@ export class SmartContractResultController { @ApiOperation({ summary: 'Smart contract results', description: 'Returns all smart contract results available on the blockchain' }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'miniBlockHash', description: 'The hash of the parent miniBlock', required: false }) @ApiQuery({ name: 'originalTxHashes', description: 'Original transaction hashes', required: false }) @ApiQuery({ name: 'sender', description: 'Sender address', required: false }) @@ -27,6 +28,7 @@ export class SmartContractResultController { getScResults( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('miniBlockHash', ParseBlockHashPipe) miniBlockHash?: string, @Query('originalTxHashes', ParseArrayPipe, ParseTransactionHashPipe) originalTxHashes?: string[], @Query('sender', ParseAddressPipe) sender?: string, @@ -35,7 +37,7 @@ export class SmartContractResultController { @Query('withActionTransferValue', ParseBoolPipe) withActionTransferValue?: boolean, ): Promise { return this.scResultService.getScResults( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new SmartContractResultFilter({ miniBlockHash, originalTxHashes, sender, receiver, functions }), new SmartContractResultOptions({ withActionTransferValue }), ); diff --git a/src/endpoints/tokens/token.controller.ts b/src/endpoints/tokens/token.controller.ts index ca67cb242..7ca2e1848 100644 --- a/src/endpoints/tokens/token.controller.ts +++ b/src/endpoints/tokens/token.controller.ts @@ -40,6 +40,7 @@ export class TokenController { @ApiOkResponse({ type: [TokenDetailed] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'type', description: 'Token type', required: false, enum: TokenType }) @ApiQuery({ name: 'search', description: 'Search by collection identifier', required: false }) @ApiQuery({ name: 'name', description: 'Search by token name', required: false }) @@ -53,6 +54,7 @@ export class TokenController { async getTokens( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('type', new ParseEnumPipe(TokenType)) type?: TokenType, @Query('search') search?: string, @Query('name') name?: string, @@ -66,7 +68,7 @@ export class TokenController { ): Promise { return await this.tokenService.getTokens( - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), new TokenFilter({ type, search, name, identifier, identifiers, includeMetaESDT, sort, order, mexPairType, priceSource }) ); } @@ -159,14 +161,15 @@ export class TokenController { async getTokenAccounts( @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, - @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number + @Query("size", new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, ): Promise { const isToken = await this.tokenService.isToken(identifier); if (!isToken) { throw new HttpException('Token not found', HttpStatus.NOT_FOUND); } - const accounts = await this.tokenService.getTokenAccounts(new QueryPagination({ from, size }), identifier); + const accounts = await this.tokenService.getTokenAccounts(new QueryPagination({ from, size, searchAfter }), identifier); if (!accounts) { throw new NotFoundException('Token not found'); } @@ -226,6 +229,7 @@ export class TokenController { @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -277,7 +281,7 @@ export class TokenController { return await this.transactionService.getTransactions( transactionFilter, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, undefined, fields, @@ -406,6 +410,7 @@ export class TokenController { @Param('identifier', ParseTokenPipe) identifier: string, @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('senderShard', ParseIntPipe) senderShard?: number, @@ -448,7 +453,7 @@ export class TokenController { round, isScCall, }), - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, fields ); diff --git a/src/endpoints/transactions/transaction.controller.ts b/src/endpoints/transactions/transaction.controller.ts index a3829b949..1d460ed0b 100644 --- a/src/endpoints/transactions/transaction.controller.ts +++ b/src/endpoints/transactions/transaction.controller.ts @@ -42,6 +42,7 @@ export class TransactionController { @ApiQuery({ name: 'fields', description: 'List of fields to filter by', required: false, isArray: true, style: 'form', explode: false }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Cursor for continuing from the previous result set', required: false }) @ApiQuery({ name: 'condition', description: 'Condition for elastic search queries', required: false, deprecated: true }) @ApiQuery({ name: 'withScResults', description: 'Return results for transactions. When "withScResults" parameter is applied, complexity estimation is 200', required: false, type: Boolean }) @ApiQuery({ name: 'withOperations', description: 'Return operations for transactions. When "withOperations" parameter is applied, complexity estimation is 200', required: false, type: Boolean }) @@ -57,6 +58,7 @@ export class TransactionController { getTransactions( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('sender', ParseAddressAndMetachainPipe) sender?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('relayer', ParseAddressPipe) relayer?: string, @@ -108,7 +110,7 @@ export class TransactionController { }); TransactionFilter.validate(transactionFilter, size); return this.transactionService.getTransactions(transactionFilter, - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, undefined, fields, diff --git a/src/endpoints/transfers/transfer.controller.ts b/src/endpoints/transfers/transfer.controller.ts index 8e02225fd..76b7204a5 100644 --- a/src/endpoints/transfers/transfer.controller.ts +++ b/src/endpoints/transfers/transfer.controller.ts @@ -25,6 +25,7 @@ export class TransferController { @ApiOkResponse({ type: [Transaction] }) @ApiQuery({ name: 'from', description: 'Number of items to skip for the result set', required: false }) @ApiQuery({ name: 'size', description: 'Number of items to retrieve', required: false }) + @ApiQuery({ name: 'searchAfter', description: 'Base64 encoded cursor to continue from the last document', required: false }) @ApiQuery({ name: 'sender', description: 'Search by multiple sender addresses, comma-separated', required: false }) @ApiQuery({ name: 'receiver', description: 'Search by multiple receiver addresses, comma-separated', required: false }) @ApiQuery({ name: 'token', description: 'Identifier of the token', required: false }) @@ -53,6 +54,7 @@ export class TransferController { async getAccountTransfers( @Query('from', new DefaultValuePipe(0), ParseIntPipe) from: number, @Query('size', new DefaultValuePipe(25), ParseIntPipe) size: number, + @Query('searchAfter') searchAfter?: string, @Query('receiver', ParseAddressArrayPipe) receiver?: string[], @Query('sender', ParseAddressArrayPipe) sender?: string[], @Query('token') token?: string, @@ -102,7 +104,7 @@ export class TransferController { withRefunds, isScCall, }), - new QueryPagination({ from, size }), + new QueryPagination({ from, size, searchAfter }), options, fields );