diff --git a/bindings/core/src/method/client.rs b/bindings/core/src/method/client.rs index 9fd4892db9..7ce48daf54 100644 --- a/bindings/core/src/method/client.rs +++ b/bindings/core/src/method/client.rs @@ -156,15 +156,18 @@ pub enum ClientMethod { /// The Account ID of the account. account_id: AccountId, }, - /// Returns the totally available Mana rewards of an account or delegation output decayed up to endEpoch index - /// provided in the response. + /// Returns all the available Mana rewards of an account or delegation output in the returned range of epochs. #[serde(rename_all = "camelCase")] GetRewards { /// Output ID of an account or delegation output. output_id: OutputId, /// A client can specify a slot index explicitly, which should be equal to the slot it uses as the commitment - /// input for the claiming transaction. This parameter is only recommended to be provided when requesting - /// rewards for a Delegation Output in delegating state (i.e. when Delegation ID is zeroed). + /// input for the claiming transaction to ensure the node calculates the rewards identically as during + /// transaction execution. Rewards are decayed up to the epoch corresponding to the given slotIndex + + /// MinCommittableAge. For a Delegation Output in delegating state (i.e. when Delegation ID is zeroed), that + /// epoch - 1 is also used as the last epoch for which rewards are fetched. Callers that do not build + /// transactions with the returned values may omit this value in which case it defaults to the latest committed + /// slot, which is good enough to, e.g. display estimated rewards to users. slot_index: Option, }, /// Returns information of all registered validators and if they are active, ordered by their holding stake. @@ -282,17 +285,17 @@ pub enum ClientMethod { /// Look up a commitment by a given commitment index. GetCommitmentByIndex { /// Index of the commitment to look up. - index: SlotIndex, + slot: SlotIndex, }, /// Get all UTXO changes of a given slot by commitment index. GetUtxoChangesByIndex { /// Index of the commitment to look up. - index: SlotIndex, + slot: SlotIndex, }, /// Get all full UTXO changes of a given slot by commitment index. GetUtxoChangesFullByIndex { /// Index of the commitment to look up. - index: SlotIndex, + slot: SlotIndex, }, ////////////////////////////////////////////////////////////////////// diff --git a/bindings/core/src/method_handler/client.rs b/bindings/core/src/method_handler/client.rs index b5f2ae738f..629ec5251b 100644 --- a/bindings/core/src/method_handler/client.rs +++ b/bindings/core/src/method_handler/client.rs @@ -9,7 +9,7 @@ use iota_sdk::{ api::core::OutputWithMetadataResponse, block::{ output::{ - AccountOutput, BasicOutput, FoundryOutput, MinimumOutputAmount, NftOutput, Output, OutputBuilderAmount, + AccountOutputBuilder, BasicOutputBuilder, FoundryOutputBuilder, MinimumOutputAmount, NftOutputBuilder, }, payload::Payload, Block, BlockDto, UnsignedBlockDto, @@ -64,21 +64,23 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM features, immutable_features, } => { - let output = Output::from(AccountOutput::try_from_dtos( - if let Some(amount) = amount { - OutputBuilderAmount::Amount(amount) - } else { - OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) - }, - mana, - &account_id, - foundry_counter, - unlock_conditions, - features, - immutable_features, - )?); + let mut output_builder = if let Some(amount) = amount { + AccountOutputBuilder::new_with_amount(amount, account_id) + } else { + AccountOutputBuilder::new_with_minimum_amount(client.get_storage_score_parameters().await?, account_id) + } + .with_mana(mana) + .with_foundry_counter(foundry_counter) + .with_unlock_conditions(unlock_conditions); + + if let Some(features) = features { + output_builder = output_builder.with_features(features); + } + if let Some(immutable_features) = immutable_features { + output_builder = output_builder.with_immutable_features(immutable_features) + } - Response::Output(output) + Response::Output(output_builder.finish_output()?) } ClientMethod::BuildBasicOutput { amount, @@ -86,18 +88,19 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM unlock_conditions, features, } => { - let output = Output::from(BasicOutput::try_from_dtos( - if let Some(amount) = amount { - OutputBuilderAmount::Amount(amount) - } else { - OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) - }, - mana, - unlock_conditions, - features, - )?); + let mut output_builder = if let Some(amount) = amount { + BasicOutputBuilder::new_with_amount(amount) + } else { + BasicOutputBuilder::new_with_minimum_amount(client.get_storage_score_parameters().await?) + } + .with_mana(mana) + .with_unlock_conditions(unlock_conditions); - Response::Output(output) + if let Some(features) = features { + output_builder = output_builder.with_features(features); + } + + Response::Output(output_builder.finish_output()?) } ClientMethod::BuildFoundryOutput { amount, @@ -107,20 +110,25 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM features, immutable_features, } => { - let output = Output::from(FoundryOutput::try_from_dtos( - if let Some(amount) = amount { - OutputBuilderAmount::Amount(amount) - } else { - OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) - }, - serial_number, - token_scheme, - unlock_conditions, - features, - immutable_features, - )?); + let mut output_builder = if let Some(amount) = amount { + FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme) + } else { + FoundryOutputBuilder::new_with_minimum_amount( + client.get_storage_score_parameters().await?, + serial_number, + token_scheme, + ) + } + .with_unlock_conditions(unlock_conditions); - Response::Output(output) + if let Some(features) = features { + output_builder = output_builder.with_features(features); + } + if let Some(immutable_features) = immutable_features { + output_builder = output_builder.with_immutable_features(immutable_features) + } + + Response::Output(output_builder.finish_output()?) } ClientMethod::BuildNftOutput { amount, @@ -130,20 +138,22 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM features, immutable_features, } => { - let output = Output::from(NftOutput::try_from_dtos( - if let Some(amount) = amount { - OutputBuilderAmount::Amount(amount) - } else { - OutputBuilderAmount::MinimumAmount(client.get_storage_score_parameters().await?) - }, - mana, - &nft_id, - unlock_conditions, - features, - immutable_features, - )?); + let mut output_builder = if let Some(amount) = amount { + NftOutputBuilder::new_with_amount(amount, nft_id) + } else { + NftOutputBuilder::new_with_minimum_amount(client.get_storage_score_parameters().await?, nft_id) + } + .with_mana(mana) + .with_unlock_conditions(unlock_conditions); + + if let Some(features) = features { + output_builder = output_builder.with_features(features); + } + if let Some(immutable_features) = immutable_features { + output_builder = output_builder.with_immutable_features(immutable_features) + } - Response::Output(output) + Response::Output(output_builder.finish_output()?) } ClientMethod::BuildBasicBlock { issuer_id, payload } => { let payload = if let Some(payload) = payload { @@ -241,14 +251,14 @@ pub(crate) async fn call_client_method_internal(client: &Client, method: ClientM .get_utxo_changes_full_by_slot_commitment_id(&commitment_id) .await?, ), - ClientMethod::GetCommitmentByIndex { index } => { - Response::SlotCommitment(client.get_slot_commitment_by_slot(index).await?) + ClientMethod::GetCommitmentByIndex { slot } => { + Response::SlotCommitment(client.get_slot_commitment_by_slot(slot).await?) } - ClientMethod::GetUtxoChangesByIndex { index } => { - Response::UtxoChanges(client.get_utxo_changes_by_slot(index).await?) + ClientMethod::GetUtxoChangesByIndex { slot } => { + Response::UtxoChanges(client.get_utxo_changes_by_slot(slot).await?) } - ClientMethod::GetUtxoChangesFullByIndex { index } => { - Response::UtxoChangesFull(client.get_utxo_changes_full_by_slot(index).await?) + ClientMethod::GetUtxoChangesFullByIndex { slot } => { + Response::UtxoChangesFull(client.get_utxo_changes_full_by_slot(slot).await?) } ClientMethod::OutputIds { query_parameters } => { Response::OutputIdsResponse(client.output_ids(query_parameters).await?) diff --git a/bindings/nodejs/examples/exchange/4-listen-events.ts b/bindings/nodejs/examples/exchange/4-listen-events.ts index 09e93b7f45..d80cfd2046 100644 --- a/bindings/nodejs/examples/exchange/4-listen-events.ts +++ b/bindings/nodejs/examples/exchange/4-listen-events.ts @@ -5,14 +5,14 @@ // Run with command: // yarn run-example ./exchange/4-listen-events.ts -import { Wallet, Event, WalletEventType } from '@iota/sdk'; +import { Wallet, WalletEvent, WalletEventType } from '@iota/sdk'; // This example uses secrets in environment variables for simplicity which should not be done in production. require('dotenv').config({ path: '.env' }); async function run() { try { - for (const envVar of ['WALLET_DB_PATH']) { + for (const envVar of ['WALLET_DB_PATH', 'FAUCET_URL']) { if (!(envVar in process.env)) { throw new Error( `.env ${envVar} is undefined, see .env.example`, @@ -24,9 +24,8 @@ async function run() { storagePath: process.env.WALLET_DB_PATH, }); - const callback = function (err: any, event: Event) { - console.log('AccountIndex:', event.accountIndex); - console.log('Event:', event.event); + const callback = function (err: any, event: WalletEvent) { + console.log('Event:', event); // Exit after receiving an event. process.exit(0); @@ -37,7 +36,7 @@ async function run() { // Use the faucet to send testnet tokens to your address. console.log( - 'Fill your address with the faucet: https://faucet.testnet.shimmer.network/', + `Fill your address with the faucet: ${process.env.FAUCET_URL}`, ); const address = await wallet.address(); diff --git a/bindings/nodejs/examples/wallet/events.ts b/bindings/nodejs/examples/wallet/events.ts index f11178fc82..d0f8f4f5fd 100644 --- a/bindings/nodejs/examples/wallet/events.ts +++ b/bindings/nodejs/examples/wallet/events.ts @@ -2,8 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { - Event, - ConsolidationRequiredWalletEvent, + WalletEvent, TransactionProgressWalletEvent, SelectingInputsProgress, } from '@iota/sdk'; @@ -24,13 +23,12 @@ async function run() { // Create the wallet const wallet = await getUnlockedWallet(); - const callback = function (err: any, event: Event) { + const callback = function (err: any, event: WalletEvent) { console.log('Event:', event); }; await wallet.listen([], callback); - await wallet.emitTestEvent(new ConsolidationRequiredWalletEvent()); await wallet.emitTestEvent( new TransactionProgressWalletEvent(new SelectingInputsProgress()), ); diff --git a/bindings/nodejs/lib/client/client.ts b/bindings/nodejs/lib/client/client.ts index e67935cf7d..082c52bbfa 100644 --- a/bindings/nodejs/lib/client/client.ts +++ b/bindings/nodejs/lib/client/client.ts @@ -44,12 +44,14 @@ import { DelegationId, UnsignedBlock, parseUnsignedBlock, + SlotIndex, + SlotCommitmentId, + SlotCommitment, } from '../types/block'; import { HexEncodedString } from '../utils'; import { IBlockMetadata, INodeInfo, - IPeer, UTXOInput, Response, OutputId, @@ -58,10 +60,19 @@ import { TransactionId, Bech32Address, IBlockWithMetadata, + TransactionMetadata, } from '../types'; -import { OutputResponse, IOutputsResponse } from '../types/models/api'; +import { + OutputResponse, + IOutputsResponse, + CongestionResponse, + UtxoChangesResponse, + UtxoChangesFullResponse, +} from '../types/models/api'; import { plainToInstance } from 'class-transformer'; +import { ManaRewardsResponse } from '../types/models/api/mana-rewards-response'; +import { ValidatorsResponse } from '../types/models/api/validators-response'; /** The Client to interact with nodes. */ export class Client { @@ -106,6 +117,74 @@ export class Client { return JSON.parse(response).payload; } + /** + * Check the readiness of the node to issue a new block, the reference mana cost based on the rate setter and + * current network congestion, and the block issuance credits of the requested account. + */ + async getAccountCongestion( + accountId: AccountId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getAccountCongestion', + data: { + accountId, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Returns the totally available Mana rewards of an account or delegation output decayed up to endEpoch index + * provided in the response. + */ + async getRewards( + outputId: OutputId, + slotIndex?: SlotIndex, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getRewards', + data: { + outputId, + slotIndex, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Returns information of all registered validators and if they are active, ordered by their holding stake. + */ + async getValidators( + pageSize?: number, + cursor?: string, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getValidators', + data: { + pageSize, + cursor, + }, + }); + + return JSON.parse(response).payload; + } + + /** + * Return information about a validator. + */ + async getValidator(accountId: AccountId): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getValidator', + data: { + accountId, + }, + }); + + return JSON.parse(response).payload; + } + /** * Get output from a given output ID. */ @@ -393,17 +472,6 @@ export class Client { return JSON.parse(response).payload; } - /** - * Get the peers of the node. - */ - async getPeers(): Promise { - const response = await this.methodHandler.callMethod({ - name: 'getPeers', - }); - - return JSON.parse(response).payload; - } - /** * Post block as raw bytes, returns the block ID. * @@ -463,15 +531,136 @@ export class Client { */ async getIncludedBlockMetadata( transactionId: TransactionId, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'getIncludedBlockMetadata', data: { transactionId, }, }); - const parsed = JSON.parse(response) as Response; - return parseBlock(parsed.payload); + return JSON.parse(response).payload; + } + + /** + * Find the metadata of a transaction. + * + * @param transactionId The ID of the transaction. + * @returns The transaction metadata. + */ + async getTransactionMetadata( + transactionId: TransactionId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getTransactionMetadata', + data: { + transactionId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Look up a commitment by a given commitment ID. + * + * @param commitmentId Commitment ID of the commitment to look up. + * @returns The commitment. + */ + async getCommitment( + commitmentId: SlotCommitmentId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getCommitment', + data: { + commitmentId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get all UTXO changes of a given slot by Commitment ID. + * + * @param commitmentId Commitment ID of the commitment to look up. + * @returns The UTXO changes. + */ + async getUtxoChanges( + commitmentId: SlotCommitmentId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getUtxoChanges', + data: { + commitmentId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get all full UTXO changes of a given slot by Commitment ID. + * + * @param commitmentId Commitment ID of the commitment to look up. + * @returns The UTXO changes. + */ + async getUtxoChangesFull( + commitmentId: SlotCommitmentId, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getUtxoChangesFull', + data: { + commitmentId, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Look up a commitment by a given commitment index. + * + * @param slot Index of the commitment to look up. + * @returns The commitment. + */ + async getCommitmentByIndex(slot: SlotIndex): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getCommitmentByIndex', + data: { + slot, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get all UTXO changes of a given slot by commitment index. + * + * @param slot Index of the commitment to look up. + * @returns The UTXO changes. + */ + async getUtxoChangesByIndex(slot: SlotIndex): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getUtxoChangesByIndex', + data: { + slot, + }, + }); + return JSON.parse(response).payload; + } + + /** + * Get all full UTXO changes of a given slot by commitment index. + * + * @param slot Index of the commitment to look up. + * @returns The UTXO changes. + */ + async getUtxoChangesFullByIndex( + slot: SlotIndex, + ): Promise { + const response = await this.methodHandler.callMethod({ + name: 'getUtxoChangesFullByIndex', + data: { + slot, + }, + }); + return JSON.parse(response).payload; } /** diff --git a/bindings/nodejs/lib/types/client/bridge/client.ts b/bindings/nodejs/lib/types/client/bridge/client.ts index 93bc8f9aec..2919d75f52 100644 --- a/bindings/nodejs/lib/types/client/bridge/client.ts +++ b/bindings/nodejs/lib/types/client/bridge/client.ts @@ -16,6 +16,7 @@ import type { Output, OutputId, Payload, + SlotIndex, } from '../../block'; import type { PreparedTransactionData } from '../prepared-transaction-data'; import type { @@ -68,6 +69,36 @@ export interface __GetNetworkInfoMethod__ { name: 'getNetworkInfo'; } +export interface __GetAccountCongestionMethod__ { + name: 'getAccountCongestion'; + data: { + accountId: AccountId; + }; +} + +export interface __GetRewardsMethod__ { + name: 'getRewards'; + data: { + outputId: OutputId; + slotIndex?: SlotIndex; + }; +} + +export interface __GetValidatorsMethod__ { + name: 'getValidators'; + data: { + pageSize?: number; + cursor?: string; + }; +} + +export interface __GetValidatorMethod__ { + name: 'getValidator'; + data: { + accountId: AccountId; + }; +} + export interface __GetBlockMethod__ { name: 'getBlock'; data: { @@ -153,10 +184,6 @@ export interface __GetNodeInfoMethod__ { }; } -export interface __GetPeersMethod__ { - name: 'getPeers'; -} - export interface __PostBlockRawMethod__ { name: 'postBlockRaw'; data: { @@ -185,6 +212,55 @@ export interface __GetIncludedBlockMetadataMethod__ { }; } +export interface __GetTransactionMetadataMethod__ { + name: 'getTransactionMetadata'; + data: { + transactionId: TransactionId; + }; +} + +export interface __GetCommitmentMethod__ { + name: 'getCommitment'; + data: { + commitmentId: HexEncodedString; + }; +} + +export interface __GetUtxoChangesMethod__ { + name: 'getUtxoChanges'; + data: { + commitmentId: HexEncodedString; + }; +} + +export interface __GetUtxoChangesFullMethod__ { + name: 'getUtxoChangesFull'; + data: { + commitmentId: HexEncodedString; + }; +} + +export interface __GetCommitmentByIndexMethod__ { + name: 'getCommitmentByIndex'; + data: { + slot: SlotIndex; + }; +} + +export interface __GetUtxoChangesByIndexMethod__ { + name: 'getUtxoChangesByIndex'; + data: { + slot: SlotIndex; + }; +} + +export interface __GetUtxoChangesFullByIndexMethod__ { + name: 'getUtxoChangesFullByIndex'; + data: { + slot: SlotIndex; + }; +} + export interface __HexToBech32Method__ { name: 'hexToBech32'; data: { diff --git a/bindings/nodejs/lib/types/client/bridge/index.ts b/bindings/nodejs/lib/types/client/bridge/index.ts index 20de332b70..743c428eda 100644 --- a/bindings/nodejs/lib/types/client/bridge/index.ts +++ b/bindings/nodejs/lib/types/client/bridge/index.ts @@ -10,6 +10,10 @@ import type { __PostBlockMethod__, __GetTipsMethod__, __GetNetworkInfoMethod__, + __GetAccountCongestionMethod__, + __GetRewardsMethod__, + __GetValidatorsMethod__, + __GetValidatorMethod__, __GetBlockMethod__, __GetBlockMetadataMethod__, __GetBlockWithMetadataMethod__, @@ -22,11 +26,17 @@ import type { __GetProtocolParametersMethod__, __GetHealthMethod__, __GetNodeInfoMethod__, - __GetPeersMethod__, __PostBlockRawMethod__, __GetBlockRawMethod__, __GetIncludedBlockMethod__, __GetIncludedBlockMetadataMethod__, + __GetTransactionMetadataMethod__, + __GetCommitmentMethod__, + __GetUtxoChangesMethod__, + __GetUtxoChangesFullMethod__, + __GetCommitmentByIndexMethod__, + __GetUtxoChangesByIndexMethod__, + __GetUtxoChangesFullByIndexMethod__, __HexToBech32Method__, __AccountIdToBech32Method__, __NftIdToBech32Method__, @@ -65,6 +75,10 @@ export type __ClientMethods__ = | __PostBlockMethod__ | __GetTipsMethod__ | __GetNetworkInfoMethod__ + | __GetAccountCongestionMethod__ + | __GetRewardsMethod__ + | __GetValidatorsMethod__ + | __GetValidatorMethod__ | __GetBlockMethod__ | __GetBlockMetadataMethod__ | __GetBlockWithMetadataMethod__ @@ -78,11 +92,17 @@ export type __ClientMethods__ = | __GetProtocolParametersMethod__ | __GetHealthMethod__ | __GetNodeInfoMethod__ - | __GetPeersMethod__ | __PostBlockRawMethod__ | __GetBlockRawMethod__ | __GetIncludedBlockMethod__ | __GetIncludedBlockMetadataMethod__ + | __GetTransactionMetadataMethod__ + | __GetCommitmentMethod__ + | __GetUtxoChangesMethod__ + | __GetUtxoChangesFullMethod__ + | __GetCommitmentByIndexMethod__ + | __GetUtxoChangesByIndexMethod__ + | __GetUtxoChangesFullByIndexMethod__ | __HexToBech32Method__ | __AccountIdToBech32Method__ | __NftIdToBech32Method__ diff --git a/bindings/nodejs/lib/types/models/api/congestion-response.ts b/bindings/nodejs/lib/types/models/api/congestion-response.ts new file mode 100644 index 0000000000..cf40d8effd --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/congestion-response.ts @@ -0,0 +1,27 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { SlotIndex } from '../../block/slot'; +import { u64 } from '../../utils'; + +/** + * Provides the cost and readiness to issue estimates. + */ +export class CongestionResponse { + /** + * The slot index for which the congestion estimate is provided. + */ + slot!: SlotIndex; + /** + * Indicates if a node is ready to issue a block in a current congestion or should wait. + */ + ready!: boolean; + /** + * The cost in mana for issuing a block in a current congestion estimated based on RMC and slot index. + */ + referenceManaCost!: u64; + /** + * The Block Issuance Credits of the requested account. + */ + blockIssuanceCredits!: u64; +} diff --git a/bindings/nodejs/lib/types/models/api/index.ts b/bindings/nodejs/lib/types/models/api/index.ts index bf835a1377..703fe9a5b0 100644 --- a/bindings/nodejs/lib/types/models/api/index.ts +++ b/bindings/nodejs/lib/types/models/api/index.ts @@ -4,7 +4,11 @@ export * from './plugins'; export * from './block-id-response'; +export * from './congestion-response'; +export * from './mana-rewards-response'; export * from './output-metadata-response'; export * from './output-response'; export * from './response'; export * from './tips-response'; +export * from './validators-response'; +export * from './utxo-changes-response'; diff --git a/bindings/nodejs/lib/types/models/api/mana-rewards-response.ts b/bindings/nodejs/lib/types/models/api/mana-rewards-response.ts new file mode 100644 index 0000000000..0a5f93d196 --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/mana-rewards-response.ts @@ -0,0 +1,31 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { EpochIndex } from '../../block/slot'; +import { u64 } from '../../utils'; + +/** + * The mana rewards of an account or delegation output. + */ +export class ManaRewardsResponse { + /** + * The starting epoch index for which the mana rewards are returned. + */ + startEpoch!: EpochIndex; + /** + * The ending epoch index for which the mana rewards are returned, the decay is applied up to this point + * included. + */ + endEpoch!: EpochIndex; + /** + * The amount of totally available rewards the requested output may claim. + */ + rewards!: u64; + /** + * The rewards of the latest committed epoch of the staking pool to which this validator or delegator belongs. + * The ratio of this value and the maximally possible rewards for the latest committed epoch can be used to + * determine how well the validator of this staking pool performed in that epoch. Note that if the pool was not + * part of the committee in the latest committed epoch, this value is 0. + */ + latestCommittedEpochPoolRewards!: u64; +} diff --git a/bindings/nodejs/lib/types/models/api/utxo-changes-response.ts b/bindings/nodejs/lib/types/models/api/utxo-changes-response.ts new file mode 100644 index 0000000000..6db602b7d8 --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/utxo-changes-response.ts @@ -0,0 +1,60 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { OutputId } from '../../block/output'; +import { SlotCommitmentId } from '../../block'; +import { Output, OutputDiscriminator } from '../../block/output'; +import { Type } from 'class-transformer'; + +/** + * All UTXO changes that happened at a specific slot. + */ +export class UtxoChangesResponse { + /** + * The commitment ID of the requested slot that contains the changes. + */ + commitmentId!: SlotCommitmentId; + /** + * The created outputs of the given slot. + */ + createdOutputs!: OutputId[]; + /** + * The consumed outputs of the given slot. + */ + consumedOutputs!: OutputId[]; +} + +/** + * An output with its id. + */ +export class OutputWithId { + /** + * The output id. + */ + outputId!: OutputId; + /** + * The output. + */ + @Type(() => Output, { + discriminator: OutputDiscriminator, + }) + output!: Output; +} + +/** + * All full UTXO changes that happened at a specific slot. + */ +export class UtxoChangesFullResponse { + /** + * The commitment ID of the requested slot that contains the changes. + */ + commitmentId!: SlotCommitmentId; + /** + * The created outputs of the given slot. + */ + createdOutputs!: OutputWithId[]; + /** + * The consumed outputs of the given slot. + */ + consumedOutputs!: OutputWithId[]; +} diff --git a/bindings/nodejs/lib/types/models/api/validators-response.ts b/bindings/nodejs/lib/types/models/api/validators-response.ts new file mode 100644 index 0000000000..ccb518fc9d --- /dev/null +++ b/bindings/nodejs/lib/types/models/api/validators-response.ts @@ -0,0 +1,64 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import { Bech32Address, EpochIndex } from '../../block'; +import { u64 } from '../../utils'; +import type { HexEncodedString } from '../../utils/hex-encoding'; + +/** + * Information of a validator. + */ +export interface ValidatorResponse { + /** + * Account address of the validator. + */ + address: Bech32Address; + /** + * The epoch index until which the validator registered to stake. + */ + stakingEndEpoch: EpochIndex; + /** + * The total stake of the pool, including delegators. + */ + poolStake: u64; + /** + * The stake of a validator. + */ + validatorStake: u64; + /** + * The fixed cost of the validator, which it receives as part of its Mana rewards. + */ + fixedCost: u64; + /** + * Shows whether the validator was active recently. + */ + active: boolean; + /** + * The latest protocol version the validator supported. + */ + latestSupportedProtocolVersion: number; + /** + * The latest protocol version the validator supported. + */ + latestSupportedProtocolHash: HexEncodedString; +} + +/** + * A paginated list of all registered validators ready for the next epoch and indicates if they were active recently + * (are eligible for committee selection). + */ +export interface ValidatorsResponse { + /** + * List of registered validators ready for the next epoch. + */ + validators: ValidatorResponse[]; + /** + * The number of validators returned per one API request with pagination. + */ + pageSize: number; + /** + * The cursor that needs to be provided as cursor query parameter to request the next page. If empty, this was the + * last page. + */ + cursor?: string; +} diff --git a/bindings/nodejs/lib/types/models/block-metadata.ts b/bindings/nodejs/lib/types/models/block-metadata.ts index 70043f2a60..ba7cee6f57 100644 --- a/bindings/nodejs/lib/types/models/block-metadata.ts +++ b/bindings/nodejs/lib/types/models/block-metadata.ts @@ -1,11 +1,12 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -import type { TransactionFailureReason } from './transaction-failure-reason'; import type { HexEncodedString } from '../utils/hex-encoding'; import { BlockState, TransactionState } from './state'; import { BlockFailureReason } from './block-failure-reason'; import { Block } from '../block'; +import { TransactionId } from '../wallet'; +import { TransactionFailureReason } from './transaction-failure-reason'; /** * Response from the metadata endpoint. @@ -19,18 +20,14 @@ export interface IBlockMetadata { * The block state. */ blockState: BlockState; - /** - * The transaction state. - */ - transactionState?: TransactionState; /** * The block failure reason. */ blockFailureReason?: BlockFailureReason; /** - * The transaction failure reason. + * The metadata of the transaction in the block. */ - transactionFailureReason?: TransactionFailureReason; + transactionMetadata?: TransactionMetadata; } /** @@ -46,3 +43,21 @@ export interface IBlockWithMetadata { */ metadata: IBlockMetadata; } + +/** + * Metadata of a transaction. + */ +export interface TransactionMetadata { + /** + * The transaction id. + */ + transactionId: TransactionId; + /** + * The transaction state. + */ + transactionState: TransactionState; + /** + * The transaction failure reason. + */ + transactionFailureReason?: TransactionFailureReason; +} diff --git a/bindings/nodejs/lib/types/models/gossip-heartbeat.ts b/bindings/nodejs/lib/types/models/gossip-heartbeat.ts deleted file mode 100644 index 8ed9204451..0000000000 --- a/bindings/nodejs/lib/types/models/gossip-heartbeat.ts +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -/** - * Gossip heartbeat. - */ -export interface IGossipHeartbeat { - /** - * Solid milestone index. - */ - solidMilestoneIndex: number; - /** - * Pruned milestone index. - */ - prunedMilestoneIndex: number; - /** - * Latest milestone index. - */ - latestMilestoneIndex: number; - /** - * Connected peers. - */ - connectedPeers: number; - /** - * Synced peers. - */ - syncedPeers: number; -} diff --git a/bindings/nodejs/lib/types/models/gossip-metrics.ts b/bindings/nodejs/lib/types/models/gossip-metrics.ts deleted file mode 100644 index 9ce4f6119b..0000000000 --- a/bindings/nodejs/lib/types/models/gossip-metrics.ts +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -/** - * Gossip metrics. - */ -export interface IGossipMetrics { - /** - * The number of new blocks. - */ - newBlocks: number; - /** - * The number of known blocks. - */ - knownBlocks: number; - /** - * The number of received blocks. - */ - receivedBlocks: number; - /** - * The number of received block requests. - */ - receivedBlockRequests: number; - /** - * The number of received milestone requests. - */ - receivedMilestoneRequests: number; - /** - * The number of received heartbeats. - */ - receivedHeartbeats: number; - /** - * The number of sent blocks. - */ - sentBlocks: number; - /** - * The number of sent block requests. - */ - sentBlockRequests: number; - /** - * The number of sent miletsone requests. - */ - sentMilestoneRequests: number; - /** - * The number of sent heartbeats. - */ - sentHeartbeats: number; - /** - * The number of dropped sent packets. - */ - droppedPackets: number; -} diff --git a/bindings/nodejs/lib/types/models/index.ts b/bindings/nodejs/lib/types/models/index.ts index 8789189173..a4290a39a3 100644 --- a/bindings/nodejs/lib/types/models/index.ts +++ b/bindings/nodejs/lib/types/models/index.ts @@ -6,9 +6,6 @@ export * from './info'; export * from './transaction-failure-reason'; export * from './block-metadata'; -export * from './gossip-metrics'; -export * from './gossip-heartbeat'; export * from './native-token'; -export * from './peer'; export * from './storage-score'; export * from './state'; diff --git a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts b/bindings/nodejs/lib/types/models/info/node-info-protocol.ts index 84f70cd5e4..e7a31d3251 100644 --- a/bindings/nodejs/lib/types/models/info/node-info-protocol.ts +++ b/bindings/nodejs/lib/types/models/info/node-info-protocol.ts @@ -119,6 +119,11 @@ export interface ProtocolParameters { * Target Committee Size defines the target size of the committee. If there's fewer candidates the actual committee size could be smaller in a given epoch. */ targetCommitteeSize: number; + /** + * Defines the number of heavier slots that a chain needs to be ahead of the current chain to be considered for + * switching. + */ + chainSwitchingThreshold: number; } /** diff --git a/bindings/nodejs/lib/types/models/peer.ts b/bindings/nodejs/lib/types/models/peer.ts deleted file mode 100644 index aa7094bce1..0000000000 --- a/bindings/nodejs/lib/types/models/peer.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -import type { IGossipHeartbeat } from './gossip-heartbeat'; -import type { IGossipMetrics } from './gossip-metrics'; -/** - * Peer details. - */ -export interface IPeer { - /** - * The id of the peer. - */ - id: string; - /** - * The addresses of the peer. - */ - multiAddresses: string[]; - /** - * The alias of the peer. - */ - alias?: string; - /** - * The relation of the peer. - */ - relation: string; - /** - * Is it connected. - */ - connected: boolean; - /** - * Gossip metrics for the peer. - */ - gossip?: { - /** - * The peer heartbeat. - */ - heartbeat: IGossipHeartbeat; - /** - * The peer metrics. - */ - metrics: IGossipMetrics; - }; -} diff --git a/bindings/nodejs/lib/types/models/state.ts b/bindings/nodejs/lib/types/models/state.ts index 37b6d8b70b..a996330949 100644 --- a/bindings/nodejs/lib/types/models/state.ts +++ b/bindings/nodejs/lib/types/models/state.ts @@ -13,9 +13,15 @@ export declare type BlockState = /** * The different states of a transaction. + * If 'pending', the transaction is not included yet. + * If 'accepted', the transaction is included. + * If 'confirmed', the transaction is included and its included block is confirmed. + * If 'finalized', the transaction is included, its included block is finalized and cannot be reverted anymore. + * If 'failed', the transaction is not successfully issued due to failure reason. */ export declare type TransactionState = | 'pending' + | 'accepted' | 'confirmed' | 'finalized' | 'failed'; diff --git a/bindings/nodejs/lib/types/wallet/event.ts b/bindings/nodejs/lib/types/wallet/event.ts index 2ade953208..fbe8d2b7fa 100644 --- a/bindings/nodejs/lib/types/wallet/event.ts +++ b/bindings/nodejs/lib/types/wallet/event.ts @@ -13,45 +13,20 @@ import { HexEncodedString } from '../utils'; */ export type TransactionId = string; -/** - * An wallet account event. - */ -class Event { - /** - * The account index for which the event was emitted. - */ - accountIndex: number; - /** - * The wallet event. - */ - event: WalletEvent; - - /** - * @param accountIndex The account index. - * @param event The wallet event. - */ - constructor(accountIndex: number, event: WalletEvent) { - this.accountIndex = accountIndex; - this.event = event; - } -} - /** * All of the wallet event types. */ enum WalletEventType { - /** Consolidation is required. */ - ConsolidationRequired = 0, /** Nano Ledger has generated an address. */ - LedgerAddressGeneration = 1, + LedgerAddressGeneration = 0, /** A new output was created. */ - NewOutput = 2, + NewOutput = 1, /** An output was spent. */ - SpentOutput = 3, + SpentOutput = 2, /** A transaction was included into the ledger. */ - TransactionInclusion = 4, + TransactionInclusion = 3, /** A progress update while submitting a transaction. */ - TransactionProgress = 5, + TransactionProgress = 4, } /** @@ -68,15 +43,6 @@ abstract class WalletEvent { } } -/** - * A 'consolidation required' wallet event. - */ -class ConsolidationRequiredWalletEvent extends WalletEvent { - constructor() { - super(WalletEventType.ConsolidationRequired); - } -} - /** * A 'ledger address generation' wallet event. */ @@ -280,10 +246,8 @@ class BroadcastingProgress extends TransactionProgress { } export { - Event, WalletEventType, WalletEvent, - ConsolidationRequiredWalletEvent, LedgerAddressGenerationWalletEvent, NewOutputWalletEvent, SpentOutputWalletEvent, diff --git a/bindings/nodejs/lib/wallet/wallet-method-handler.ts b/bindings/nodejs/lib/wallet/wallet-method-handler.ts index b91eda5834..7bb9783da5 100644 --- a/bindings/nodejs/lib/wallet/wallet-method-handler.ts +++ b/bindings/nodejs/lib/wallet/wallet-method-handler.ts @@ -11,9 +11,9 @@ import { } from '../bindings'; import { WalletEventType, + WalletEvent, WalletOptions, __WalletMethod__, - Event, } from '../types/wallet'; import { Client, ClientMethodHandler } from '../client'; import { SecretManager, SecretManagerMethodHandler } from '../secret_manager'; @@ -74,17 +74,17 @@ export class WalletMethodHandler { */ async listen( eventTypes: WalletEventType[], - callback: (error: Error, event: Event) => void, + callback: (error: Error, event: WalletEvent) => void, ): Promise { return listenWallet( this.methodHandler, eventTypes, function (err: any, data: string) { - const parsed = JSON.parse(data); + const parsed: WalletEvent = JSON.parse(data); callback( // Send back raw error instead of parsing err, - new Event(parsed.accountIndex, parsed.event), + parsed, ); }, ).catch((error: any) => { diff --git a/bindings/nodejs/lib/wallet/wallet.ts b/bindings/nodejs/lib/wallet/wallet.ts index a8b1a53a2a..380b55a7b9 100644 --- a/bindings/nodejs/lib/wallet/wallet.ts +++ b/bindings/nodejs/lib/wallet/wallet.ts @@ -52,7 +52,6 @@ import type { WalletOptions, WalletEventType, WalletEvent, - Event, } from '../types/wallet'; import { IAuth, IClientOptions, LedgerNanoStatus } from '../types/client'; import { SecretManager } from '../secret_manager'; @@ -169,7 +168,7 @@ export class Wallet { */ async listen( eventTypes: WalletEventType[], - callback: (error: Error, event: Event) => void, + callback: (error: Error, event: WalletEvent) => void, ): Promise { return this.methodHandler.listen(eventTypes, callback); } diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 6bc039b6fc..ae90b2131d 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -1,7 +1,10 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, +}; use iota_sdk_bindings_core::{ call_wallet_method as rust_call_wallet_method, @@ -16,26 +19,58 @@ use crate::{ build_js_error, client::ClientMethodHandler, destroyed_err, secret_manager::SecretManagerMethodHandler, NodejsError, }; -pub type WalletMethodHandler = Arc>>; +pub struct WalletMethodHandlerInner(Option); + +impl Drop for WalletMethodHandlerInner { + fn drop(&mut self) { + log::debug!("drop WalletMethodHandlerInner"); + // Request to stop the background syncing silently if this wallet hasn't been destroyed yet + if let Some(wallet) = self.0.take() { + wallet.request_stop_background_syncing(); + } + } +} + +impl Deref for WalletMethodHandlerInner { + type Target = Option; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for WalletMethodHandlerInner { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +pub type WalletMethodHandler = Arc>; #[napi(js_name = "createWallet")] pub async fn create_wallet(options: String) -> Result> { let wallet_options = serde_json::from_str::(&options).map_err(NodejsError::new)?; let wallet = wallet_options.build().await.map_err(NodejsError::new)?; - Ok(External::new(Arc::new(RwLock::new(Some(wallet))))) + Ok(External::new(Arc::new(RwLock::new(WalletMethodHandlerInner(Some( + wallet, + )))))) } #[napi(js_name = "destroyWallet")] pub async fn destroy_wallet(wallet: External) { - *wallet.as_ref().write().await = None; + let mut wallet = wallet.as_ref().write().await; + if let Some(wallet) = &**wallet { + wallet.request_stop_background_syncing(); + } + **wallet = None; } #[napi(js_name = "callWalletMethod")] pub async fn call_wallet_method(wallet: External, method: String) -> Result { let method = serde_json::from_str::(&method).map_err(NodejsError::new)?; - match &*wallet.as_ref().read().await { + match &**wallet.as_ref().read().await { Some(wallet) => { let response = rust_call_wallet_method(wallet, method).await; match response { @@ -58,7 +93,7 @@ pub async fn listen_wallet( validated_event_types.push(WalletEventType::try_from(event_type).map_err(NodejsError::new)?); } - match &*wallet.as_ref().read().await { + match &**wallet.as_ref().read().await { Some(wallet) => { wallet .listen(validated_event_types, move |event_data| { @@ -78,7 +113,7 @@ pub async fn listen_wallet( #[napi(js_name = "getClient")] pub async fn get_client(wallet: External) -> Result> { - if let Some(wallet) = &*wallet.as_ref().read().await { + if let Some(wallet) = &**wallet.as_ref().read().await { Ok(External::new(Arc::new(RwLock::new(Some(wallet.client().clone()))))) } else { Err(destroyed_err("Wallet")) @@ -87,7 +122,7 @@ pub async fn get_client(wallet: External) -> Result) -> Result> { - if let Some(wallet) = &*wallet.as_ref().read().await { + if let Some(wallet) = &**wallet.as_ref().read().await { Ok(External::new(wallet.get_secret_manager().clone())) } else { Err(destroyed_err("Wallet")) diff --git a/bindings/nodejs/tests/client/infoMethods.spec.ts b/bindings/nodejs/tests/client/infoMethods.spec.ts index afeeb165de..5ec63bba74 100644 --- a/bindings/nodejs/tests/client/infoMethods.spec.ts +++ b/bindings/nodejs/tests/client/infoMethods.spec.ts @@ -59,13 +59,6 @@ describe.skip('Client info methods', () => { expect(tips.length).toBeGreaterThan(0); }); - it('gets peers', async () => { - const client = await makeClient(); - await expect(client.getPeers()).rejects.toMatch( - 'missing or malformed jwt', - ); - }); - it('gets networkInfo', async () => { const client = await makeClient(); const networkInfo = await client.getNetworkInfo(); diff --git a/bindings/python/examples/exchange/4_listen_events.py b/bindings/python/examples/exchange/4_listen_events.py index ca89373e93..7de0246462 100644 --- a/bindings/python/examples/exchange/4_listen_events.py +++ b/bindings/python/examples/exchange/4_listen_events.py @@ -27,9 +27,8 @@ def callback(event): """Callback function for the event listener""" - event_dict = json.loads(event) - print('AccountIndex:', event_dict["accountIndex"]) - print('Event:', event_dict["event"]) + event = json.loads(event) + print('Event:', event) # Exit after receiving an event. # pylint: disable=global-statement diff --git a/bindings/python/iota_sdk/types/block/metadata.py b/bindings/python/iota_sdk/types/block/metadata.py index 19a45386e2..06d12ebb6f 100644 --- a/bindings/python/iota_sdk/types/block/metadata.py +++ b/bindings/python/iota_sdk/types/block/metadata.py @@ -49,15 +49,17 @@ class TransactionState(Enum): """Describes the state of a transaction. Attributes: - Pending: Stored but not confirmed. - Confirmed: Confirmed with the first level of knowledge. - Finalized: Included and can no longer be reverted. - Failed: The block is not successfully issued due to failure reason. + Pending: Not included yet. + Accepted: Included. + Confirmed: Included and its included block is confirmed. + Finalized: Included, its included block is finalized and cannot be reverted anymore. + Failed: Not successfully issued due to failure reason. """ Pending = 0 - Confirmed = 1 - Finalized = 2 - Failed = 3 + Accepted = 1 + Confirmed = 2 + Finalized = 3 + Failed = 4 class BlockFailureReason(IntEnum): diff --git a/bindings/python/iota_sdk/types/event.py b/bindings/python/iota_sdk/types/event.py index 9e378dea09..75ed34886a 100644 --- a/bindings/python/iota_sdk/types/event.py +++ b/bindings/python/iota_sdk/types/event.py @@ -8,16 +8,14 @@ class WalletEventType(IntEnum): """Types of wallet events. Attributes: - ConsolidationRequired (0): Consolidation is required. - LedgerAddressGeneration (1): Nano Ledger has generated an address. - NewOutput (2): A new output was created. - SpentOutput (3): An output was spent. - TransactionInclusion (4): A transaction was included into the ledger. - TransactionProgress (5): A progress update while submitting a transaction. + LedgerAddressGeneration (0): Nano Ledger has generated an address. + NewOutput (1): A new output was created. + SpentOutput (2): An output was spent. + TransactionInclusion (3): A transaction was included into the ledger. + TransactionProgress (4): A progress update while submitting a transaction. """ - ConsolidationRequired = 0 - LedgerAddressGeneration = 1 - NewOutput = 2 - SpentOutput = 3 - TransactionInclusion = 4 - TransactionProgress = 5 + LedgerAddressGeneration = 0 + NewOutput = 1 + SpentOutput = 2 + TransactionInclusion = 3 + TransactionProgress = 4 diff --git a/cli/src/wallet_cli/completer.rs b/cli/src/wallet_cli/completer.rs index af3bc3a843..b3f316db4f 100644 --- a/cli/src/wallet_cli/completer.rs +++ b/cli/src/wallet_cli/completer.rs @@ -17,6 +17,7 @@ const WALLET_COMMANDS: &[&str] = &[ "claim", "claimable-outputs", "clear", + "congestion", "consolidate", "create-alias-output", "create-native-token", diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index dedccc8d8c..96d6386e7c 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -13,7 +13,7 @@ use iota_sdk::{ types::{ api::plugins::participation::types::ParticipationEventId, block::{ - address::Bech32Address, + address::{Bech32Address, ToBech32Ext}, output::{ unlock_condition::AddressUnlockCondition, AccountId, BasicOutputBuilder, FoundryId, NativeToken, NativeTokensBuilder, NftId, Output, OutputId, TokenId, @@ -24,8 +24,8 @@ use iota_sdk::{ }, utils::ConvertTo, wallet::{ - types::OutputData, ConsolidationParams, CreateNativeTokenParams, MintNftParams, OutputsToClaim, - SendNativeTokenParams, SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, + types::OutputData, ConsolidationParams, CreateNativeTokenParams, Error as WalletError, MintNftParams, + OutputsToClaim, SendNativeTokenParams, SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, }, U256, }; @@ -81,6 +81,8 @@ pub enum WalletCommand { }, /// Print details about claimable outputs - if there are any. ClaimableOutputs, + /// Checks if an account is ready to issue a block. + Congestion { account_id: Option }, /// Consolidate all basic outputs into one address. Consolidate, /// Create a new account output. @@ -314,7 +316,25 @@ impl FromStr for OutputSelector { // `accounts` command pub async fn accounts_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.data().await.accounts().cloned().collect(), "Accounts:") + let wallet_data = wallet.data().await; + let accounts = wallet_data.accounts(); + + println_log_info!("Accounts:\n"); + + for account in accounts { + let output_id = account.output_id; + let account_id = account.output.as_account().account_id_non_null(&output_id); + let account_address = account_id.to_bech32(wallet.client().get_bech32_hrp().await?); + + println_log_info!( + "{:<16} {output_id}\n{:<16} {account_id}\n{:<16} {account_address}\n", + "Output ID:", + "Account ID:", + "Account Address:" + ); + } + + Ok(()) } // `address` command @@ -460,6 +480,33 @@ pub async fn claimable_outputs_command(wallet: &Wallet) -> Result<(), Error> { Ok(()) } +// `congestion` command +pub async fn congestion_command(wallet: &Wallet, account_id: Option) -> Result<(), Error> { + let account_id = { + let wallet_data = wallet.data().await; + account_id + .or_else(|| { + wallet_data + .accounts() + .next() + .map(|o| o.output.as_account().account_id_non_null(&o.output_id)) + }) + .or_else(|| { + wallet_data + .implicit_accounts() + .next() + .map(|o| AccountId::from(&o.output_id)) + }) + .ok_or(WalletError::NoAccountToIssueBlock)? + }; + + let congestion = wallet.client().get_account_congestion(&account_id).await?; + + println_log_info!("{congestion:#?}"); + + Ok(()) +} + // `consolidate` command pub async fn consolidate_command(wallet: &Wallet) -> Result<(), Error> { println_log_info!("Consolidating outputs."); @@ -612,10 +659,25 @@ pub async fn implicit_account_transition_command( // `implicit-accounts` command pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs( - wallet.data().await.implicit_accounts().cloned().collect(), - "Implicit accounts:", - ) + let wallet_data = wallet.data().await; + let implicit_accounts = wallet_data.implicit_accounts(); + + println_log_info!("Implicit accounts:\n"); + + for implicit_account in implicit_accounts { + let output_id = implicit_account.output_id; + let account_id = AccountId::from(&output_id); + let account_address = account_id.to_bech32(wallet.client().get_bech32_hrp().await?); + + println_log_info!( + "{:<16} {output_id}\n{:<16} {account_id}\n{:<16} {account_address}\n", + "Output ID:", + "Account ID:", + "Account Address:" + ); + } + + Ok(()) } // `melt-native-token` command @@ -1118,6 +1180,7 @@ pub async fn prompt_internal( WalletCommand::BurnNft { nft_id } => burn_nft_command(wallet, nft_id).await, WalletCommand::Claim { output_id } => claim_command(wallet, output_id).await, WalletCommand::ClaimableOutputs => claimable_outputs_command(wallet).await, + WalletCommand::Congestion { account_id } => congestion_command(wallet, account_id).await, WalletCommand::Consolidate => consolidate_command(wallet).await, WalletCommand::CreateAccountOutput => create_account_output_command(wallet).await, WalletCommand::CreateNativeToken { diff --git a/sdk/src/client/error.rs b/sdk/src/client/error.rs index 5c9bdcbdff..8094d81ea2 100644 --- a/sdk/src/client/error.rs +++ b/sdk/src/client/error.rs @@ -36,9 +36,6 @@ pub enum Error { /// Block types error #[error("{0}")] Block(#[from] crate::types::block::Error), - /// The wallet has enough funds, but split on too many outputs - #[error("the wallet has enough funds, but split on too many outputs: {0}, max. is 128, consolidate them")] - ConsolidationRequired(usize), /// Crypto.rs error #[error("{0}")] Crypto(#[from] crypto::Error), diff --git a/sdk/src/types/api/core.rs b/sdk/src/types/api/core.rs index 83f6391c68..bfe36ef4f1 100644 --- a/sdk/src/types/api/core.rs +++ b/sdk/src/types/api/core.rs @@ -25,7 +25,7 @@ use crate::{ }; /// Response of GET /api/core/v3/info. -/// Returns general information about the node. +/// General information about the node. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct InfoResponse { @@ -206,8 +206,8 @@ pub struct ValidatorResponse { #[serde(rename_all = "camelCase")] pub struct ValidatorsResponse { /// List of registered validators ready for the next epoch. - validators: Vec, - /// The number of validators returned per one API request with pagination. + stakers: Vec, + /// The number of validators returned per one API request with pagination. page_size: u32, /// The cursor that needs to be provided as cursor query parameter to request the next page. If empty, this was the /// last page. @@ -216,7 +216,7 @@ pub struct ValidatorsResponse { } /// Response of GET /api/core/v3/rewards/{outputId}. -/// Returns the mana rewards of an account or delegation output. +/// The mana rewards of an account or delegation output. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ManaRewardsResponse { @@ -254,7 +254,7 @@ pub struct CommitteeResponse { pub epoch: EpochIndex, } -/// Returns information of a committee member. +/// Information of a committee member. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CommitteeMember { @@ -325,7 +325,7 @@ pub struct CongestionResponse { pub reference_mana_cost: u64, /// The Block Issuance Credits of the requested account. #[serde(with = "string")] - pub block_issuance_credits: u64, + pub block_issuance_credits: i128, } /// Response of POST /api/core/v3/blocks. @@ -356,11 +356,13 @@ pub enum BlockState { #[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub enum TransactionState { - // Stored but not confirmed. + // Not included yet. Pending, - // Confirmed with the first level of knowledge. + // Included. + Accepted, + // Included and its included block is confirmed. Confirmed, - // Included and can no longer be reverted. + // Included, its included block is finalized and cannot be reverted anymore. Finalized, // The block is not successfully issued due to failure reason. Failed, @@ -411,7 +413,7 @@ pub struct TransactionMetadataResponse { } /// Response of GET /api/core/v3/blocks/{blockId}/metadata. -/// Returns the metadata of a block. +/// The metadata of a block. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BlockMetadataResponse { @@ -424,7 +426,7 @@ pub struct BlockMetadataResponse { } /// Response of GET /api/core/v3/blocks/{blockId}/full. -/// Returns a block and its metadata. +/// A block and its metadata. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct BlockWithMetadataResponse { pub block: BlockDto, @@ -432,7 +434,7 @@ pub struct BlockWithMetadataResponse { } /// Response of GET /api/core/v3/outputs/{output_id}. -/// Returns an output and its metadata. +/// An output and its metadata. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct OutputWithMetadataResponse { @@ -456,7 +458,7 @@ impl From for OutputWithMetadataResponse { } /// Response of GET /api/routes. -/// Returns the available API route groups of the node. +/// The available API route groups of the node. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct RoutesResponse { @@ -466,7 +468,7 @@ pub struct RoutesResponse { /// Response of /// - GET /api/core/v3/commitments/{commitmentId}/utxo-changes /// - GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes -/// Returns all UTXO changes that happened at a specific slot. +/// All UTXO changes that happened at a specific slot. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UtxoChangesResponse { @@ -478,7 +480,7 @@ pub struct UtxoChangesResponse { /// Response of /// - GET /api/core/v3/commitments/{commitmentId}/utxo-changes/full /// - GET /api/core/v3/commitments/by-slot/{slot}/utxo-changes/full -/// Returns all full UTXO changes that happened at a specific slot. +/// All full UTXO changes that happened at a specific slot. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UtxoChangesFullResponse { diff --git a/sdk/src/types/block/address/multi.rs b/sdk/src/types/block/address/multi.rs index 377a11cce2..3db33a7d79 100644 --- a/sdk/src/types/block/address/multi.rs +++ b/sdk/src/types/block/address/multi.rs @@ -1,7 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use alloc::{boxed::Box, vec::Vec}; +use alloc::{boxed::Box, collections::BTreeMap, vec::Vec}; use core::{fmt, ops::RangeInclusive}; use crypto::hashes::{blake2b::Blake2b256, Digest}; @@ -11,10 +11,7 @@ use packable::{bounded::BoundedU8, prefix::BoxedSlicePrefix, Packable, PackableE use crate::types::block::{address::Address, output::StorageScore, Error}; -pub(crate) type WeightedAddressCount = - BoundedU8<{ *MultiAddress::ADDRESSES_COUNT.start() }, { *MultiAddress::ADDRESSES_COUNT.end() }>; - -/// An address with an assigned weight. +/// An [`Address`] with an assigned weight. #[derive(Clone, Debug, Display, Eq, PartialEq, Ord, PartialOrd, Hash, From, AsRef, Deref, Packable)] #[display(fmt = "{address}")] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -30,7 +27,9 @@ pub struct WeightedAddress { impl WeightedAddress { /// Creates a new [`WeightedAddress`]. - pub fn new(address: Address, weight: u8) -> Result { + pub fn new(address: impl Into
, weight: u8) -> Result { + let address = address.into(); + verify_address::(&address)?; verify_weight::(&weight)?; @@ -69,9 +68,11 @@ fn verify_weight(weight: &u8) -> Result<(), Error> { } } -/// An address that consists of addresses with weights and a threshold value. -/// The Multi Address can be unlocked if the cumulative weight of all unlocked addresses is equal to or exceeds the -/// threshold. +pub(crate) type WeightedAddressCount = + BoundedU8<{ *MultiAddress::ADDRESSES_COUNT.start() }, { *MultiAddress::ADDRESSES_COUNT.end() }>; + +/// An [`Address`] that consists of addresses with weights and a threshold value. +/// It can be unlocked if the cumulative weight of all unlocked addresses is equal to or exceeds the threshold. #[derive(Clone, Debug, Deref, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] #[packable(unpack_error = Error)] #[packable(verify_with = verify_multi_address)] @@ -95,10 +96,15 @@ impl MultiAddress { /// Creates a new [`MultiAddress`]. #[inline(always)] pub fn new(addresses: impl IntoIterator, threshold: u16) -> Result { - let mut addresses = addresses.into_iter().collect::>(); - - addresses.sort_by(|a, b| a.address().cmp(b.address())); - + // Using an intermediate BTreeMap to sort the addresses without having to repeatedly packing them. + let addresses = addresses + .into_iter() + .map(|address| (address.address().pack_to_vec(), address)) + .collect::>() + .into_values() + .collect::>(); + + verify_addresses::(&addresses)?; verify_threshold::(&threshold)?; let addresses = BoxedSlicePrefix::::try_from(addresses) @@ -128,7 +134,7 @@ impl MultiAddress { pub fn hash(&self) -> [u8; 32] { let mut digest = Blake2b256::new(); - digest.update([MultiAddress::KIND]); + digest.update([Self::KIND]); digest.update(self.pack_to_vec()); digest.finalize().into() @@ -136,7 +142,7 @@ impl MultiAddress { } fn verify_addresses(addresses: &[WeightedAddress]) -> Result<(), Error> { - if VERIFY && !is_unique_sorted(addresses.iter().map(WeightedAddress::address)) { + if VERIFY && !is_unique_sorted(addresses.iter().map(|a| a.address.pack_to_vec())) { Err(Error::WeightedAddressesNotUniqueSorted) } else { Ok(()) @@ -162,6 +168,7 @@ fn verify_multi_address(address: &MultiAddress) -> Result<() }); } } + Ok(()) } diff --git a/sdk/src/types/block/address/restricted.rs b/sdk/src/types/block/address/restricted.rs index 9ac601da5c..d52c316371 100644 --- a/sdk/src/types/block/address/restricted.rs +++ b/sdk/src/types/block/address/restricted.rs @@ -1,16 +1,22 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! An extension to the address format to make them configurable. +//! This enables an address to opt-in or -out of certain functionality, like disabling the receipt of Native Tokens, NFT +//! Outputs or Timelock Unlock Conditions. +//! [TIP-50: Configurable Addresses](https://github.com/iotaledger/tips/blob/tip50/tips/TIP-0050/tip-0050.md). + use getset::Getters; use packable::{Packable, PackableExt}; -use super::Address; use crate::types::block::{ + address::Address, capabilities::{Capabilities, CapabilityFlag}, output::{StorageScore, StorageScoreParameters}, Error, }; +/// An [`Address`] that contains another address and allows for configuring its capabilities. #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Getters, Packable)] #[getset(get = "pub")] pub struct RestrictedAddress { diff --git a/sdk/src/types/block/error.rs b/sdk/src/types/block/error.rs index b44bd1b619..672be985dc 100644 --- a/sdk/src/types/block/error.rs +++ b/sdk/src/types/block/error.rs @@ -11,7 +11,7 @@ use primitive_types::U256; use super::slot::EpochIndex; use crate::types::block::{ - address::WeightedAddressCount, + address::{AddressCapabilityFlag, WeightedAddressCount}, context_input::RewardContextInputIndex, input::UtxoInput, mana::ManaAllotmentCount, @@ -201,6 +201,7 @@ pub enum Error { target: EpochIndex, }, TrailingCapabilityBytes, + RestrictedAddressCapability(AddressCapabilityFlag), } #[cfg(feature = "std")] @@ -432,6 +433,7 @@ impl fmt::Display for Error { write!(f, "invalid epoch diff: created {created}, target {target}") } Self::TrailingCapabilityBytes => write!(f, "capability bytes have trailing zeroes"), + Self::RestrictedAddressCapability(cap) => write!(f, "restricted address capability: {cap:?}"), } } } diff --git a/sdk/src/types/block/output/account.rs b/sdk/src/types/block/output/account.rs index 942693651c..e2792c4362 100644 --- a/sdk/src/types/block/output/account.rs +++ b/sdk/src/types/block/output/account.rs @@ -15,7 +15,10 @@ use crate::types::block::{ address::{AccountAddress, Address}, output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + unlock_condition::{ + verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, + UnlockConditions, + }, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, @@ -217,6 +220,12 @@ impl AccountOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses( + &unlock_conditions, + AccountOutput::KIND, + features.native_token(), + self.mana, + )?; verify_allowed_features(&features, AccountOutput::ALLOWED_FEATURES)?; let immutable_features = Features::from_set(self.immutable_features)?; @@ -493,6 +502,8 @@ impl Packable for AccountOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { + verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) + .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } @@ -602,47 +613,6 @@ mod dto { } } - impl AccountOutput { - #[allow(clippy::too_many_arguments)] - pub fn try_from_dtos( - amount: OutputBuilderAmount, - mana: u64, - account_id: &AccountId, - foundry_counter: Option, - unlock_conditions: Vec, - features: Option>, - immutable_features: Option>, - ) -> Result { - let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => AccountOutputBuilder::new_with_amount(amount, *account_id), - OutputBuilderAmount::MinimumAmount(params) => { - AccountOutputBuilder::new_with_minimum_amount(params, *account_id) - } - } - .with_mana(mana); - - if let Some(foundry_counter) = foundry_counter { - builder = builder.with_foundry_counter(foundry_counter); - } - - let unlock_conditions = unlock_conditions - .into_iter() - .map(UnlockCondition::from) - .collect::>(); - builder = builder.with_unlock_conditions(unlock_conditions); - - if let Some(features) = features { - builder = builder.with_features(features); - } - - if let Some(immutable_features) = immutable_features { - builder = builder.with_immutable_features(immutable_features); - } - - builder.finish() - } - } - crate::impl_serde_typed_dto!(AccountOutput, AccountOutputDto, "account output"); } @@ -652,12 +622,7 @@ mod tests { use super::*; use crate::types::block::{ - output::account::dto::AccountOutputDto, - protocol::protocol_parameters, - rand::output::{ - feature::rand_allowed_features, rand_account_id, rand_account_output, - unlock_condition::rand_address_unlock_condition_different_from_account_id, - }, + output::account::dto::AccountOutputDto, protocol::protocol_parameters, rand::output::rand_account_output, }; #[test] @@ -667,47 +632,5 @@ mod tests { let dto = AccountOutputDto::from(&account_output); let output = Output::Account(AccountOutput::try_from(dto).unwrap()); assert_eq!(&account_output, output.as_account()); - - let output_split = AccountOutput::try_from_dtos( - OutputBuilderAmount::Amount(account_output.amount()), - account_output.mana(), - account_output.account_id(), - account_output.foundry_counter().into(), - account_output.unlock_conditions().to_vec(), - Some(account_output.features().to_vec()), - Some(account_output.immutable_features().to_vec()), - ) - .unwrap(); - assert_eq!(account_output, output_split); - - let account_id = rand_account_id(); - let address = rand_address_unlock_condition_different_from_account_id(&account_id); - - let test_split_dto = |builder: AccountOutputBuilder| { - let output_split = AccountOutput::try_from_dtos( - builder.amount, - builder.mana, - &builder.account_id, - builder.foundry_counter, - builder.unlock_conditions.iter().cloned().collect(), - Some(builder.features.iter().cloned().collect()), - Some(builder.immutable_features.iter().cloned().collect()), - ) - .unwrap(); - assert_eq!(builder.finish().unwrap(), output_split); - }; - - let builder = AccountOutput::build_with_amount(100, account_id) - .add_unlock_condition(address.clone()) - .with_features(rand_allowed_features(AccountOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(AccountOutput::ALLOWED_IMMUTABLE_FEATURES)); - test_split_dto(builder); - - let builder = - AccountOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters(), account_id) - .add_unlock_condition(address) - .with_features(rand_allowed_features(AccountOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(AccountOutput::ALLOWED_IMMUTABLE_FEATURES)); - test_split_dto(builder); } } diff --git a/sdk/src/types/block/output/anchor.rs b/sdk/src/types/block/output/anchor.rs index a2d2bcef20..958b8eecda 100644 --- a/sdk/src/types/block/output/anchor.rs +++ b/sdk/src/types/block/output/anchor.rs @@ -15,7 +15,10 @@ use crate::types::block::{ address::{Address, AnchorAddress}, output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, - unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + unlock_condition::{ + verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, + UnlockConditions, + }, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, @@ -248,6 +251,12 @@ impl AnchorOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses( + &unlock_conditions, + AnchorOutput::KIND, + features.native_token(), + self.mana, + )?; verify_allowed_features(&features, AnchorOutput::ALLOWED_FEATURES)?; let immutable_features = Features::from_set(self.immutable_features)?; @@ -549,6 +558,8 @@ impl Packable for AnchorOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { + verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) + .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } @@ -668,44 +679,6 @@ mod dto { } } - impl AnchorOutput { - #[allow(clippy::too_many_arguments)] - pub fn try_from_dtos( - amount: OutputBuilderAmount, - mana: u64, - anchor_id: &AnchorId, - state_index: u32, - unlock_conditions: Vec, - features: Option>, - immutable_features: Option>, - ) -> Result { - let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => AnchorOutputBuilder::new_with_amount(amount, *anchor_id), - OutputBuilderAmount::MinimumAmount(params) => { - AnchorOutputBuilder::new_with_minimum_amount(params, *anchor_id) - } - } - .with_mana(mana) - .with_state_index(state_index); - - let unlock_conditions = unlock_conditions - .into_iter() - .map(UnlockCondition::from) - .collect::>(); - builder = builder.with_unlock_conditions(unlock_conditions); - - if let Some(features) = features { - builder = builder.with_features(features); - } - - if let Some(immutable_features) = immutable_features { - builder = builder.with_immutable_features(immutable_features); - } - - builder.finish() - } - } - crate::impl_serde_typed_dto!(AnchorOutput, AnchorOutputDto, "anchor output"); } @@ -713,16 +686,7 @@ mod dto { mod tests { use super::*; use crate::types::block::{ - output::anchor::dto::AnchorOutputDto, - protocol::protocol_parameters, - rand::output::{ - feature::rand_allowed_features, - rand_anchor_id, rand_anchor_output, - unlock_condition::{ - rand_governor_address_unlock_condition_different_from, - rand_state_controller_address_unlock_condition_different_from, - }, - }, + output::anchor::dto::AnchorOutputDto, protocol::protocol_parameters, rand::output::rand_anchor_output, }; #[test] @@ -732,50 +696,5 @@ mod tests { let dto = AnchorOutputDto::from(&anchor_output); let output = Output::Anchor(AnchorOutput::try_from(dto).unwrap()); assert_eq!(&anchor_output, output.as_anchor()); - - let output_split = AnchorOutput::try_from_dtos( - OutputBuilderAmount::Amount(output.amount()), - anchor_output.mana(), - anchor_output.anchor_id(), - anchor_output.state_index(), - anchor_output.unlock_conditions().to_vec(), - Some(anchor_output.features().to_vec()), - Some(anchor_output.immutable_features().to_vec()), - ) - .unwrap(); - assert_eq!(anchor_output, output_split); - - let anchor_id = rand_anchor_id(); - let gov_address = rand_governor_address_unlock_condition_different_from(&anchor_id); - let state_address = rand_state_controller_address_unlock_condition_different_from(&anchor_id); - - let test_split_dto = |builder: AnchorOutputBuilder| { - let output_split = AnchorOutput::try_from_dtos( - builder.amount, - builder.mana, - &builder.anchor_id, - builder.state_index, - builder.unlock_conditions.iter().cloned().collect(), - Some(builder.features.iter().cloned().collect()), - Some(builder.immutable_features.iter().cloned().collect()), - ) - .unwrap(); - assert_eq!(builder.finish().unwrap(), output_split); - }; - - let builder = AnchorOutput::build_with_amount(100, anchor_id) - .add_unlock_condition(gov_address.clone()) - .add_unlock_condition(state_address.clone()) - .with_features(rand_allowed_features(AnchorOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(AnchorOutput::ALLOWED_IMMUTABLE_FEATURES)); - test_split_dto(builder); - - let builder = - AnchorOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters(), anchor_id) - .add_unlock_condition(gov_address) - .add_unlock_condition(state_address) - .with_features(rand_allowed_features(AnchorOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(AnchorOutput::ALLOWED_IMMUTABLE_FEATURES)); - test_split_dto(builder); } } diff --git a/sdk/src/types/block/output/basic.rs b/sdk/src/types/block/output/basic.rs index 6b07bd0f21..41f81ac911 100644 --- a/sdk/src/types/block/output/basic.rs +++ b/sdk/src/types/block/output/basic.rs @@ -10,8 +10,8 @@ use crate::types::block::{ output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features, NativeTokenFeature}, unlock_condition::{ - verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, - UnlockCondition, UnlockConditionFlags, UnlockConditions, + verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition, + StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, MinimumOutputAmount, NativeToken, Output, OutputBuilderAmount, StorageScore, StorageScoreParameters, }, @@ -192,6 +192,12 @@ impl BasicOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses( + &unlock_conditions, + BasicOutput::KIND, + features.native_token(), + self.mana, + )?; verify_features::(&features)?; let mut output = BasicOutput { @@ -230,6 +236,7 @@ impl From<&BasicOutput> for BasicOutputBuilder { #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] #[packable(unpack_error = Error)] #[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_basic_output)] pub struct BasicOutput { /// Amount of IOTA coins held by the output. amount: u64, @@ -392,6 +399,15 @@ fn verify_features_packable(features: &Features, _: &Protoco verify_features::(features) } +fn verify_basic_output(output: &BasicOutput, _: &ProtocolParameters) -> Result<(), Error> { + verify_restricted_addresses( + output.unlock_conditions(), + BasicOutput::KIND, + output.features.native_token(), + output.mana, + ) +} + #[cfg(feature = "serde")] mod dto { use alloc::vec::Vec; @@ -446,33 +462,6 @@ mod dto { } } - impl BasicOutput { - pub fn try_from_dtos( - amount: OutputBuilderAmount, - mana: u64, - unlock_conditions: Vec, - features: Option>, - ) -> Result { - let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => BasicOutputBuilder::new_with_amount(amount), - OutputBuilderAmount::MinimumAmount(params) => BasicOutputBuilder::new_with_minimum_amount(params), - } - .with_mana(mana); - - let unlock_conditions = unlock_conditions - .into_iter() - .map(UnlockCondition::from) - .collect::>(); - builder = builder.with_unlock_conditions(unlock_conditions); - - if let Some(features) = features { - builder = builder.with_features(features); - } - - builder.finish() - } - } - crate::impl_serde_typed_dto!(BasicOutput, BasicOutputDto, "basic output"); } @@ -499,41 +488,6 @@ mod tests { let dto = BasicOutputDto::from(&basic_output); let output = Output::Basic(BasicOutput::try_from(dto).unwrap()); assert_eq!(&basic_output, output.as_basic()); - - let output_split = BasicOutput::try_from_dtos( - OutputBuilderAmount::Amount(basic_output.amount()), - basic_output.mana(), - basic_output.unlock_conditions().to_vec(), - Some(basic_output.features().to_vec()), - ) - .unwrap(); - assert_eq!(basic_output, output_split); - - let foundry_id = FoundryId::build(&rand_account_address(), 0, SimpleTokenScheme::KIND); - let address = rand_address_unlock_condition(); - - let test_split_dto = |builder: BasicOutputBuilder| { - let output_split = BasicOutput::try_from_dtos( - builder.amount, - builder.mana, - builder.unlock_conditions.iter().cloned().collect(), - Some(builder.features.iter().cloned().collect()), - ) - .unwrap(); - assert_eq!(builder.finish().unwrap(), output_split); - }; - - let builder = BasicOutput::build_with_amount(100) - .with_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) - .add_unlock_condition(address.clone()) - .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)); - test_split_dto(builder); - - let builder = BasicOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters()) - .with_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) - .add_unlock_condition(address) - .with_features(rand_allowed_features(BasicOutput::ALLOWED_FEATURES)); - test_split_dto(builder); } // TODO: re-enable when rent is figured out diff --git a/sdk/src/types/block/output/delegation.rs b/sdk/src/types/block/output/delegation.rs index 00c909c919..d9e880019c 100644 --- a/sdk/src/types/block/output/delegation.rs +++ b/sdk/src/types/block/output/delegation.rs @@ -9,7 +9,10 @@ use crate::types::block::{ address::{AccountAddress, Address}, output::{ chain_id::ChainId, - unlock_condition::{verify_allowed_unlock_conditions, UnlockCondition, UnlockConditionFlags, UnlockConditions}, + unlock_condition::{ + verify_allowed_unlock_conditions, verify_restricted_addresses, UnlockCondition, UnlockConditionFlags, + UnlockConditions, + }, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, }, protocol::{ProtocolParameters, WorkScore, WorkScoreParameters}, @@ -172,6 +175,7 @@ impl DelegationOutputBuilder { let unlock_conditions = UnlockConditions::from_set(self.unlock_conditions)?; verify_unlock_conditions::(&unlock_conditions)?; + verify_restricted_addresses(&unlock_conditions, DelegationOutput::KIND, None, 0)?; let mut output = DelegationOutput { amount: 0, @@ -215,6 +219,7 @@ impl From<&DelegationOutput> for DelegationOutputBuilder { #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Packable)] #[packable(unpack_error = Error)] #[packable(unpack_visitor = ProtocolParameters)] +#[packable(verify_with = verify_delegation_output)] pub struct DelegationOutput { /// Amount of IOTA coins held by the output. amount: u64, @@ -392,6 +397,13 @@ fn verify_unlock_conditions_packable( verify_unlock_conditions::(unlock_conditions) } +fn verify_delegation_output( + output: &DelegationOutput, + _: &ProtocolParameters, +) -> Result<(), Error> { + verify_restricted_addresses(output.unlock_conditions(), DelegationOutput::KIND, None, 0) +} + #[cfg(feature = "serde")] mod dto { use alloc::vec::Vec; @@ -400,10 +412,7 @@ mod dto { use super::*; use crate::{ - types::block::{ - output::{unlock_condition::UnlockCondition, OutputBuilderAmount}, - Error, - }, + types::block::{output::unlock_condition::UnlockCondition, Error}, utils::serde::string, }; @@ -459,43 +468,5 @@ mod dto { } } - impl DelegationOutput { - #[allow(clippy::too_many_arguments)] - pub fn try_from_dtos( - amount: OutputBuilderAmount, - delegated_amount: u64, - delegation_id: &DelegationId, - validator_address: &AccountAddress, - start_epoch: impl Into, - end_epoch: impl Into, - unlock_conditions: Vec, - ) -> Result { - let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => DelegationOutputBuilder::new_with_amount( - amount, - delegated_amount, - *delegation_id, - *validator_address, - ), - OutputBuilderAmount::MinimumAmount(params) => DelegationOutputBuilder::new_with_minimum_amount( - params, - delegated_amount, - *delegation_id, - *validator_address, - ), - } - .with_start_epoch(start_epoch) - .with_end_epoch(end_epoch); - - let unlock_conditions = unlock_conditions - .into_iter() - .map(UnlockCondition::from) - .collect::>(); - builder = builder.with_unlock_conditions(unlock_conditions); - - builder.finish() - } - } - crate::impl_serde_typed_dto!(DelegationOutput, DelegationOutputDto, "delegation output"); } diff --git a/sdk/src/types/block/output/foundry.rs b/sdk/src/types/block/output/foundry.rs index ccc61cdbcc..66fa6789fd 100644 --- a/sdk/src/types/block/output/foundry.rs +++ b/sdk/src/types/block/output/foundry.rs @@ -638,43 +638,6 @@ mod dto { } } - impl FoundryOutput { - #[allow(clippy::too_many_arguments)] - pub fn try_from_dtos( - amount: OutputBuilderAmount, - serial_number: u32, - token_scheme: TokenScheme, - unlock_conditions: Vec, - features: Option>, - immutable_features: Option>, - ) -> Result { - let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => { - FoundryOutputBuilder::new_with_amount(amount, serial_number, token_scheme) - } - OutputBuilderAmount::MinimumAmount(params) => { - FoundryOutputBuilder::new_with_minimum_amount(params, serial_number, token_scheme) - } - }; - - let unlock_conditions = unlock_conditions - .into_iter() - .map(UnlockCondition::from) - .collect::>(); - builder = builder.with_unlock_conditions(unlock_conditions); - - if let Some(features) = features { - builder = builder.with_features(features); - } - - if let Some(immutable_features) = immutable_features { - builder = builder.with_immutable_features(immutable_features); - } - - builder.finish() - } - } - crate::impl_serde_typed_dto!(FoundryOutput, FoundryOutputDto, "foundry output"); } @@ -684,18 +647,7 @@ mod tests { use super::*; use crate::types::block::{ - output::{ - foundry::dto::FoundryOutputDto, unlock_condition::ImmutableAccountAddressUnlockCondition, FoundryId, - SimpleTokenScheme, TokenId, - }, - protocol::protocol_parameters, - rand::{ - address::rand_account_address, - output::{ - feature::{rand_allowed_features, rand_metadata_feature}, - rand_foundry_output, rand_token_scheme, - }, - }, + output::foundry::dto::FoundryOutputDto, protocol::protocol_parameters, rand::output::rand_foundry_output, }; #[test] @@ -705,38 +657,5 @@ mod tests { let dto = FoundryOutputDto::from(&foundry_output); let output = Output::Foundry(FoundryOutput::try_from(dto).unwrap()); assert_eq!(&foundry_output, output.as_foundry()); - - let foundry_id = FoundryId::build(&rand_account_address(), 0, SimpleTokenScheme::KIND); - - let test_split_dto = |builder: FoundryOutputBuilder| { - let output_split = FoundryOutput::try_from_dtos( - builder.amount, - builder.serial_number, - builder.token_scheme.clone(), - builder.unlock_conditions.iter().cloned().collect(), - Some(builder.features.iter().cloned().collect()), - Some(builder.immutable_features.iter().cloned().collect()), - ) - .unwrap(); - assert_eq!(builder.finish().unwrap(), output_split); - }; - - let builder = FoundryOutput::build_with_amount(100, 123, rand_token_scheme()) - .with_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) - .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(rand_account_address())) - .add_immutable_feature(rand_metadata_feature()) - .with_features(rand_allowed_features(FoundryOutput::ALLOWED_FEATURES)); - test_split_dto(builder); - - let builder = FoundryOutput::build_with_minimum_amount( - protocol_parameters.storage_score_parameters(), - 123, - rand_token_scheme(), - ) - .with_native_token(NativeToken::new(TokenId::from(foundry_id), 1000).unwrap()) - .add_unlock_condition(ImmutableAccountAddressUnlockCondition::new(rand_account_address())) - .add_immutable_feature(rand_metadata_feature()) - .with_features(rand_allowed_features(FoundryOutput::ALLOWED_FEATURES)); - test_split_dto(builder); } } diff --git a/sdk/src/types/block/output/mod.rs b/sdk/src/types/block/output/mod.rs index e2ec4241a5..196986c4bc 100644 --- a/sdk/src/types/block/output/mod.rs +++ b/sdk/src/types/block/output/mod.rs @@ -310,17 +310,18 @@ impl Output { }) } - /// Verifies if a valid storage deposit was made. Each [`Output`] has to have an amount that covers its associated - /// byte cost, given by [`StorageScoreParameters`]. + /// Verifies if a valid storage deposit was made. + /// Each [`Output`] has to have an amount that covers its associated byte cost, given by [`StorageScoreParameters`]. /// If there is a [`StorageDepositReturnUnlockCondition`](unlock_condition::StorageDepositReturnUnlockCondition), /// its amount is also checked. pub fn verify_storage_deposit(&self, params: StorageScoreParameters) -> Result<(), Error> { - let required_output_amount = self.minimum_amount(params); + let minimum_storage_deposit = self.minimum_amount(params); - if self.amount() < required_output_amount { + // For any created `Output` in a transaction, it must hold that `Output::Amount >= Minimum Storage Deposit`. + if self.amount() < minimum_storage_deposit { return Err(Error::InsufficientStorageDepositAmount { amount: self.amount(), - required: required_output_amount, + required: minimum_storage_deposit, }); } @@ -337,13 +338,13 @@ impl Output { }); } - let minimum_deposit = BasicOutput::minimum_amount(return_condition.return_address(), params); + let minimum_storage_deposit = BasicOutput::minimum_amount(return_condition.return_address(), params); // `Minimum Storage Deposit` ≤ `Return Amount` - if return_condition.amount() < minimum_deposit { + if return_condition.amount() < minimum_storage_deposit { return Err(Error::InsufficientStorageDepositReturnAmount { deposit: return_condition.amount(), - required: minimum_deposit, + required: minimum_storage_deposit, }); } } diff --git a/sdk/src/types/block/output/nft.rs b/sdk/src/types/block/output/nft.rs index 0e5930b880..d641ee1f46 100644 --- a/sdk/src/types/block/output/nft.rs +++ b/sdk/src/types/block/output/nft.rs @@ -15,8 +15,8 @@ use crate::types::block::{ output::{ feature::{verify_allowed_features, Feature, FeatureFlags, Features}, unlock_condition::{ - verify_allowed_unlock_conditions, AddressUnlockCondition, StorageDepositReturnUnlockCondition, - UnlockCondition, UnlockConditionFlags, UnlockConditions, + verify_allowed_unlock_conditions, verify_restricted_addresses, AddressUnlockCondition, + StorageDepositReturnUnlockCondition, UnlockCondition, UnlockConditionFlags, UnlockConditions, }, BasicOutputBuilder, ChainId, MinimumOutputAmount, Output, OutputBuilderAmount, OutputId, StorageScore, StorageScoreParameters, @@ -256,6 +256,7 @@ impl NftOutputBuilder { let features = Features::from_set(self.features)?; + verify_restricted_addresses(&unlock_conditions, NftOutput::KIND, features.native_token(), self.mana)?; verify_allowed_features(&features, NftOutput::ALLOWED_FEATURES)?; let immutable_features = Features::from_set(self.immutable_features)?; @@ -469,6 +470,8 @@ impl Packable for NftOutput { let features = Features::unpack::<_, VERIFY>(unpacker, &())?; if VERIFY { + verify_restricted_addresses(&unlock_conditions, Self::KIND, features.native_token(), mana) + .map_err(UnpackError::Packable)?; verify_allowed_features(&features, Self::ALLOWED_FEATURES).map_err(UnpackError::Packable)?; } @@ -564,42 +567,6 @@ mod dto { } } - impl NftOutput { - #[allow(clippy::too_many_arguments)] - pub fn try_from_dtos( - amount: OutputBuilderAmount, - mana: u64, - nft_id: &NftId, - unlock_conditions: Vec, - features: Option>, - immutable_features: Option>, - ) -> Result { - let mut builder = match amount { - OutputBuilderAmount::Amount(amount) => NftOutputBuilder::new_with_amount(amount, *nft_id), - OutputBuilderAmount::MinimumAmount(params) => { - NftOutputBuilder::new_with_minimum_amount(params, *nft_id) - } - } - .with_mana(mana); - - let unlock_conditions = unlock_conditions - .into_iter() - .map(UnlockCondition::from) - .collect::>(); - builder = builder.with_unlock_conditions(unlock_conditions); - - if let Some(features) = features { - builder = builder.with_features(features); - } - - if let Some(immutable_features) = immutable_features { - builder = builder.with_immutable_features(immutable_features); - } - - builder.finish() - } - } - crate::impl_serde_typed_dto!(NftOutput, NftOutputDto, "nft output"); } @@ -609,11 +576,7 @@ mod tests { use super::*; use crate::types::block::{ - output::nft::dto::NftOutputDto, - protocol::protocol_parameters, - rand::output::{ - feature::rand_allowed_features, rand_nft_output, unlock_condition::rand_address_unlock_condition, - }, + output::nft::dto::NftOutputDto, protocol::protocol_parameters, rand::output::rand_nft_output, }; #[test] @@ -623,42 +586,5 @@ mod tests { let dto = NftOutputDto::from(&nft_output); let output = Output::Nft(NftOutput::try_from(dto).unwrap()); assert_eq!(&nft_output, output.as_nft()); - - let output_split = NftOutput::try_from_dtos( - OutputBuilderAmount::Amount(nft_output.amount()), - nft_output.mana(), - nft_output.nft_id(), - nft_output.unlock_conditions().to_vec(), - Some(nft_output.features().to_vec()), - Some(nft_output.immutable_features().to_vec()), - ) - .unwrap(); - assert_eq!(nft_output, output_split); - - let test_split_dto = |builder: NftOutputBuilder| { - let output_split = NftOutput::try_from_dtos( - builder.amount, - builder.mana, - &builder.nft_id, - builder.unlock_conditions.iter().cloned().collect(), - Some(builder.features.iter().cloned().collect()), - Some(builder.immutable_features.iter().cloned().collect()), - ) - .unwrap(); - assert_eq!(builder.finish().unwrap(), output_split); - }; - - let builder = NftOutput::build_with_amount(100, NftId::null()) - .add_unlock_condition(rand_address_unlock_condition()) - .with_features(rand_allowed_features(NftOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(NftOutput::ALLOWED_IMMUTABLE_FEATURES)); - test_split_dto(builder); - - let builder = - NftOutput::build_with_minimum_amount(protocol_parameters.storage_score_parameters(), NftId::null()) - .add_unlock_condition(rand_address_unlock_condition()) - .with_features(rand_allowed_features(NftOutput::ALLOWED_FEATURES)) - .with_immutable_features(rand_allowed_features(NftOutput::ALLOWED_IMMUTABLE_FEATURES)); - test_split_dto(builder); } } diff --git a/sdk/src/types/block/output/storage_score.rs b/sdk/src/types/block/output/storage_score.rs index 02d6b09be4..ad4bf77f36 100644 --- a/sdk/src/types/block/output/storage_score.rs +++ b/sdk/src/types/block/output/storage_score.rs @@ -1,6 +1,11 @@ // Copyright 2022 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +//! Storage deposit is a concept that creates a monetary incentive to keep the ledger state small. +//! This is achieved by enforcing a minimum IOTA coin deposit in every output based on the disk space that will actually +//! be used to store it. +//! [TIP-47: Storage Deposit Dust Protection](https://github.com/iotaledger/tips/blob/tip47/tips/TIP-0047/tip-0047.md). + use packable::Packable; use crate::types::block::{ @@ -13,16 +18,14 @@ use crate::types::block::{ BlockId, }; -const DEFAULT_STORAGE_COST: u64 = 500; +const DEFAULT_STORAGE_COST: u64 = 100; const DEFAULT_FACTOR_DATA: u8 = 1; const DEFAULT_OFFSET_OUTPUT_OVERHEAD: u64 = 10; -const DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY: u64 = 50; +const DEFAULT_OFFSET_ED25519_BLOCK_ISSUER_KEY: u64 = 100; const DEFAULT_OFFSET_STAKING_FEATURE: u64 = 100; const DEFAULT_OFFSET_DELEGATION: u64 = 100; -// Defines the parameters of storage score calculations on objects which take node resources. -// This structure defines the minimum base token deposit required on an object. This deposit does not -// generate Mana, which serves as a payment in Mana for storing the object. +// Parameters of storage score calculations on objects which take node resources. #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Packable)] #[cfg_attr( feature = "serde", @@ -30,21 +33,21 @@ const DEFAULT_OFFSET_DELEGATION: u64 = 100; serde(rename_all = "camelCase") )] pub struct StorageScoreParameters { - /// Defines the number of IOTA tokens required per unit of storage score. + /// Number of IOTA tokens required per unit of storage score. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] storage_cost: u64, - /// Defines the factor to be used for data only fields. + /// Factor to be used for data only fields. factor_data: u8, - /// Defines the offset to be applied to all outputs for the overhead of handling them in storage. + /// Offset to be applied to all outputs for the overhead of handling them in storage. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_output_overhead: u64, - /// Defines the offset to be used for block issuer feature public keys. + /// Offset to be used for Ed25519-based block issuer keys. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_ed25519_block_issuer_key: u64, - /// Defines the offset to be used for staking feature. + /// Offset to be used for staking feature. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_staking_feature: u64, - /// Defines the offset to be used for delegation output. + /// Offset to be used for delegation. #[cfg_attr(feature = "serde", serde(with = "crate::utils::serde::string"))] offset_delegation: u64, } diff --git a/sdk/src/types/block/output/unlock_condition/mod.rs b/sdk/src/types/block/output/unlock_condition/mod.rs index 6b2bc79ed5..b3506172c1 100644 --- a/sdk/src/types/block/output/unlock_condition/mod.rs +++ b/sdk/src/types/block/output/unlock_condition/mod.rs @@ -24,8 +24,11 @@ pub use self::{ storage_deposit_return::StorageDepositReturnUnlockCondition, timelock::TimelockUnlockCondition, }; use crate::types::block::{ - address::Address, - output::{StorageScore, StorageScoreParameters}, + address::{Address, AddressCapabilityFlag, RestrictedAddress}, + output::{ + feature::NativeTokenFeature, AccountOutput, AnchorOutput, DelegationOutput, NftOutput, StorageScore, + StorageScoreParameters, + }, protocol::{CommittableAgeRange, ProtocolParameters, WorkScore}, slot::SlotIndex, Error, @@ -314,6 +317,22 @@ impl UnlockConditions { Ok(address) } + + /// Returns an iterator over all addresses except StorageDepositReturn address. + pub fn addresses(&self) -> impl Iterator { + self.iter().filter_map(|uc| match uc { + UnlockCondition::Address(uc) => Some(uc.address()), + UnlockCondition::Expiration(uc) => Some(uc.return_address()), + UnlockCondition::StateControllerAddress(uc) => Some(uc.address()), + UnlockCondition::GovernorAddress(uc) => Some(uc.address()), + _ => None, + }) + } + + /// Returns an iterator over all restricted addresses. + pub fn restricted_addresses(&self) -> impl Iterator { + self.addresses().filter_map(Address::as_restricted_opt) + } } impl StorageScore for UnlockConditions { @@ -355,6 +374,73 @@ pub(crate) fn verify_allowed_unlock_conditions( Ok(()) } +pub(crate) fn verify_restricted_addresses( + unlock_conditions: &UnlockConditions, + output_kind: u8, + native_token: Option<&NativeTokenFeature>, + mana: u64, +) -> Result<(), Error> { + let addresses = unlock_conditions.restricted_addresses(); + + for address in addresses { + if native_token.is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithNativeTokens, + )); + } + + if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithMana, + )); + } + + if unlock_conditions.timelock().is_some() && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) + { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithTimelock, + )); + } + + if unlock_conditions.expiration().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) + { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithExpiration, + )); + } + + if unlock_conditions.storage_deposit_return().is_some() + && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) + { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::OutputsWithStorageDepositReturn, + )); + } + + match output_kind { + AccountOutput::KIND if !address.has_capability(AddressCapabilityFlag::AccountOutputs) => { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::AccountOutputs, + )); + } + AnchorOutput::KIND if !address.has_capability(AddressCapabilityFlag::AnchorOutputs) => { + return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::AnchorOutputs)); + } + NftOutput::KIND if !address.has_capability(AddressCapabilityFlag::NftOutputs) => { + return Err(Error::RestrictedAddressCapability(AddressCapabilityFlag::NftOutputs)); + } + DelegationOutput::KIND if !address.has_capability(AddressCapabilityFlag::DelegationOutputs) => { + return Err(Error::RestrictedAddressCapability( + AddressCapabilityFlag::DelegationOutputs, + )); + } + _ => {} + } + } + Ok(()) +} + #[cfg(test)] mod test { use pretty_assertions::assert_eq; diff --git a/sdk/src/types/block/payload/tagged_data.rs b/sdk/src/types/block/payload/tagged_data.rs index f99a2e280e..0bdec46c8d 100644 --- a/sdk/src/types/block/payload/tagged_data.rs +++ b/sdk/src/types/block/payload/tagged_data.rs @@ -1,7 +1,8 @@ // Copyright 2020-2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -//! Module describing the tagged data payload. +//! A basic payload type that allows the addition of arbitrary data. +//! [TIP-53: Tagged Data](https://github.com/iotaledger/tips/blob/tip53/tips/TIP-0053/tip-0053.md). use alloc::boxed::Box; use core::ops::RangeInclusive; @@ -22,7 +23,7 @@ pub(crate) type TagLength = pub(crate) type TaggedDataLength = BoundedU32<{ *TaggedDataPayload::DATA_LENGTH_RANGE.start() }, { *TaggedDataPayload::DATA_LENGTH_RANGE.end() }>; -/// A payload which holds a tag and associated data. +/// A payload which holds optional data with an optional tag. #[derive(Clone, Eq, PartialEq, Packable)] #[packable(unpack_error = Error)] pub struct TaggedDataPayload { @@ -35,9 +36,9 @@ pub struct TaggedDataPayload { impl TaggedDataPayload { /// The [`Payload`](crate::types::block::payload::Payload) kind of a [`TaggedDataPayload`]. pub const KIND: u8 = 0; - /// Valid length range for the tag. + /// Valid tag length range. pub const TAG_LENGTH_RANGE: RangeInclusive = 0..=64; - /// Valid length range for the data. + /// Valid data length range. pub const DATA_LENGTH_RANGE: RangeInclusive = 0..=8192; /// Creates a new [`TaggedDataPayload`]. diff --git a/sdk/src/types/block/semantic/mod.rs b/sdk/src/types/block/semantic/mod.rs index 08317e7d55..5e6a7a429f 100644 --- a/sdk/src/types/block/semantic/mod.rs +++ b/sdk/src/types/block/semantic/mod.rs @@ -15,10 +15,9 @@ pub use self::{ state_transition::{StateTransitionError, StateTransitionVerifier}, }; use crate::types::block::{ - address::{Address, AddressCapabilityFlag}, + address::Address, output::{ AccountId, AnchorOutput, ChainId, FoundryId, MinimumOutputAmount, NativeTokens, Output, OutputId, TokenId, - UnlockCondition, }, payload::signed_transaction::{Transaction, TransactionCapabilityFlag, TransactionSigningHash}, protocol::ProtocolParameters, @@ -243,65 +242,6 @@ impl<'a> SemanticValidationContext<'a> { Output::Delegation(output) => (output.amount(), 0, None, None), }; - if let Some(unlock_conditions) = created_output.unlock_conditions() { - // Check the possibly restricted address-containing conditions - let addresses = unlock_conditions - .iter() - .filter_map(|uc| match uc { - UnlockCondition::Address(uc) => Some(uc.address()), - UnlockCondition::Expiration(uc) => Some(uc.return_address()), - UnlockCondition::StateControllerAddress(uc) => Some(uc.address()), - UnlockCondition::GovernorAddress(uc) => Some(uc.address()), - _ => None, - }) - .filter_map(Address::as_restricted_opt); - for address in addresses { - if created_native_token.is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithNativeTokens) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - - if mana > 0 && !address.has_capability(AddressCapabilityFlag::OutputsWithMana) { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - - if unlock_conditions.timelock().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithTimelock) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - - if unlock_conditions.expiration().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithExpiration) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - - if unlock_conditions.storage_deposit_return().is_some() - && !address.has_capability(AddressCapabilityFlag::OutputsWithStorageDepositReturn) - { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - - if match &created_output { - Output::Account(_) => !address.has_capability(AddressCapabilityFlag::AccountOutputs), - Output::Anchor(_) => !address.has_capability(AddressCapabilityFlag::AnchorOutputs), - Output::Nft(_) => !address.has_capability(AddressCapabilityFlag::NftOutputs), - Output::Delegation(_) => !address.has_capability(AddressCapabilityFlag::DelegationOutputs), - _ => false, - } { - // TODO: add a variant https://github.com/iotaledger/iota-sdk/issues/1430 - return Ok(Some(TransactionFailureReason::SemanticValidationFailed)); - } - } - } - if let Some(sender) = features.and_then(|f| f.sender()) { if !self.unlocked_addresses.contains(sender.address()) { return Ok(Some(TransactionFailureReason::SenderNotUnlocked)); diff --git a/sdk/src/wallet/core/builder.rs b/sdk/src/wallet/core/builder.rs index 63e87715ee..c9aa3e7e23 100644 --- a/sdk/src/wallet/core/builder.rs +++ b/sdk/src/wallet/core/builder.rs @@ -227,9 +227,6 @@ where #[cfg(feature = "storage")] self.save(&storage_manager).await?; - #[cfg(feature = "events")] - let event_emitter = tokio::sync::RwLock::new(EventEmitter::new()); - // It happened that inputs got locked, the transaction failed, but they weren't unlocked again, so we do this // here #[cfg(feature = "storage")] @@ -253,7 +250,7 @@ where client, secret_manager: self.secret_manager.expect("make WalletInner::secret_manager optional?"), #[cfg(feature = "events")] - event_emitter, + event_emitter: tokio::sync::RwLock::new(EventEmitter::new()), #[cfg(feature = "storage")] storage_options, #[cfg(feature = "storage")] diff --git a/sdk/src/wallet/core/operations/address_generation.rs b/sdk/src/wallet/core/operations/address_generation.rs index fe902ee8ec..50427dd324 100644 --- a/sdk/src/wallet/core/operations/address_generation.rs +++ b/sdk/src/wallet/core/operations/address_generation.rs @@ -36,41 +36,34 @@ impl Wallet { // needs to have it visible on the computer first, so we need to generate it without the // prompt first let options = options.into(); + #[cfg(feature = "events")] if options.as_ref().map_or(false, |o| o.ledger_nano_prompt) { - #[cfg(feature = "events")] - { - let changed_options = options.map(|mut options| { - // Change options so ledger will not show the prompt the first time - options.ledger_nano_prompt = false; - options - }); - // Generate without prompt to be able to display it - let address = ledger_nano - .generate_ed25519_addresses( - coin_type, - account_index, - address_index..address_index + 1, - changed_options, - ) - .await?; + let changed_options = options.map(|mut options| { + // Change options so ledger will not show the prompt the first time + options.ledger_nano_prompt = false; + options + }); + // Generate without prompt to be able to display it + let address = ledger_nano + .generate_ed25519_addresses( + coin_type, + account_index, + address_index..address_index + 1, + changed_options, + ) + .await?; - let bech32_hrp = self.bech32_hrp().await; + let bech32_hrp = self.bech32_hrp().await; - self.emit(WalletEvent::LedgerAddressGeneration(AddressData { - address: address[0].to_bech32(bech32_hrp), - })) - .await; - } - - // Generate with prompt so the user can verify - ledger_nano - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } else { - ledger_nano - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? + self.emit(WalletEvent::LedgerAddressGeneration(AddressData { + address: address[0].to_bech32(bech32_hrp), + })) + .await; } + // Generate with prompt so the user can verify + ledger_nano + .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) + .await? } #[cfg(feature = "stronghold")] SecretManager::Stronghold(stronghold) => { diff --git a/sdk/src/wallet/core/operations/background_syncing.rs b/sdk/src/wallet/core/operations/background_syncing.rs index 9840b967b6..9bea901acc 100644 --- a/sdk/src/wallet/core/operations/background_syncing.rs +++ b/sdk/src/wallet/core/operations/background_syncing.rs @@ -72,6 +72,12 @@ where Ok(()) } + /// Request to stop the background syncing of the wallet + pub fn request_stop_background_syncing(&self) { + log::debug!("[request_stop_background_syncing]"); + self.background_syncing_status.store(2, Ordering::Relaxed); + } + /// Stop the background syncing of the wallet pub async fn stop_background_syncing(&self) -> crate::wallet::Result<()> { log::debug!("[stop_background_syncing]"); @@ -80,7 +86,7 @@ where return Ok(()); } // send stop request - self.background_syncing_status.store(2, Ordering::Relaxed); + self.request_stop_background_syncing(); // wait until it stopped while self.background_syncing_status.load(Ordering::Relaxed) != 0 { #[cfg(target_family = "wasm")] diff --git a/sdk/src/wallet/error.rs b/sdk/src/wallet/error.rs index ad26c39a91..e6b222517f 100644 --- a/sdk/src/wallet/error.rs +++ b/sdk/src/wallet/error.rs @@ -33,9 +33,6 @@ pub enum Error { new_bip_path: Option, old_bip_path: Option, }, - /// Funds are spread over too many outputs - #[error("funds are spread over too many outputs {output_count}/{output_count_max}, consolidation required")] - ConsolidationRequired { output_count: usize, output_count_max: u16 }, /// Crypto.rs error #[error("{0}")] Crypto(#[from] crypto::Error), diff --git a/sdk/src/wallet/events/mod.rs b/sdk/src/wallet/events/mod.rs index e908a0d1f9..c75cc39acc 100644 --- a/sdk/src/wallet/events/mod.rs +++ b/sdk/src/wallet/events/mod.rs @@ -41,7 +41,6 @@ impl EventEmitter { WalletEventType::SpentOutput, WalletEventType::TransactionInclusion, WalletEventType::TransactionProgress, - WalletEventType::ConsolidationRequired, #[cfg(feature = "ledger_nano")] WalletEventType::LedgerAddressGeneration, ] { @@ -74,7 +73,6 @@ impl EventEmitter { WalletEvent::SpentOutput(_) => WalletEventType::SpentOutput, WalletEvent::TransactionInclusion(_) => WalletEventType::TransactionInclusion, WalletEvent::TransactionProgress(_) => WalletEventType::TransactionProgress, - WalletEvent::ConsolidationRequired => WalletEventType::ConsolidationRequired, #[cfg(feature = "ledger_nano")] WalletEvent::LedgerAddressGeneration(_) => WalletEventType::LedgerAddressGeneration, }; @@ -126,18 +124,18 @@ mod tests { let event_counter = Arc::new(AtomicUsize::new(0)); // single event - emitter.on([WalletEventType::ConsolidationRequired], |_name| { - // println!("ConsolidationRequired: {:?}", name); + emitter.on([WalletEventType::TransactionInclusion], |_name| { + // println!("TransactionInclusion: {:?}", name); }); // listen to two events emitter.on( [ WalletEventType::TransactionProgress, - WalletEventType::ConsolidationRequired, + WalletEventType::TransactionInclusion, ], move |_name| { - // println!("TransactionProgress or ConsolidationRequired: {:?}", name); + // println!("TransactionProgress or TransactionInclusion: {:?}", name); }, ); @@ -149,7 +147,6 @@ mod tests { }); // emit events - emitter.emit(WalletEvent::ConsolidationRequired); emitter.emit(WalletEvent::TransactionProgress( TransactionProgressEvent::SelectingInputs, )); @@ -161,14 +158,16 @@ mod tests { inclusion_state: InclusionState::Confirmed, })); - assert_eq!(3, event_counter.load(Ordering::SeqCst)); + assert_eq!(2, event_counter.load(Ordering::SeqCst)); // remove handlers of single event - emitter.clear([WalletEventType::ConsolidationRequired]); + emitter.clear([WalletEventType::TransactionProgress]); // emit event of removed type - emitter.emit(WalletEvent::ConsolidationRequired); + emitter.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::SelectingInputs, + )); - assert_eq!(3, event_counter.load(Ordering::SeqCst)); + assert_eq!(2, event_counter.load(Ordering::SeqCst)); // remove handlers of all events emitter.clear([]); @@ -183,18 +182,20 @@ mod tests { .expect("invalid tx id"), inclusion_state: InclusionState::Confirmed, })); - assert_eq!(3, event_counter.load(Ordering::SeqCst)); + assert_eq!(2, event_counter.load(Ordering::SeqCst)); // listen to a single event let event_counter_clone = Arc::clone(&event_counter); - emitter.on([WalletEventType::ConsolidationRequired], move |_name| { + emitter.on([WalletEventType::TransactionProgress], move |_name| { // println!("Any event: {:?}", name); event_counter_clone.fetch_add(1, Ordering::SeqCst); }); for _ in 0..1_000_000 { - emitter.emit(WalletEvent::ConsolidationRequired); + emitter.emit(WalletEvent::TransactionProgress( + TransactionProgressEvent::SelectingInputs, + )); } - assert_eq!(1_000_003, event_counter.load(Ordering::SeqCst)); + assert_eq!(1_000_002, event_counter.load(Ordering::SeqCst)); } } diff --git a/sdk/src/wallet/events/types.rs b/sdk/src/wallet/events/types.rs index 71a4e7b927..475fd5ab2e 100644 --- a/sdk/src/wallet/events/types.rs +++ b/sdk/src/wallet/events/types.rs @@ -22,7 +22,6 @@ use crate::{ #[derive(Clone, Debug, Eq, PartialEq)] #[non_exhaustive] pub enum WalletEvent { - ConsolidationRequired, #[cfg(feature = "ledger_nano")] #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))] LedgerAddressGeneration(AddressData), @@ -45,13 +44,12 @@ impl Serialize for WalletEvent { #[derive(Serialize)] #[serde(untagged)] enum WalletEvent_<'a> { - T0, #[cfg(feature = "ledger_nano")] - T1(&'a AddressData), - T2(&'a NewOutputEvent), - T3(&'a SpentOutputEvent), - T4(&'a TransactionInclusionEvent), - T5(TransactionProgressEvent_<'a>), + T0(&'a AddressData), + T1(&'a NewOutputEvent), + T2(&'a SpentOutputEvent), + T3(&'a TransactionInclusionEvent), + T4(TransactionProgressEvent_<'a>), } #[derive(Serialize)] struct TypedWalletEvent_<'a> { @@ -61,30 +59,26 @@ impl Serialize for WalletEvent { event: WalletEvent_<'a>, } let event = match self { - Self::ConsolidationRequired => TypedWalletEvent_ { - kind: WalletEventType::ConsolidationRequired as u8, - event: WalletEvent_::T0, - }, #[cfg(feature = "ledger_nano")] Self::LedgerAddressGeneration(e) => TypedWalletEvent_ { kind: WalletEventType::LedgerAddressGeneration as u8, - event: WalletEvent_::T1(e), + event: WalletEvent_::T0(e), }, Self::NewOutput(e) => TypedWalletEvent_ { kind: WalletEventType::NewOutput as u8, - event: WalletEvent_::T2(e), + event: WalletEvent_::T1(e), }, Self::SpentOutput(e) => TypedWalletEvent_ { kind: WalletEventType::SpentOutput as u8, - event: WalletEvent_::T3(e), + event: WalletEvent_::T2(e), }, Self::TransactionInclusion(e) => TypedWalletEvent_ { kind: WalletEventType::TransactionInclusion as u8, - event: WalletEvent_::T4(e), + event: WalletEvent_::T3(e), }, Self::TransactionProgress(e) => TypedWalletEvent_ { kind: WalletEventType::TransactionProgress as u8, - event: WalletEvent_::T5(TransactionProgressEvent_ { progress: e }), + event: WalletEvent_::T4(TransactionProgressEvent_ { progress: e }), }, }; event.serialize(serializer) @@ -108,7 +102,6 @@ impl<'de> Deserialize<'de> for WalletEvent { ) .map_err(serde::de::Error::custom)? { - WalletEventType::ConsolidationRequired => Self::ConsolidationRequired, #[cfg(feature = "ledger_nano")] WalletEventType::LedgerAddressGeneration => { Self::LedgerAddressGeneration(AddressData::deserialize(value).map_err(|e| { @@ -146,14 +139,13 @@ impl<'de> Deserialize<'de> for WalletEvent { #[repr(u8)] #[non_exhaustive] pub enum WalletEventType { - ConsolidationRequired = 0, #[cfg(feature = "ledger_nano")] #[cfg_attr(docsrs, doc(cfg(feature = "ledger_nano")))] - LedgerAddressGeneration = 1, - NewOutput = 2, - SpentOutput = 3, - TransactionInclusion = 4, - TransactionProgress = 5, + LedgerAddressGeneration = 0, + NewOutput = 1, + SpentOutput = 2, + TransactionInclusion = 3, + TransactionProgress = 4, } impl TryFrom for WalletEventType { @@ -161,13 +153,12 @@ impl TryFrom for WalletEventType { fn try_from(value: u8) -> Result { let event_type = match value { - 0 => Self::ConsolidationRequired, #[cfg(feature = "ledger_nano")] - 1 => Self::LedgerAddressGeneration, - 2 => Self::NewOutput, - 3 => Self::SpentOutput, - 4 => Self::TransactionInclusion, - 5 => Self::TransactionProgress, + 0 => Self::LedgerAddressGeneration, + 1 => Self::NewOutput, + 2 => Self::SpentOutput, + 3 => Self::TransactionInclusion, + 4 => Self::TransactionProgress, _ => return Err(Error::InvalidEventType(value)), }; Ok(event_type) diff --git a/sdk/src/wallet/operations/syncing/transactions.rs b/sdk/src/wallet/operations/syncing/transactions.rs index 609dff8c62..57e4662179 100644 --- a/sdk/src/wallet/operations/syncing/transactions.rs +++ b/sdk/src/wallet/operations/syncing/transactions.rs @@ -103,8 +103,10 @@ where Ok(metadata) => { if let Some(tx_state) = metadata.transaction_metadata.map(|m| m.transaction_state) { match tx_state { - // TODO: Separate TransactionState::Finalized? - TransactionState::Finalized | TransactionState::Confirmed => { + // TODO: Separate TransactionState::Finalized, TransactionState::Accepted? https://github.com/iotaledger/iota-sdk/issues/1814 + TransactionState::Accepted + | TransactionState::Confirmed + | TransactionState::Finalized => { log::debug!( "[SYNC] confirmed transaction {transaction_id} in block {}", metadata.block_id diff --git a/sdk/src/wallet/operations/transaction/input_selection.rs b/sdk/src/wallet/operations/transaction/input_selection.rs index 5422f2a922..9a5b8c8bb2 100644 --- a/sdk/src/wallet/operations/transaction/input_selection.rs +++ b/sdk/src/wallet/operations/transaction/input_selection.rs @@ -171,22 +171,7 @@ where input_selection = input_selection.with_burn(burn.clone()); } - let selected_transaction_data = match input_selection.select() { - Ok(r) => r, - // TODO this error doesn't exist with the new ISA - // Err(crate::client::Error::ConsolidationRequired(output_count)) => { - // #[cfg(feature = "events")] - // self.event_emitter - // .lock() - // .await - // .emit(account.index, WalletEvent::ConsolidationRequired); - // return Err(crate::wallet::Error::ConsolidationRequired { - // output_count, - // output_count_max: INPUT_COUNT_MAX, - // }); - // } - Err(e) => return Err(e.into()), - }; + let selected_transaction_data = input_selection.select()?; // lock outputs so they don't get used by another transaction for output in &selected_transaction_data.inputs { diff --git a/sdk/tests/types/address/multi.rs b/sdk/tests/types/address/multi.rs index 8705b358ec..a0ba5e6fb6 100644 --- a/sdk/tests/types/address/multi.rs +++ b/sdk/tests/types/address/multi.rs @@ -1,10 +1,32 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -use iota_sdk::types::block::address::{Address, ToBech32Ext}; +use iota_sdk::types::block::{ + address::{AccountAddress, Address, Ed25519Address, MultiAddress, ToBech32Ext, WeightedAddress}, + output::AccountId, + rand::bytes::rand_bytes_array, +}; use packable::PackableExt; use pretty_assertions::assert_eq; +#[test] +fn ordered_by_packed_bytes() { + let mut bytes_1 = rand_bytes_array::<32>(); + bytes_1[0] = 0; + let mut bytes_2 = bytes_1.clone(); + bytes_2[0] = 1; + + let weighted_1 = WeightedAddress::new(AccountAddress::from(AccountId::from(bytes_1)), 1).unwrap(); + let weighted_2 = WeightedAddress::new(Ed25519Address::from(bytes_2), 1).unwrap(); + + let multi_1 = MultiAddress::new([weighted_1, weighted_2], 2).unwrap(); + let bytes = multi_1.pack_to_vec(); + let multi_2 = MultiAddress::unpack_verified(bytes, &()).unwrap(); + + assert!(multi_2.addresses()[0].address().is_ed25519()); + assert!(multi_2.addresses()[1].address().is_account()); +} + #[test] fn json_packable_bech32() { // Test from https://github.com/iotaledger/tips/blob/tip52/tips/TIP-0052/tip-0052.md#bech32 diff --git a/sdk/tests/wallet/events.rs b/sdk/tests/wallet/events.rs index 62892ea721..9dac2168ea 100644 --- a/sdk/tests/wallet/events.rs +++ b/sdk/tests/wallet/events.rs @@ -40,8 +40,6 @@ fn assert_serde_eq(event_0: WalletEvent) { #[test] fn wallet_events_serde() { - assert_serde_eq(WalletEvent::ConsolidationRequired); - #[cfg(feature = "ledger_nano")] assert_serde_eq(WalletEvent::LedgerAddressGeneration(AddressData { address: Bech32Address::try_from_str("rms1qpllaj0pyveqfkwxmnngz2c488hfdtmfrj3wfkgxtk4gtyrax0jaxzt70zy")