diff --git a/sdk/typescript/lib/helpers/exhaust-pagination.ts b/sdk/typescript/lib/helpers/exhaust-pagination.ts index fe5478cc8..ec7fd1e65 100644 --- a/sdk/typescript/lib/helpers/exhaust-pagination.ts +++ b/sdk/typescript/lib/helpers/exhaust-pagination.ts @@ -1,29 +1,5 @@ import { LedgerState } from '../generated' -/** - * Exhausts a paginated API resource and returns all the results. - */ -export const exhaustPagination = async ( - queryFunction: ( - cursor?: string - ) => Promise<{ items: T[]; next_cursor?: string | null }>, - start?: string -): Promise => { - let next_cursor: string | null | undefined = start - const aggregatedEntities: T[] = [] - - do { - const queryFunctionResponse: { - next_cursor?: string | null - items: T[] - } = await queryFunction(next_cursor) - aggregatedEntities.push(...queryFunctionResponse.items) - next_cursor = queryFunctionResponse.next_cursor - } while (next_cursor) - - return aggregatedEntities -} - /** * Exhausts a paginated API resource and returns all the results. */ diff --git a/sdk/typescript/lib/subapis/state.ts b/sdk/typescript/lib/subapis/state.ts index 24c996ea5..47789d579 100644 --- a/sdk/typescript/lib/subapis/state.ts +++ b/sdk/typescript/lib/subapis/state.ts @@ -1,6 +1,5 @@ import { chunk } from '../helpers/chunk' import { - exhaustPagination, exhaustPaginationWithLedgerState, } from '../helpers/exhaust-pagination' import { @@ -17,6 +16,7 @@ import { StateEntityDetailsResponseItem, StateEntityFungiblesPageResponse, StateEntityMetadataPageResponse, + StateEntityNonFungiblesPageResponse, StateNonFungibleDetailsResponseItem, StateNonFungibleIdsResponse, StateNonFungibleLocationResponseItem, @@ -62,14 +62,14 @@ export class State { constructor( public innerClient: StateApi, public configuration: RuntimeConfiguration - ) {} + ) { } /** * Get detailed information about entities together with vault aggregated fungible and non-fungible resources. * Returns an array or single item depending on input value. If array is passed, it will be split into chunks of 20 addresses * which will be requested separately and returned only if all requests are successful. * - * Calling this function will exhaust list of all fungible resources for each entity. + * Calling this function will exhaust list of all resources for each entity. * If any of the requests fail, the whole operation will fail. * * When requesting details for `internal_vault` entity, `non_fungible_resources` and `fungible_resources` will be defaulted to objects with empty arrays @@ -135,21 +135,20 @@ export class State { return isArray ? Promise.all( - (items as StateEntityDetailsVaultResponseItem[]).map((item) => - this.queryAllFungibles( - item, - ledgerState || { - state_version: ledger_state.state_version, - } - ) + (items as StateEntityDetailsVaultResponseItem[]).map((item) => + this.queryAllResources( + item, + ledgerState || { + state_version: ledger_state.state_version, + } ) - ) - : this.queryAllFungibles( - items[0] as StateEntityDetailsVaultResponseItem, - ledgerState || { - state_version: ledger_state.state_version, - } - ) + )) + : this.queryAllResources( + items[0] as StateEntityDetailsVaultResponseItem, + ledgerState || { + state_version: ledger_state.state_version, + } + ) } /** @@ -208,10 +207,10 @@ export class State { address: string, startCursor?: string ): Promise { - return exhaustPagination( + return exhaustPaginationWithLedgerState( this.getEntityMetadata.bind(this, address), startCursor - ) + ).then((res) => res.aggregatedEntities) } /** @@ -232,11 +231,20 @@ export class State { * Get list of all validators. This will iterate over returned cursors and aggregate all responses. */ async getAllValidators(start?: string): Promise { - return exhaustPagination(this.getValidators.bind(this), start) + return exhaustPaginationWithLedgerState((cursor?: string) => { + const v = this.getValidatorsWithLedgerState(cursor) + return v.then((res) => ({ + items: res.validators.items, + ledger_state: res.ledger_state, + next_cursor: res.validators.next_cursor + })) + }, start).then( + (res) => res.aggregatedEntities + ) } /** - * Get paged list of validators + * Get paged list of validators with ledger state * @param cursor */ async getValidatorsWithLedgerState(cursor?: string) { @@ -256,6 +264,7 @@ export class State { this.getValidatorsWithLedgerState(cursor).then((res) => ({ items: res.validators.items, ledger_state: res.ledger_state, + next_cursor: res.validators.next_cursor })), start ) @@ -383,6 +392,57 @@ export class State { > } + private async getEntityNonFungiblesPageVaultAggregated( + entity: string, + nextCursor?: string | undefined, + ledgerState?: LedgerStateSelector + ): Promise< + ReplaceProperty< + StateEntityNonFungiblesPageResponse, + 'items', + NonFungibleResourcesCollectionItemVaultAggregated[] + > + > { + return this.innerClient.entityNonFungiblesPage({ + stateEntityNonFungiblesPageRequest: { + address: entity, + cursor: nextCursor, + aggregation_level: 'Vault', + at_ledger_state: ledgerState, + }, + }) as Promise< + ReplaceProperty< + StateEntityNonFungiblesPageResponse, + 'items', + NonFungibleResourcesCollectionItemVaultAggregated[] + > + > + } + + private ensureResourcesProperties( + response: StateEntityDetailsResponse + ): ReplaceProperty< + StateEntityDetailsResponse, + 'items', + StateEntityDetailsVaultResponseItem[] + > { + return { + ...response, + items: response.items.map((item) => ({ + ...item, + fungible_resources: item.fungible_resources || { + total_count: 0, + items: [], + }, + non_fungible_resources: item.non_fungible_resources || { + total_count: 0, + items: [], + }, + })) as StateEntityDetailsVaultResponseItem[], + } + } + + private async queryAllFungibles( stateEntityDetails: StateEntityDetailsVaultResponseItem, ledgerState?: LedgerStateSelector @@ -412,26 +472,59 @@ export class State { }) } - private ensureResourcesProperties( - response: StateEntityDetailsResponse - ): ReplaceProperty< - StateEntityDetailsResponse, - 'items', - StateEntityDetailsVaultResponseItem[] - > { - return { - ...response, - items: response.items.map((item) => ({ - ...item, - fungible_resources: item.fungible_resources || { - total_count: 0, - items: [], + + private async queryAllNonFungibles( + stateEntityDetails: StateEntityDetailsVaultResponseItem, + ledgerState?: LedgerStateSelector + ): Promise { + const nextCursor = stateEntityDetails.non_fungible_resources.next_cursor + + if (!nextCursor) return Promise.resolve(stateEntityDetails) + + const allNonFungibles = await exhaustPaginationWithLedgerState( + (cursor) => + this.getEntityNonFungiblesPageVaultAggregated( + stateEntityDetails.address, + cursor, + ledgerState + ), + nextCursor + ) + + return Promise.resolve({ + ...stateEntityDetails, + non_fungible_resources: { + items: [ + ...stateEntityDetails.non_fungible_resources.items, + ...allNonFungibles.aggregatedEntities, + ], + }, + }) + } + + private async queryAllResources( + stateEntityDetails: StateEntityDetailsVaultResponseItem, + ledgerState?: LedgerStateSelector + ): Promise { + const itemsWithAllFungibles = this.queryAllFungibles(stateEntityDetails, ledgerState) + const itemsWithAllNonFungibles = this.queryAllNonFungibles(stateEntityDetails, ledgerState) + + return Promise.all([itemsWithAllFungibles, itemsWithAllNonFungibles]).then((results) => { + return { + ...stateEntityDetails, + fungible_resources: { + ...stateEntityDetails.fungible_resources, + items: [ + ...results[0].fungible_resources.items, + ], }, - non_fungible_resources: item.non_fungible_resources || { - total_count: 0, - items: [], + non_fungible_resources: { + ...stateEntityDetails.non_fungible_resources, + items: [ + ...results[1].non_fungible_resources.items, + ], }, - })) as StateEntityDetailsVaultResponseItem[], - } + } + }) } }