Skip to content

Commit

Permalink
Add indexer /outputs route (#1246)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update sdk/src/client/node_api/indexer/routes.rs

Co-authored-by: Thibault Martinez <[email protected]>

* 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 <[email protected]>
  • Loading branch information
Thoralf-M and thibault-martinez authored Sep 19, 2023
1 parent 4dfc5b5 commit 9975a40
Show file tree
Hide file tree
Showing 16 changed files with 179 additions and 5 deletions.
6 changes: 6 additions & 0 deletions bindings/core/src/method/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<QueryParameter>,
},
/// Fetch basic output IDs
#[serde(rename_all = "camelCase")]
BasicOutputIds {
Expand Down
3 changes: 3 additions & 0 deletions bindings/core/src/method_handler/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?)
}
Expand Down
1 change: 1 addition & 0 deletions bindings/core/src/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions bindings/nodejs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 17 additions & 0 deletions bindings/nodejs/lib/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
FoundryQueryParameter,
NftQueryParameter,
AliasQueryParameter,
GenericQueryParameter,
} from '../types/client';
import type { INodeInfoWrapper } from '../types/client/nodeInfo';
import {
Expand Down Expand Up @@ -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<IOutputsResponse> {
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.
*/
Expand Down
8 changes: 8 additions & 0 deletions bindings/nodejs/lib/types/client/bridge/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { PreparedTransactionData } from '../prepared-transaction-data';
import type {
AliasQueryParameter,
FoundryQueryParameter,
GenericQueryParameter,
NftQueryParameter,
QueryParameter,
} from '../query-parameters';
Expand All @@ -33,6 +34,13 @@ export interface __GetOutputMethod__ {
};
}

export interface __GetOutputIdsMethod__ {
name: 'outputIds';
data: {
queryParameters: GenericQueryParameter[];
};
}

export interface __GetBasicOutputIdsMethod__ {
name: 'basicOutputIds';
data: {
Expand Down
2 changes: 2 additions & 0 deletions bindings/nodejs/lib/types/client/bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import type {
__GetInfoMethod__,
__GetOutputIdsMethod__,
__GetBasicOutputIdsMethod__,
__GetOutputMethod__,
__GetOutputsMethod__,
Expand Down Expand Up @@ -72,6 +73,7 @@ import type {
export type __ClientMethods__ =
| __GetInfoMethod__
| __GetOutputMethod__
| __GetOutputIdsMethod__
| __GetBasicOutputIdsMethod__
| __GetOutputsMethod__
| __PostBlockMethod__
Expand Down
17 changes: 16 additions & 1 deletion bindings/nodejs/lib/types/client/query-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
2 changes: 2 additions & 0 deletions bindings/python/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
19 changes: 19 additions & 0 deletions bindings/python/iota_sdk/client/_node_indexer_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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(
Expand All @@ -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.
Expand Down
2 changes: 2 additions & 0 deletions sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
20 changes: 20 additions & 0 deletions sdk/src/client/node_api/indexer/query_parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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}"),
}
}

Expand Down Expand Up @@ -182,6 +185,7 @@ impl QueryParameter {
Self::Tag(_) => 20,
Self::TimelockedAfter(_) => 21,
Self::TimelockedBefore(_) => 22,
Self::UnlockableByAddress(_) => 23,
}
}
}
Expand All @@ -204,6 +208,22 @@ macro_rules! verify_query_parameters {
};
}

pub(crate) fn verify_query_parameters_outputs(query_parameters: Vec<QueryParameter>) -> Result<QueryParameters> {
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<QueryParameter>) -> Result<QueryParameters> {
verify_query_parameters!(
query_parameters,
Expand Down
20 changes: 19 additions & 1 deletion sdk/src/client/node_api/indexer/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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<Vec<QueryParameter>> + Send,
) -> Result<OutputIdsResponse> {
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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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"))]
Expand Down
18 changes: 18 additions & 0 deletions sdk/src/wallet/account/operations/syncing/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand All @@ -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")]
Expand All @@ -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
}
}

0 comments on commit 9975a40

Please sign in to comment.