From e415fa8ceffa126b2905717976a052b999ad3e72 Mon Sep 17 00:00:00 2001 From: Daniel Porteous Date: Tue, 1 Nov 2022 16:33:44 -0700 Subject: [PATCH] Add cursor support to /accounts//resources --- api/doc/spec.json | 205 +++++++++++++++ api/doc/spec.yaml | 233 ++++++++++++++++++ api/src/accounts.rs | 93 ++++++- api/src/context.rs | 8 + api/src/events.rs | 4 +- api/src/page.rs | 45 ++-- api/src/response.rs | 16 ++ api/src/tests/accounts_test.rs | 172 +++++++++++++ api/src/transactions.rs | 2 +- api/types/src/derives.rs | 34 ++- api/types/src/lib.rs | 2 +- api/types/src/wrappers.rs | 44 +++- config/src/config/api_config.rs | 6 + .../typescript/sdk/src/generated/index.ts | 2 + .../src/generated/models/StateKeyWrapper.ts | 9 + .../src/generated/schemas/$StateKeyWrapper.ts | 8 + .../src/generated/services/AccountsService.ts | 27 ++ 17 files changed, 867 insertions(+), 43 deletions(-) create mode 100644 ecosystem/typescript/sdk/src/generated/models/StateKeyWrapper.ts create mode 100644 ecosystem/typescript/sdk/src/generated/schemas/$StateKeyWrapper.ts diff --git a/api/doc/spec.json b/api/doc/spec.json index 7b2102d352930..ac1f3a6d1e321 100644 --- a/api/doc/spec.json +++ b/api/doc/spec.json @@ -158,6 +158,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -602,6 +609,29 @@ "required": false, "deprecated": false, "explode": true + }, + { + "name": "start", + "schema": { + "$ref": "#/components/schemas/StateKeyWrapper" + }, + "in": "query", + "description": "Cursor specifying where to start for pagination\n\nThis cursor cannot be derived manually client-side. Instead, you must\ncall this endpoint once without this query parameter specified, and\nthen use the cursor returned in the X-Aptos-Cursor header in the\nresponse.", + "required": false, + "deprecated": false, + "explode": true + }, + { + "name": "limit", + "schema": { + "type": "integer", + "format": "uint16" + }, + "in": "query", + "description": "Max number of account resources to retrieve\n\nIf not provided, defaults to default page size.", + "required": false, + "deprecated": false, + "explode": true } ], "responses": { @@ -689,6 +719,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -1133,6 +1170,29 @@ "required": false, "deprecated": false, "explode": true + }, + { + "name": "start", + "schema": { + "$ref": "#/components/schemas/StateKeyWrapper" + }, + "in": "query", + "description": "Cursor specifying where to start for pagination\n\nThis cursor cannot be derived manually client-side. Instead, you must\ncall this endpoint once without this query parameter specified, and\nthen use the cursor returned in the X-Aptos-Cursor header in the\nresponse.", + "required": false, + "deprecated": false, + "explode": true + }, + { + "name": "limit", + "schema": { + "type": "integer", + "format": "uint16" + }, + "in": "query", + "description": "Max number of account modules to retrieve\n\nIf not provided, defaults to default page size.", + "required": false, + "deprecated": false, + "explode": true } ], "responses": { @@ -1220,6 +1280,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -1760,6 +1827,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -2017,6 +2091,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -2546,6 +2627,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -3100,6 +3188,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -3665,6 +3760,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -4169,6 +4271,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -4572,6 +4681,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -5111,6 +5227,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -5649,6 +5772,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -6181,6 +6311,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -6702,6 +6839,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -7219,6 +7363,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -7736,6 +7887,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -8279,6 +8437,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -8805,6 +8970,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -8889,6 +9061,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -9450,6 +9629,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -9964,6 +10150,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -10332,6 +10525,13 @@ "type": "integer", "format": "uint64" } + }, + "X-APTOS-CURSOR": { + "description": "Cursor to be used for endpoints that support cursor-based\npagination. Pass this to the `start` field of the endpoint\non the next call to get the next page of results.", + "deprecated": false, + "schema": { + "type": "string" + } } } }, @@ -11844,6 +12044,11 @@ } } }, + "StateKeyWrapper": { + "type": "string", + "description": "Representation of a StateKey as a hex string. This is used for cursor based pagination.\n", + "example": "0000000000000000000000000000000000000000000000000000000000000000012f0000000000000000000000000000000000000000000000000000000000000000010d7374616b696e675f70726f7879" + }, "SubmitTransactionRequest": { "type": "object", "description": "A request to submit a transaction\n\nThis requires a transaction and a signature of it", diff --git a/api/doc/spec.yaml b/api/doc/spec.yaml index 3ab68b3d57aee..363f687f0d84d 100644 --- a/api/doc/spec.yaml +++ b/api/doc/spec.yaml @@ -119,6 +119,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -445,6 +453,32 @@ paths: required: false deprecated: false explode: true + - name: start + schema: + $ref: '#/components/schemas/StateKeyWrapper' + in: query + description: |- + Cursor specifying where to start for pagination + + This cursor cannot be derived manually client-side. Instead, you must + call this endpoint once without this query parameter specified, and + then use the cursor returned in the X-Aptos-Cursor header in the + response. + required: false + deprecated: false + explode: true + - name: limit + schema: + type: integer + format: uint16 + in: query + description: |- + Max number of account resources to retrieve + + If not provided, defaults to default page size. + required: false + deprecated: false + explode: true responses: '200': description: '' @@ -510,6 +544,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -836,6 +878,32 @@ paths: required: false deprecated: false explode: true + - name: start + schema: + $ref: '#/components/schemas/StateKeyWrapper' + in: query + description: |- + Cursor specifying where to start for pagination + + This cursor cannot be derived manually client-side. Instead, you must + call this endpoint once without this query parameter specified, and + then use the cursor returned in the X-Aptos-Cursor header in the + response. + required: false + deprecated: false + explode: true + - name: limit + schema: + type: integer + format: uint16 + in: query + description: |- + Max number of account modules to retrieve + + If not provided, defaults to default page size. + required: false + deprecated: false + explode: true responses: '200': description: '' @@ -901,6 +969,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -1301,6 +1377,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '503': description: '' content: @@ -1498,6 +1582,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -1891,6 +1983,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -2307,6 +2407,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -2728,6 +2836,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -3094,6 +3210,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -3393,6 +3517,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -3790,6 +3922,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -4190,6 +4330,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -4584,6 +4732,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -4975,6 +5131,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -5359,6 +5523,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -5734,6 +5906,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -6136,6 +6316,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -6534,6 +6722,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '206': description: '' content: @@ -6596,6 +6792,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -7016,6 +7220,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -7402,6 +7614,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -7674,6 +7894,14 @@ paths: schema: type: integer format: uint64 + X-APTOS-CURSOR: + description: |- + Cursor to be used for endpoints that support cursor-based + pagination. Pass this to the `start` field of the endpoint + on the next call to get the next page of results. + deprecated: false + schema: + type: string '400': description: '' content: @@ -8879,6 +9107,11 @@ components: $ref: '#/components/schemas/WriteSetChange' timestamp: $ref: '#/components/schemas/U64' + StateKeyWrapper: + type: string + description: | + Representation of a StateKey as a hex string. This is used for cursor based pagination. + example: 0000000000000000000000000000000000000000000000000000000000000000012f0000000000000000000000000000000000000000000000000000000000000000010d7374616b696e675f70726f7879 SubmitTransactionRequest: type: object description: |- diff --git a/api/src/accounts.rs b/api/src/accounts.rs index ee229a2bb088e..523d70deca056 100644 --- a/api/src/accounts.rs +++ b/api/src/accounts.rs @@ -4,6 +4,7 @@ use crate::accept_type::AcceptType; use crate::context::Context; use crate::failpoint::fail_point_poem; +use crate::page::determine_limit; use crate::response::{ account_not_found, resource_not_found, struct_field_not_found, BadRequestError, BasicErrorWith404, BasicResponse, BasicResponseStatus, BasicResultWith404, InternalError, @@ -12,7 +13,7 @@ use crate::ApiTags; use anyhow::Context as AnyhowContext; use aptos_api_types::{ AccountData, Address, AptosErrorCode, AsConverter, LedgerInfo, MoveModuleBytecode, - MoveModuleId, MoveResource, MoveStructTag, U64, + MoveModuleId, MoveResource, MoveStructTag, StateKeyWrapper, U64, }; use aptos_types::access_path::AccessPath; use aptos_types::account_config::AccountResource; @@ -30,7 +31,6 @@ use poem_openapi::{param::Path, OpenApi}; use std::collections::BTreeMap; use std::convert::TryInto; use std::sync::Arc; -use storage_interface::MAX_REQUEST_LIMIT; /// API for accounts, their associated resources, and modules pub struct AccountsApi { @@ -64,7 +64,13 @@ impl AccountsApi { fail_point_poem("endpoint_get_account")?; self.context .check_api_output_enabled("Get account", &accept_type)?; - let account = Account::new(self.context.clone(), address.0, ledger_version.0)?; + let account = Account::new( + self.context.clone(), + address.0, + ledger_version.0, + None, + None, + )?; account.account(&accept_type) } @@ -90,11 +96,28 @@ impl AccountsApi { /// /// If not provided, it will be the latest version ledger_version: Query>, + /// Cursor specifying where to start for pagination + /// + /// This cursor cannot be derived manually client-side. Instead, you must + /// call this endpoint once without this query parameter specified, and + /// then use the cursor returned in the X-Aptos-Cursor header in the + /// response. + start: Query>, + /// Max number of account resources to retrieve + /// + /// If not provided, defaults to default page size. + limit: Query>, ) -> BasicResultWith404> { fail_point_poem("endpoint_get_account_resources")?; self.context .check_api_output_enabled("Get account resources", &accept_type)?; - let account = Account::new(self.context.clone(), address.0, ledger_version.0)?; + let account = Account::new( + self.context.clone(), + address.0, + ledger_version.0, + start.0.map(StateKey::from), + limit.0, + )?; account.resources(&accept_type) } @@ -120,11 +143,28 @@ impl AccountsApi { /// /// If not provided, it will be the latest version ledger_version: Query>, + /// Cursor specifying where to start for pagination + /// + /// This cursor cannot be derived manually client-side. Instead, you must + /// call this endpoint once without this query parameter specified, and + /// then use the cursor returned in the X-Aptos-Cursor header in the + /// response. + start: Query>, + /// Max number of account modules to retrieve + /// + /// If not provided, defaults to default page size. + limit: Query>, ) -> BasicResultWith404> { fail_point_poem("endpoint_get_account_modules")?; self.context .check_api_output_enabled("Get account modules", &accept_type)?; - let account = Account::new(self.context.clone(), address.0, ledger_version.0)?; + let account = Account::new( + self.context.clone(), + address.0, + ledger_version.0, + start.0.map(StateKey::from), + limit.0, + )?; account.modules(&accept_type) } } @@ -136,6 +176,10 @@ pub struct Account { address: Address, /// Lookup ledger version ledger_version: u64, + /// Where to start for pagination + start: Option, + /// Max number of items to retrieve + limit: Option, /// Current ledger info pub latest_ledger_info: LedgerInfo, } @@ -147,6 +191,8 @@ impl Account { context: Arc, address: Address, requested_ledger_version: Option, + start: Option, + limit: Option, ) -> Result { // Use the latest ledger version, or the requested associated version let (latest_ledger_info, requested_ledger_version) = context @@ -158,6 +204,8 @@ impl Account { context, address, ledger_version: requested_ledger_version, + start, + limit, latest_ledger_info, }) } @@ -219,16 +267,26 @@ impl Account { /// /// * JSON: Return a JSON encoded version of [`Vec`] /// * BCS: Return a sorted BCS encoded version of BCS encoded resources [`BTreeMap>`] + /// + /// Note: For the BCS response, if results are being returned in pages, i.e. with the + /// `start` and `limit` query parameters, the results will only be sorted within each page. pub fn resources(self, accept_type: &AcceptType) -> BasicResultWith404> { // check account exists self.get_account_resource()?; + let max_account_resources_page_size = self.context.max_account_resources_page_size(); let (resources, next_state_key) = self .context .get_resources_by_pagination( self.address.into(), - None, + self.start.as_ref(), self.ledger_version, - MAX_REQUEST_LIMIT, + // Just use the max as the default + determine_limit( + self.limit, + max_account_resources_page_size, + max_account_resources_page_size, + &self.latest_ledger_info, + )? as u64, ) .context("Failed to get resources from storage") .map_err(|err| { @@ -238,7 +296,6 @@ impl Account { &self.latest_ledger_info, ) })?; - assert_eq!(next_state_key, None); match accept_type { AcceptType::Json => { @@ -255,12 +312,12 @@ impl Account { &self.latest_ledger_info, ) })?; - BasicResponse::try_from_json(( converted_resources, &self.latest_ledger_info, BasicResponseStatus::Ok, )) + .map(|v| v.with_cursor(next_state_key)) } AcceptType::Bcs => { // Put resources in a BTreeMap to ensure they're ordered the same every time @@ -270,6 +327,7 @@ impl Account { &self.latest_ledger_info, BasicResponseStatus::Ok, )) + .map(|v| v.with_cursor(next_state_key)) } } } @@ -278,16 +336,26 @@ impl Account { /// /// * JSON: Return a JSON encoded version of [`Vec`] with parsed ABIs /// * BCS: Return a sorted BCS encoded version of bytecode [`BTreeMap>`] + /// + /// Note: For the BCS response, if results are being returned in pages, i.e. with the + /// `start` and `limit` query parameters, the results will only be sorted within each page. pub fn modules(self, accept_type: &AcceptType) -> BasicResultWith404> { // check account exists self.get_account_resource()?; + let max_account_modules_page_size = self.context.max_account_modules_page_size(); let (modules, next_state_key) = self .context .get_modules_by_pagination( self.address.into(), - None, + self.start.as_ref(), self.ledger_version, - MAX_REQUEST_LIMIT, + // Just use the max as the default + determine_limit( + self.limit, + max_account_modules_page_size, + max_account_modules_page_size, + &self.latest_ledger_info, + )? as u64, ) .context("Failed to get modules from storage") .map_err(|err| { @@ -297,7 +365,6 @@ impl Account { &self.latest_ledger_info, ) })?; - assert_eq!(next_state_key, None); match accept_type { AcceptType::Json => { @@ -322,6 +389,7 @@ impl Account { &self.latest_ledger_info, BasicResponseStatus::Ok, )) + .map(|v| v.with_cursor(next_state_key)) } AcceptType::Bcs => { // Sort modules by name @@ -334,6 +402,7 @@ impl Account { &self.latest_ledger_info, BasicResponseStatus::Ok, )) + .map(|v| v.with_cursor(next_state_key)) } } } diff --git a/api/src/context.rs b/api/src/context.rs index 13a5dfc8fa233..f9ed9fd986aef 100644 --- a/api/src/context.rs +++ b/api/src/context.rs @@ -94,6 +94,14 @@ impl Context { self.node_config.api.max_events_page_size } + pub fn max_account_resources_page_size(&self) -> u16 { + self.node_config.api.max_account_resources_page_size + } + + pub fn max_account_modules_page_size(&self) -> u16 { + self.node_config.api.max_account_modules_page_size + } + pub fn move_resolver(&self) -> Result> { self.db .latest_state_checkpoint_view() diff --git a/api/src/events.rs b/api/src/events.rs index 48a7f526a610e..24d30c18425e7 100644 --- a/api/src/events.rs +++ b/api/src/events.rs @@ -69,7 +69,7 @@ impl EventsApi { ); // Ensure that account exists - let account = Account::new(self.context.clone(), address.0, None)?; + let account = Account::new(self.context.clone(), address.0, None, None, None)?; account.get_account_resource()?; self.list( account.latest_ledger_info, @@ -131,7 +131,7 @@ impl EventsApi { limit.0, self.context.max_events_page_size(), ); - let account = Account::new(self.context.clone(), address.0, None)?; + let account = Account::new(self.context.clone(), address.0, None, None, None)?; let key = account.find_event_key(event_handle.0, field_name.0.into())?; self.list(account.latest_ledger_info, accept_type, page, key) } diff --git a/api/src/page.rs b/api/src/page.rs index eb8475840dbed..ad998532d9123 100644 --- a/api/src/page.rs +++ b/api/src/page.rs @@ -62,19 +62,36 @@ impl Page { /// Get the page size for the request pub fn limit(&self, ledger_info: &LedgerInfo) -> Result { - let limit = self.limit.unwrap_or(DEFAULT_PAGE_SIZE); - if limit == 0 { - return Err(E::bad_request_with_code( - &format!("Given limit value ({}) must not be zero", limit), - AptosErrorCode::InvalidInput, - ledger_info, - )); - } - // If we go over the max page size, we return the max page size - if limit > self.max_page_size { - Ok(self.max_page_size) - } else { - Ok(limit) - } + determine_limit( + self.limit, + self.max_page_size, + DEFAULT_PAGE_SIZE, + ledger_info, + ) + } +} + +pub fn determine_limit( + // The limit requested by the user, if any. + requested_limit: Option, + // The default limit to use, if requested_limit is None. + default_limit: u16, + // The ceiling on the limit. If the requested value is higher than this, we just use this value. + max_limit: u16, + ledger_info: &LedgerInfo, +) -> Result { + let limit = requested_limit.unwrap_or(default_limit); + if limit == 0 { + return Err(E::bad_request_with_code( + &format!("Given limit value ({}) must not be zero", limit), + AptosErrorCode::InvalidInput, + ledger_info, + )); + } + // If we go over the max page size, we return the max page size + if limit > max_limit { + Ok(max_limit) + } else { + Ok(limit) } } diff --git a/api/src/response.rs b/api/src/response.rs index 9fd609e9730d9..c3a298f9bf306 100644 --- a/api/src/response.rs +++ b/api/src/response.rs @@ -298,6 +298,10 @@ macro_rules! generate_success_response { #[oai(header = "X-Aptos-Block-Height")] u64, /// Oldest non-pruned block height of the chain #[oai(header = "X-Aptos-Oldest-Block-Height")] u64, + /// Cursor to be used for endpoints that support cursor-based + /// pagination. Pass this to the `start` field of the endpoint + /// on the next call to get the next page of results. + #[oai(header = "X-Aptos-Cursor")] Option, ), )* } @@ -337,6 +341,7 @@ macro_rules! generate_success_response { ledger_info.epoch.into(), ledger_info.block_height.into(), ledger_info.oldest_block_height.into(), + None, ) }, )* @@ -456,6 +461,17 @@ macro_rules! generate_success_response { status ))) } + + pub fn with_cursor(mut self, new_cursor: Option) -> Self { + match self { + $( + [<$enum_name>]::$name(_, _, _, _, _, _, _, _, ref mut cursor) => { + *cursor = new_cursor.map(|c| aptos_api_types::StateKeyWrapper::from(c).to_string()); + } + )* + } + self + } } } }; diff --git a/api/src/tests/accounts_test.rs b/api/src/tests/accounts_test.rs index 728f0804ca8f0..3ba1628365578 100644 --- a/api/src/tests/accounts_test.rs +++ b/api/src/tests/accounts_test.rs @@ -1,8 +1,11 @@ // Copyright (c) Aptos // SPDX-License-Identifier: Apache-2.0 +use std::str::FromStr; + use super::new_test_context; use aptos_api_test_context::{current_function_name, find_value}; +use aptos_api_types::{MoveModuleBytecode, MoveResource, StateKeyWrapper}; use serde_json::json; /* TODO: reactivate once cause of failure for `"8"` vs `8` in the JSON output is known. @@ -204,6 +207,175 @@ async fn test_get_core_account_data_not_found() { context.check_golden_output(resp); } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_get_account_resources_with_pagination() { + let context = new_test_context(current_function_name!()); + let address = "0x1"; + + // Make a request with no limit. We'll use this full list of resources + // as a comparison with the results from using pagination parameters. + // There should be no cursor in the header in this case. Note: This won't + // be true if for some reason the account used in this test has more than + // the default max page size for resources (1000 at the time of writing, + // based on config/src/config/api_config.rs). + let req = warp::test::request() + .method("GET") + .path(&format!("/v1{}", account_resources(address))); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + assert!(!resp.headers().contains_key("X-Aptos-Cursor")); + let all_resources: Vec = serde_json::from_slice(resp.body()).unwrap(); + // We assert there are at least 10 resources. If there aren't, the rest of the + // test will be wrong. + assert!(all_resources.len() >= 10); + + // Make a request, assert we get a cursor back in the header for the next + // page of results. Assert we can deserialize the string representation + // of the cursor returned in the header. + let req = warp::test::request() + .method("GET") + .path(&format!("/v1{}?limit=5", account_resources(address))); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + let cursor_header = resp + .headers() + .get("X-Aptos-Cursor") + .expect("Cursor header was missing"); + let cursor_header = StateKeyWrapper::from_str(cursor_header.to_str().unwrap()).unwrap(); + let resources: Vec = serde_json::from_slice(resp.body()).unwrap(); + assert_eq!(resources.len(), 5); + assert_eq!(resources, all_resources[0..5].to_vec()); + + // Make a request using the cursor. Assert the 5 results we get back are the next 5. + let req = warp::test::request().method("GET").path(&format!( + "/v1{}?limit=5&start={}", + account_resources(address), + cursor_header + )); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + let cursor_header = resp + .headers() + .get("X-Aptos-Cursor") + .expect("Cursor header was missing"); + let cursor_header = StateKeyWrapper::from_str(cursor_header.to_str().unwrap()).unwrap(); + let resources: Vec = serde_json::from_slice(resp.body()).unwrap(); + assert_eq!(resources.len(), 5); + assert_eq!(resources, all_resources[5..10].to_vec()); + + // Get the rest of the resources, assert there is no cursor now. + let req = warp::test::request().method("GET").path(&format!( + "/v1{}?limit=1000&start={}", + account_resources(address), + cursor_header + )); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + assert!(!resp.headers().contains_key("X-Aptos-Cursor")); + let resources: Vec = serde_json::from_slice(resp.body()).unwrap(); + assert_eq!(resources.len(), all_resources.len() - 10); + assert_eq!(resources, all_resources[10..].to_vec()); +} + +// Same as the above test but for modules. +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_get_account_modules_with_pagination() { + let context = new_test_context(current_function_name!()); + let address = "0x1"; + + // Make a request with no limit. We'll use this full list of modules + // as a comparison with the results from using pagination parameters. + // There should be no cursor in the header in this case. Note: This won't + // be true if for some reason the account used in this test has more than + // the default max page size for modules (1000 at the time of writing, + // based on config/src/config/api_config.rs). + let req = warp::test::request() + .method("GET") + .path(&format!("/v1{}", account_modules(address))); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + assert!(!resp.headers().contains_key("X-Aptos-Cursor")); + let all_modules: Vec = serde_json::from_slice(resp.body()).unwrap(); + // We assert there are at least 10 modules. If there aren't, the rest of the + // test will be wrong. + assert!(all_modules.len() >= 10); + + // Make a request, assert we get a cursor back in the header for the next + // page of results. Assert we can deserialize the string representation + // of the cursor returned in the header. + let req = warp::test::request() + .method("GET") + .path(&format!("/v1{}?limit=5", account_modules(address))); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + let cursor_header = resp + .headers() + .get("X-Aptos-Cursor") + .expect("Cursor header was missing"); + let cursor_header = StateKeyWrapper::from_str(cursor_header.to_str().unwrap()).unwrap(); + let modules: Vec = serde_json::from_slice(resp.body()).unwrap(); + assert_eq!(modules.len(), 5); + assert_eq!(modules, all_modules[0..5].to_vec()); + + // Make a request using the cursor. Assert the 5 results we get back are the next 5. + let req = warp::test::request().method("GET").path(&format!( + "/v1{}?limit=5&start={}", + account_modules(address), + cursor_header + )); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + let cursor_header = resp + .headers() + .get("X-Aptos-Cursor") + .expect("Cursor header was missing"); + let cursor_header = StateKeyWrapper::from_str(cursor_header.to_str().unwrap()).unwrap(); + let modules: Vec = serde_json::from_slice(resp.body()).unwrap(); + assert_eq!(modules.len(), 5); + assert_eq!(modules, all_modules[5..10].to_vec()); + + // Get the rest of the modules, assert there is no cursor now. + let req = warp::test::request().method("GET").path(&format!( + "/v1{}?limit=1000&start={}", + account_modules(address), + cursor_header + )); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 200); + assert!(!resp.headers().contains_key("X-Aptos-Cursor")); + let modules: Vec = serde_json::from_slice(resp.body()).unwrap(); + assert_eq!(modules.len(), all_modules.len() - 10); + assert_eq!(modules, all_modules[10..].to_vec()); +} + +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn test_get_account_items_limit_params() { + let context = new_test_context(current_function_name!()); + let address = "0x1"; + + // Ensure limit=0 is rejected. + let req = warp::test::request() + .method("GET") + .path(&format!("/v1{}?limit=0", account_resources(address))); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 400); + + // Ensure limit=0 is rejected. + let req = warp::test::request() + .method("GET") + .path(&format!("/v1{}?limit=0", account_modules(address))); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 400); + + // Ensure garbage start param values are rejected. + let req = warp::test::request().method("GET").path(&format!( + "/v1{}?start=iwouldnotsurviveavibecheckrightnow", + account_modules(address) + )); + let resp = context.reply(req).await; + assert_eq!(resp.status(), 400); +} + fn account_resources(address: &str) -> String { format!("/accounts/{}/resources", address) } diff --git a/api/src/transactions.rs b/api/src/transactions.rs index d2e86bce2ec2b..6317105e08fc7 100644 --- a/api/src/transactions.rs +++ b/api/src/transactions.rs @@ -796,7 +796,7 @@ impl TransactionsApi { address: Address, ) -> BasicResultWith404> { // Verify the account exists - let account = Account::new(self.context.clone(), address, None)?; + let account = Account::new(self.context.clone(), address, None, None, None)?; account.get_account_resource()?; let latest_ledger_info = account.latest_ledger_info; diff --git a/api/types/src/derives.rs b/api/types/src/derives.rs index 40fe813caa69c..27b6e16b46706 100644 --- a/api/types/src/derives.rs +++ b/api/types/src/derives.rs @@ -24,7 +24,7 @@ use serde_json::json; use crate::{ move_types::{MoveAbility, MoveStructValue}, Address, EntryFunctionId, HashValue, HexEncodedBytes, IdentifierWrapper, MoveModuleId, - MoveStructTag, MoveType, U128, U64, + MoveStructTag, MoveType, StateKeyWrapper, U128, U64, }; use indoc::indoc; @@ -47,6 +47,23 @@ impl_poem_type!( ) ); +impl_poem_type!( + EntryFunctionId, + "string", + ( + example = Some(serde_json::Value::String( + "0x1::aptos_coin::transfer".to_string() + )), + description = Some(indoc! {" + Entry function id is string representation of a entry function defined on-chain. + + Format: `{address}::{module name}::{function name}` + + Both `module name` and `function name` are case-sensitive. + "}) + ) +); + impl_poem_type!(HashValue, "string", ()); impl_poem_type!( @@ -213,19 +230,13 @@ impl_poem_type!( ); impl_poem_type!( - EntryFunctionId, + StateKeyWrapper, "string", ( - example = Some(serde_json::Value::String( - "0x1::aptos_coin::transfer".to_string() - )), + example = Some(serde_json::Value::String("0000000000000000000000000000000000000000000000000000000000000000012f0000000000000000000000000000000000000000000000000000000000000000010d7374616b696e675f70726f7879".to_string())), description = Some(indoc! {" - Entry function id is string representation of a entry function defined on-chain. - - Format: `{address}::{module name}::{function name}` - - Both `module name` and `function name` are case-sensitive. - "}) + Representation of a StateKey as a hex string. This is used for cursor based pagination. + "}) ) ); @@ -267,6 +278,7 @@ impl_poem_parameter!( IdentifierWrapper, HexEncodedBytes, MoveStructTag, + StateKeyWrapper, U64, U128 ); diff --git a/api/types/src/lib.rs b/api/types/src/lib.rs index 9a54af468b248..37fe195cc051d 100644 --- a/api/types/src/lib.rs +++ b/api/types/src/lib.rs @@ -49,7 +49,7 @@ pub use transaction::{ UserCreateSigningMessageRequest, UserTransaction, UserTransactionRequest, VersionedEvent, WriteModule, WriteResource, WriteSet, WriteSetChange, WriteSetPayload, WriteTableItem, }; -pub use wrappers::{EventGuid, IdentifierWrapper}; +pub use wrappers::{EventGuid, IdentifierWrapper, StateKeyWrapper}; pub fn deserialize_from_string<'de, D, T>(deserializer: D) -> Result where diff --git a/api/types/src/wrappers.rs b/api/types/src/wrappers.rs index 9d918bc0fb26b..56b7cf8fbd207 100644 --- a/api/types/src/wrappers.rs +++ b/api/types/src/wrappers.rs @@ -11,8 +11,8 @@ //! then be unpacked to the real type beneath. use crate::VerifyInput; -use anyhow::bail; -use aptos_types::event::EventKey; +use anyhow::{bail, Context}; +use aptos_types::{event::EventKey, state_store::state_key::StateKey}; use move_core_types::identifier::{IdentStr, Identifier}; use poem_openapi::Object; use serde::{Deserialize, Serialize}; @@ -114,3 +114,43 @@ impl From for EventKey { ) } } + +/// This wraps the StateKey, serializing it as hex encoded bytes. +#[derive(Debug, Serialize, Deserialize)] +pub struct StateKeyWrapper(pub StateKey); + +impl fmt::Display for StateKeyWrapper { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let hex_string = hex::encode( + &self + .0 + .encode() + .context("Failed to encode StateKey") + .map_err(|_| fmt::Error)?, + ); + write!(f, "{}", hex_string) + } +} + +impl FromStr for StateKeyWrapper { + type Err = anyhow::Error; + + fn from_str(s: &str) -> anyhow::Result { + let state_key_prefix: StateKey = + StateKey::decode(&hex::decode(s).context("Failed to decode StateKey as hex string")?) + .context("Failed to decode StateKey from hex string")?; + Ok(StateKeyWrapper(state_key_prefix)) + } +} + +impl From for StateKeyWrapper { + fn from(value: StateKey) -> StateKeyWrapper { + Self(value) + } +} + +impl From for StateKey { + fn from(value: StateKeyWrapper) -> StateKey { + value.0 + } +} diff --git a/config/src/config/api_config.rs b/config/src/config/api_config.rs index b83f51cab9c05..57d3388f38b61 100644 --- a/config/src/config/api_config.rs +++ b/config/src/config/api_config.rs @@ -36,6 +36,8 @@ pub struct ApiConfig { /// Maximum page size for paginated APIs pub max_transactions_page_size: u16, pub max_events_page_size: u16, + pub max_account_resources_page_size: u16, + pub max_account_modules_page_size: u16, } pub const DEFAULT_ADDRESS: &str = "127.0.0.1"; @@ -43,6 +45,8 @@ pub const DEFAULT_PORT: u16 = 8080; pub const DEFAULT_REQUEST_CONTENT_LENGTH_LIMIT: u64 = 8 * 1024 * 1024; // 8 MB pub const DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE: usize = 10; pub const DEFAULT_MAX_PAGE_SIZE: u16 = 100; +pub const DEFAULT_MAX_ACCOUNT_RESOURCES_PAGE_SIZE: u16 = 9999; +pub const DEFAULT_MAX_ACCOUNT_MODULES_PAGE_SIZE: u16 = 9999; fn default_enabled() -> bool { true @@ -71,6 +75,8 @@ impl Default for ApiConfig { max_submit_transaction_batch_size: DEFAULT_MAX_SUBMIT_TRANSACTION_BATCH_SIZE, max_transactions_page_size: DEFAULT_MAX_PAGE_SIZE, max_events_page_size: DEFAULT_MAX_PAGE_SIZE, + max_account_resources_page_size: DEFAULT_MAX_ACCOUNT_RESOURCES_PAGE_SIZE, + max_account_modules_page_size: DEFAULT_MAX_ACCOUNT_MODULES_PAGE_SIZE, } } } diff --git a/ecosystem/typescript/sdk/src/generated/index.ts b/ecosystem/typescript/sdk/src/generated/index.ts index 97b072f2a5a85..3ae740a8e2452 100644 --- a/ecosystem/typescript/sdk/src/generated/index.ts +++ b/ecosystem/typescript/sdk/src/generated/index.ts @@ -63,6 +63,7 @@ export { RoleType } from './models/RoleType'; export type { ScriptPayload } from './models/ScriptPayload'; export type { ScriptWriteSet } from './models/ScriptWriteSet'; export type { StateCheckpointTransaction } from './models/StateCheckpointTransaction'; +export type { StateKeyWrapper } from './models/StateKeyWrapper'; export type { SubmitTransactionRequest } from './models/SubmitTransactionRequest'; export type { TableItemRequest } from './models/TableItemRequest'; export type { Transaction } from './models/Transaction'; @@ -154,6 +155,7 @@ export { $RoleType } from './schemas/$RoleType'; export { $ScriptPayload } from './schemas/$ScriptPayload'; export { $ScriptWriteSet } from './schemas/$ScriptWriteSet'; export { $StateCheckpointTransaction } from './schemas/$StateCheckpointTransaction'; +export { $StateKeyWrapper } from './schemas/$StateKeyWrapper'; export { $SubmitTransactionRequest } from './schemas/$SubmitTransactionRequest'; export { $TableItemRequest } from './schemas/$TableItemRequest'; export { $Transaction } from './schemas/$Transaction'; diff --git a/ecosystem/typescript/sdk/src/generated/models/StateKeyWrapper.ts b/ecosystem/typescript/sdk/src/generated/models/StateKeyWrapper.ts new file mode 100644 index 0000000000000..d3939a43e6a77 --- /dev/null +++ b/ecosystem/typescript/sdk/src/generated/models/StateKeyWrapper.ts @@ -0,0 +1,9 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ + +/** + * Representation of a StateKey as a hex string. This is used for cursor based pagination. + * + */ +export type StateKeyWrapper = string; diff --git a/ecosystem/typescript/sdk/src/generated/schemas/$StateKeyWrapper.ts b/ecosystem/typescript/sdk/src/generated/schemas/$StateKeyWrapper.ts new file mode 100644 index 0000000000000..e4b0eb8ec5263 --- /dev/null +++ b/ecosystem/typescript/sdk/src/generated/schemas/$StateKeyWrapper.ts @@ -0,0 +1,8 @@ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +export const $StateKeyWrapper = { + type: 'string', + description: `Representation of a StateKey as a hex string. This is used for cursor based pagination. + `, +} as const; diff --git a/ecosystem/typescript/sdk/src/generated/services/AccountsService.ts b/ecosystem/typescript/sdk/src/generated/services/AccountsService.ts index 0a059480f8492..46f59311ef8ad 100644 --- a/ecosystem/typescript/sdk/src/generated/services/AccountsService.ts +++ b/ecosystem/typescript/sdk/src/generated/services/AccountsService.ts @@ -7,6 +7,7 @@ import type { IdentifierWrapper } from '../models/IdentifierWrapper'; import type { MoveModuleBytecode } from '../models/MoveModuleBytecode'; import type { MoveResource } from '../models/MoveResource'; import type { MoveStructTag } from '../models/MoveStructTag'; +import type { StateKeyWrapper } from '../models/StateKeyWrapper'; import type { U64 } from '../models/U64'; import type { CancelablePromise } from '../core/CancelablePromise'; @@ -56,12 +57,23 @@ export class AccountsService { * @param ledgerVersion Ledger version to get state of account * * If not provided, it will be the latest version + * @param start Cursor specifying where to start for pagination + * + * This cursor cannot be derived manually client-side. Instead, you must + * call this endpoint once without this query parameter specified, and + * then use the cursor returned in the X-Aptos-Cursor header in the + * response. + * @param limit Max number of account resources to retrieve + * + * If not provided, defaults to default page size. * @returns MoveResource * @throws ApiError */ public getAccountResources( address: Address, ledgerVersion?: U64, + start?: StateKeyWrapper, + limit?: number, ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', @@ -71,6 +83,8 @@ export class AccountsService { }, query: { 'ledger_version': ledgerVersion, + 'start': start, + 'limit': limit, }, }); } @@ -86,12 +100,23 @@ export class AccountsService { * @param ledgerVersion Ledger version to get state of account * * If not provided, it will be the latest version + * @param start Cursor specifying where to start for pagination + * + * This cursor cannot be derived manually client-side. Instead, you must + * call this endpoint once without this query parameter specified, and + * then use the cursor returned in the X-Aptos-Cursor header in the + * response. + * @param limit Max number of account modules to retrieve + * + * If not provided, defaults to default page size. * @returns MoveModuleBytecode * @throws ApiError */ public getAccountModules( address: Address, ledgerVersion?: U64, + start?: StateKeyWrapper, + limit?: number, ): CancelablePromise> { return this.httpRequest.request({ method: 'GET', @@ -101,6 +126,8 @@ export class AccountsService { }, query: { 'ledger_version': ledgerVersion, + 'start': start, + 'limit': limit, }, }); }