From 9975a40e3814304513fb1c7bf2f8e13fd3ce065d Mon Sep 17 00:00:00 2001 From: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:27:14 +0200 Subject: [PATCH] Add indexer /outputs route (#1246) * Add indexer /outputs route * Merge imports * Add new GenericQueryParameter type * no-else-return * Add comment * Comment like this? * Add and use all_outputs() * Fix comment * Add cursor and pageSize to comment * Foundries included now * Update bindings/nodejs/lib/types/client/query-parameters.ts Co-authored-by: Thibault Martinez * Update sdk/src/client/node_api/indexer/routes.rs Co-authored-by: Thibault Martinez * Request foundry outputs extra if not not synced directly * Update sdk/src/client/node_api/indexer/routes.rs * Doc comment * Update comment --------- Co-authored-by: Thibault Martinez --- bindings/core/src/method/client.rs | 6 +++ bindings/core/src/method_handler/client.rs | 3 ++ bindings/core/src/response.rs | 1 + bindings/nodejs/CHANGELOG.md | 2 + bindings/nodejs/lib/client/client.ts | 17 ++++++++ .../nodejs/lib/types/client/bridge/client.ts | 8 ++++ .../nodejs/lib/types/client/bridge/index.ts | 2 + .../lib/types/client/query-parameters.ts | 17 +++++++- bindings/python/CHANGELOG.md | 2 + .../how_tos/alias/governance_transition.py | 6 ++- .../iota_sdk/client/_node_indexer_api.py | 19 +++++++++ sdk/CHANGELOG.md | 2 + .../node_api/indexer/query_parameters.rs | 20 +++++++++ sdk/src/client/node_api/indexer/routes.rs | 20 ++++++++- .../syncing/addresses/output_ids/mod.rs | 41 ++++++++++++++++++- .../account/operations/syncing/options.rs | 18 ++++++++ 16 files changed, 179 insertions(+), 5 deletions(-) diff --git a/bindings/core/src/method/client.rs b/bindings/core/src/method/client.rs index 5f64613de2..c095ffe9da 100644 --- a/bindings/core/src/method/client.rs +++ b/bindings/core/src/method/client.rs @@ -281,6 +281,12 @@ pub enum ClientMethod { ////////////////////////////////////////////////////////////////////// // Node indexer API ////////////////////////////////////////////////////////////////////// + /// Fetch alias/basic/NFT/foundry output IDs + #[serde(rename_all = "camelCase")] + OutputIds { + /// Query parameters for output requests + query_parameters: Vec, + }, /// Fetch basic output IDs #[serde(rename_all = "camelCase")] BasicOutputIds { diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index be1aa19f4d..f5c1e3a4a8 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -325,6 +325,9 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM ClientMethod::GetIncludedBlockMetadata { transaction_id } => { Response::BlockMetadata(client.get_included_block_metadata(&transaction_id).await?) } + ClientMethod::OutputIds { query_parameters } => { + Response::OutputIdsResponse(client.output_ids(query_parameters).await?) + } ClientMethod::BasicOutputIds { query_parameters } => { Response::OutputIdsResponse(client.basic_output_ids(query_parameters).await?) } diff --git a/bindings/core/src/response.rs b/bindings/core/src/response.rs index 1fc759662a..583d7e6bc9 100644 --- a/bindings/core/src/response.rs +++ b/bindings/core/src/response.rs @@ -168,6 +168,7 @@ pub enum Response { /// - [`BasicOutputIds`](crate::method::ClientMethod::BasicOutputIds) /// - [`FoundryOutputIds`](crate::method::ClientMethod::FoundryOutputIds) /// - [`NftOutputIds`](crate::method::ClientMethod::NftOutputIds) + /// - [`OutputIds`](crate::method::ClientMethod::OutputIds) OutputIdsResponse(OutputIdsResponse), /// Response for: /// - [`FindBlocks`](crate::method::ClientMethod::FindBlocks) diff --git a/bindings/nodejs/CHANGELOG.md b/bindings/nodejs/CHANGELOG.md index df82e74815..203d9f9861 100644 --- a/bindings/nodejs/CHANGELOG.md +++ b/bindings/nodejs/CHANGELOG.md @@ -24,6 +24,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `Account::{burn(), consolidateOutputs(), createAliasOutput(), meltNativeToken(), mintNativeToken(), createNativeToken(), mintNfts(), sendTransaction(), sendNativeTokens(), sendNft()}` methods; +- `Client::outputIds()` method; +- `GenericQueryParameter, UnlockableByAddress` types; ## 1.0.11 - 2023-09-14 diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index 9096fa9d41..bc6f7610ed 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -18,6 +18,7 @@ import { FoundryQueryParameter, NftQueryParameter, AliasQueryParameter, + GenericQueryParameter, } from '../types/client'; import type { INodeInfoWrapper } from '../types/client/nodeInfo'; import { @@ -94,6 +95,22 @@ export class Client { return JSON.parse(response).payload; } + /** + * Fetch alias/basic/NFT/foundry output IDs based on the given query parameters. + */ + async outputIds( + queryParameters: GenericQueryParameter[], + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'outputIds', + data: { + queryParameters, + }, + }); + + return JSON.parse(response).payload; + } + /** * Fetch basic output IDs based on the given query parameters. */ diff --git a/bindings/nodejs/lib/types/client/bridge/client.ts b/bindings/nodejs/lib/types/client/bridge/client.ts index 8e186a3f53..82f80393c6 100644 --- a/bindings/nodejs/lib/types/client/bridge/client.ts +++ b/bindings/nodejs/lib/types/client/bridge/client.ts @@ -12,6 +12,7 @@ import type { PreparedTransactionData } from '../prepared-transaction-data'; import type { AliasQueryParameter, FoundryQueryParameter, + GenericQueryParameter, NftQueryParameter, QueryParameter, } from '../query-parameters'; @@ -33,6 +34,13 @@ export interface __GetOutputMethod__ { }; } +export interface __GetOutputIdsMethod__ { + name: 'outputIds'; + data: { + queryParameters: GenericQueryParameter[]; + }; +} + export interface __GetBasicOutputIdsMethod__ { name: 'basicOutputIds'; data: { diff --git a/bindings/nodejs/lib/types/client/bridge/index.ts b/bindings/nodejs/lib/types/client/bridge/index.ts index 215076871e..e09d70f71b 100644 --- a/bindings/nodejs/lib/types/client/bridge/index.ts +++ b/bindings/nodejs/lib/types/client/bridge/index.ts @@ -3,6 +3,7 @@ import type { __GetInfoMethod__, + __GetOutputIdsMethod__, __GetBasicOutputIdsMethod__, __GetOutputMethod__, __GetOutputsMethod__, @@ -72,6 +73,7 @@ import type { export type __ClientMethods__ = | __GetInfoMethod__ | __GetOutputMethod__ + | __GetOutputIdsMethod__ | __GetBasicOutputIdsMethod__ | __GetOutputsMethod__ | __PostBlockMethod__ diff --git a/bindings/nodejs/lib/types/client/query-parameters.ts b/bindings/nodejs/lib/types/client/query-parameters.ts index 9cba7d8f79..7519d5ec2d 100644 --- a/bindings/nodejs/lib/types/client/query-parameters.ts +++ b/bindings/nodejs/lib/types/client/query-parameters.ts @@ -62,6 +62,17 @@ type CommonQueryParameters = | PageSize | Cursor; +/** Query parameters for filtering alias/basic/NFT/foundry Outputs*/ +export type GenericQueryParameter = + | UnlockableByAddress + | HasNativeTokens + | MinNativeTokenCount + | MaxNativeTokenCount + | CreatedAfter + | CreatedBefore + | PageSize + | Cursor; + /** Bech32-encoded address that should be searched for. */ interface Address { address: string; @@ -155,7 +166,11 @@ interface StateController { interface Governor { governor: string; } -/** Define the page size for the results */ +/** Define the page size for the results. */ interface PageSize { pageSize: number; } +/** Returns outputs that are unlockable by the bech32 address. */ +interface UnlockableByAddress { + unlockableByAddress: string; +} diff --git a/bindings/python/CHANGELOG.md b/bindings/python/CHANGELOG.md index 21acfb10f0..3572a4a646 100644 --- a/bindings/python/CHANGELOG.md +++ b/bindings/python/CHANGELOG.md @@ -26,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ConflictReason` display implementation with an explanation of the conflict; - `Account::{burn(), consolidate_outputs(), create_alias_output(), create_native_token(), melt_native_token(), mint_native_token(), mint_nfts(), send_transaction(), send_native_tokens(), send_nft()}` methods; +- `Client::output_ids()` method; +- `QueryParameter::unlockable_by_address` field; ## 1.0.2 - 2023-09-12 diff --git a/bindings/python/examples/how_tos/alias/governance_transition.py b/bindings/python/examples/how_tos/alias/governance_transition.py index 252df985e8..a2596400d4 100644 --- a/bindings/python/examples/how_tos/alias/governance_transition.py +++ b/bindings/python/examples/how_tos/alias/governance_transition.py @@ -44,10 +44,12 @@ def update_state_controller(unlock_condition): + """ + Replace the address in the StateControllerAddressUnlockCondition + """ if unlock_condition.type == UnlockConditionType.StateControllerAddress: return StateControllerAddressUnlockCondition(new_state_controller) - else: - return unlock_condition + return unlock_condition updated_unlock_conditions = list(map( diff --git a/bindings/python/iota_sdk/client/_node_indexer_api.py b/bindings/python/iota_sdk/client/_node_indexer_api.py index 4baf4c2043..a65a22b194 100644 --- a/bindings/python/iota_sdk/client/_node_indexer_api.py +++ b/bindings/python/iota_sdk/client/_node_indexer_api.py @@ -66,6 +66,8 @@ class QueryParameters: Returns outputs that are timelocked after a certain Unix timestamp. timelocked_before : Returns outputs that are timelocked before a certain Unix timestamp. + unlockable_by_address : + Returns outputs that are unlockable by the bech32 address. """ address: Optional[str] = None alias_address: Optional[str] = None @@ -90,6 +92,7 @@ class QueryParameters: tag: Optional[str] = None timelocked_after: Optional[int] = None timelocked_before: Optional[int] = None + unlockable_by_address: Optional[str] = None def as_dict(self): return humps.camelize( @@ -110,6 +113,22 @@ def __init__(self, dict: Dict): self.items = [OutputId.from_string( output_id) for output_id in dict["items"]] + def output_ids( + self, query_parameters: QueryParameters) -> OutputIdsResponse: + """Fetch alias/basic/NFT/foundry output IDs from the given query parameters. + Supported query parameters are: "hasNativeTokens", "minNativeTokenCount", "maxNativeTokenCount", "unlockableByAddress", "createdBefore", "createdAfter", "cursor", "pageSize". + + Returns: + The corresponding output IDs of the outputs. + """ + + query_parameters_camelized = query_parameters.as_dict() + + response = self._call_method('outputIds', { + 'queryParameters': query_parameters_camelized, + }) + return self.OutputIdsResponse(response) + def basic_output_ids( self, query_parameters: QueryParameters) -> OutputIdsResponse: """Fetch basic output IDs from the given query parameters. diff --git a/sdk/CHANGELOG.md b/sdk/CHANGELOG.md index 2dbc64c595..26d9189eb3 100644 --- a/sdk/CHANGELOG.md +++ b/sdk/CHANGELOG.md @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `ConflictReason` display implementation with an explanation of the conflict; - `TokenScheme` methods `is_simple` and `as_simple`; - `Irc27Metadata` and `Irc30Metadata` helpers; +- `Client::output_ids()` method; +- `QueryParameter::UnlockableByAddress` variant; ### Changed diff --git a/sdk/src/client/node_api/indexer/query_parameters.rs b/sdk/src/client/node_api/indexer/query_parameters.rs index 484f18509a..5849089e67 100644 --- a/sdk/src/client/node_api/indexer/query_parameters.rs +++ b/sdk/src/client/node_api/indexer/query_parameters.rs @@ -126,6 +126,8 @@ pub enum QueryParameter { TimelockedAfter(u32), /// Returns outputs that are timelocked before a certain Unix timestamp. TimelockedBefore(u32), + /// Returns outputs that are unlockable by the bech32 address. + UnlockableByAddress(Bech32Address), } impl QueryParameter { @@ -154,6 +156,7 @@ impl QueryParameter { Self::Tag(v) => format!("tag={v}"), Self::TimelockedAfter(v) => format!("timelockedAfter={v}"), Self::TimelockedBefore(v) => format!("timelockedBefore={v}"), + Self::UnlockableByAddress(v) => format!("unlockableByAddress={v}"), } } @@ -182,6 +185,7 @@ impl QueryParameter { Self::Tag(_) => 20, Self::TimelockedAfter(_) => 21, Self::TimelockedBefore(_) => 22, + Self::UnlockableByAddress(_) => 23, } } } @@ -204,6 +208,22 @@ macro_rules! verify_query_parameters { }; } +pub(crate) fn verify_query_parameters_outputs(query_parameters: Vec) -> Result { + verify_query_parameters!( + query_parameters, + QueryParameter::HasNativeTokens, + QueryParameter::MinNativeTokenCount, + QueryParameter::MaxNativeTokenCount, + QueryParameter::CreatedBefore, + QueryParameter::CreatedAfter, + QueryParameter::PageSize, + QueryParameter::Cursor, + QueryParameter::UnlockableByAddress + )?; + + Ok(QueryParameters::new(query_parameters)) +} + pub(crate) fn verify_query_parameters_basic_outputs(query_parameters: Vec) -> Result { verify_query_parameters!( query_parameters, diff --git a/sdk/src/client/node_api/indexer/routes.rs b/sdk/src/client/node_api/indexer/routes.rs index 81090976aa..35a825283c 100644 --- a/sdk/src/client/node_api/indexer/routes.rs +++ b/sdk/src/client/node_api/indexer/routes.rs @@ -8,7 +8,8 @@ use crate::{ node_api::indexer::{ query_parameters::{ verify_query_parameters_alias_outputs, verify_query_parameters_basic_outputs, - verify_query_parameters_foundry_outputs, verify_query_parameters_nft_outputs, QueryParameter, + verify_query_parameters_foundry_outputs, verify_query_parameters_nft_outputs, + verify_query_parameters_outputs, QueryParameter, }, QueryParameters, }, @@ -23,6 +24,23 @@ use crate::{ // hornet: https://github.com/gohornet/hornet/blob/develop/plugins/indexer/routes.go impl ClientInner { + /// Get basic, alias, nft and foundry outputs filtered by the given parameters. + /// GET with query parameter returns all outputIDs that fit these filter criteria. + /// Query parameters: "hasNativeTokens", "minNativeTokenCount", "maxNativeTokenCount", "unlockableByAddress", + /// "createdBefore", "createdAfter", "cursor", "pageSize". + /// Returns Err(Node(NotFound) if no results are found. + /// api/indexer/v1/outputs + pub async fn output_ids( + &self, + query_parameters: impl Into> + Send, + ) -> Result { + let route = "api/indexer/v1/outputs"; + + let query_parameters = verify_query_parameters_outputs(query_parameters.into())?; + + self.get_output_ids(route, query_parameters, true, false).await + } + /// Get basic outputs filtered by the given parameters. /// GET with query parameter returns all outputIDs that fit these filter criteria. /// Query parameters: "address", "hasStorageDepositReturn", "storageDepositReturnAddress", diff --git a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs b/sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs index db16f77e0d..6cbec6d5b5 100644 --- a/sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs +++ b/sdk/src/wallet/account/operations/syncing/addresses/output_ids/mod.rs @@ -12,7 +12,7 @@ use futures::FutureExt; use instant::Instant; use crate::{ - client::secret::SecretManage, + client::{node_api::indexer::QueryParameter, secret::SecretManage}, types::block::{ address::{Address, Bech32Address}, output::OutputId, @@ -44,6 +44,18 @@ where return Ok(output_ids); } + // If interested in alias, basic, NFT and foundry outputs, get them all at once + if (address.is_ed25519() && sync_options.account.all_outputs()) + || (address.is_nft() && sync_options.nft.all_outputs()) + || (address.is_alias() && sync_options.alias.all_outputs()) + { + return Ok(self + .client() + .output_ids([QueryParameter::UnlockableByAddress(bech32_address)]) + .await? + .items); + } + #[cfg(target_family = "wasm")] let mut results = Vec::new(); @@ -136,6 +148,33 @@ where .boxed(), ); } + } else if address.is_alias() && sync_options.alias.foundry_outputs { + // foundries + #[cfg(target_family = "wasm")] + { + results.push(Ok(self + .client() + .foundry_output_ids([QueryParameter::AliasAddress(bech32_address)]) + .await? + .items)) + } + + #[cfg(not(target_family = "wasm"))] + { + tasks.push( + async move { + let client = self.client().clone(); + tokio::spawn(async move { + Ok(client + .foundry_output_ids([QueryParameter::AliasAddress(bech32_address)]) + .await? + .items) + }) + .await + } + .boxed(), + ); + } } #[cfg(not(target_family = "wasm"))] diff --git a/sdk/src/wallet/account/operations/syncing/options.rs b/sdk/src/wallet/account/operations/syncing/options.rs index ec9e66900c..4c6c2ee37c 100644 --- a/sdk/src/wallet/account/operations/syncing/options.rs +++ b/sdk/src/wallet/account/operations/syncing/options.rs @@ -119,6 +119,12 @@ impl Default for AccountSyncOptions { } } +impl AccountSyncOptions { + pub(crate) fn all_outputs(&self) -> bool { + self.basic_outputs && self.nft_outputs && self.alias_outputs + } +} + /// Sync options for addresses from alias outputs #[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] @@ -141,6 +147,12 @@ impl Default for AliasSyncOptions { } } +impl AliasSyncOptions { + pub(crate) fn all_outputs(&self) -> bool { + self.basic_outputs && self.nft_outputs && self.alias_outputs && self.foundry_outputs + } +} + /// Sync options for addresses from NFT outputs #[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)] #[serde(default, rename_all = "camelCase")] @@ -149,3 +161,9 @@ pub struct NftSyncOptions { pub nft_outputs: bool, pub alias_outputs: bool, } + +impl NftSyncOptions { + pub(crate) fn all_outputs(&self) -> bool { + self.basic_outputs && self.nft_outputs && self.alias_outputs + } +}