From f7415be8c60c9de33ee03924db2305261d847de1 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Mon, 6 Nov 2023 13:37:40 -0500 Subject: [PATCH 1/2] allow some wallet data to be accessible by reference --- bindings/core/src/method_handler/wallet.rs | 111 +++-- cli/src/wallet_cli/mod.rs | 55 ++- .../consolidate_outputs.rs | 50 ++- .../accounts_and_addresses/list_outputs.rs | 4 +- .../list_transactions.rs | 4 +- sdk/examples/wallet/spammer.rs | 6 +- sdk/src/wallet/core/mod.rs | 400 ++++++++---------- .../wallet/operations/participation/mod.rs | 16 +- sdk/src/wallet/operations/syncing/mod.rs | 7 +- .../transaction/high_level/send_nft.rs | 3 +- .../operations/transaction/prepare_output.rs | 2 +- sdk/tests/wallet/consolidation.rs | 4 +- sdk/tests/wallet/output_preparation.rs | 12 +- 13 files changed, 351 insertions(+), 323 deletions(-) diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index 50948dadb7..aa5d379db2 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -156,18 +156,22 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let output = wallet.get_foundry_output(token_id).await?; Response::Output(OutputDto::from(&output)) } - WalletMethod::GetIncomingTransaction { transaction_id } => { - let transaction = wallet.get_incoming_transaction(&transaction_id).await; - - transaction.map_or_else( + WalletMethod::GetIncomingTransaction { transaction_id } => wallet + .data() + .await + .get_incoming_transaction(&transaction_id) + .map_or_else( || Response::Transaction(None), - |transaction| Response::Transaction(Some(Box::new(TransactionWithMetadataDto::from(&transaction)))), - ) - } - WalletMethod::GetOutput { output_id } => { - let output_data = wallet.get_output(&output_id).await; - Response::OutputData(output_data.as_ref().map(OutputDataDto::from).map(Box::new)) - } + |transaction| Response::Transaction(Some(Box::new(TransactionWithMetadataDto::from(transaction)))), + ), + WalletMethod::GetOutput { output_id } => Response::OutputData( + wallet + .data() + .await + .get_output(&output_id) + .map(OutputDataDto::from) + .map(Box::new), + ), #[cfg(feature = "participation")] WalletMethod::GetParticipationEvent { event_id } => { let event_and_nodes = wallet.get_participation_event(event_id).await?; @@ -193,10 +197,14 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let overview = wallet.get_participation_overview(event_ids).await?; Response::ParticipationOverview(overview) } - WalletMethod::GetTransaction { transaction_id } => { - let transaction = wallet.get_transaction(&transaction_id).await; - Response::Transaction(transaction.as_ref().map(TransactionWithMetadataDto::from).map(Box::new)) - } + WalletMethod::GetTransaction { transaction_id } => Response::Transaction( + wallet + .data() + .await + .get_transaction(&transaction_id) + .map(TransactionWithMetadataDto::from) + .map(Box::new), + ), #[cfg(feature = "participation")] WalletMethod::GetVotingPower => { let voting_power = wallet.get_voting_power().await?; @@ -206,22 +214,38 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; Response::Bech32Address(implicit_account_creation_address) } - WalletMethod::ImplicitAccounts => { - let implicit_accounts = wallet.implicit_accounts().await; - Response::OutputsData(implicit_accounts.iter().map(OutputDataDto::from).collect()) - } - WalletMethod::IncomingTransactions => { - let transactions = wallet.incoming_transactions().await; - Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) - } + WalletMethod::ImplicitAccounts => Response::OutputsData( + wallet + .data() + .await + .implicit_accounts() + .map(OutputDataDto::from) + .collect(), + ), + WalletMethod::IncomingTransactions => Response::Transactions( + wallet + .data() + .await + .incoming_transactions() + .map(TransactionWithMetadataDto::from) + .collect(), + ), WalletMethod::Outputs { filter_options } => { - let outputs = wallet.outputs(filter_options).await; - Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect()) - } - WalletMethod::PendingTransactions => { - let transactions = wallet.pending_transactions().await; - Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) - } + let wallet_data = wallet.data().await; + Response::OutputsData(if let Some(filter) = filter_options { + wallet_data.filtered_outputs(filter).map(OutputDataDto::from).collect() + } else { + wallet_data.outputs().values().map(OutputDataDto::from).collect() + }) + } + WalletMethod::PendingTransactions => Response::Transactions( + wallet + .data() + .await + .pending_transactions() + .map(TransactionWithMetadataDto::from) + .collect(), + ), WalletMethod::PrepareBurn { burn, options } => { let data = wallet.prepare_burn(burn, options).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) @@ -393,13 +417,28 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM Response::SentTransaction(TransactionWithMetadataDto::from(&transaction)) } WalletMethod::Sync { options } => Response::Balance(wallet.sync(options).await?), - WalletMethod::Transactions => { - let transactions = wallet.transactions().await; - Response::Transactions(transactions.iter().map(TransactionWithMetadataDto::from).collect()) - } + WalletMethod::Transactions => Response::Transactions( + wallet + .data() + .await + .transactions() + .map(TransactionWithMetadataDto::from) + .collect(), + ), WalletMethod::UnspentOutputs { filter_options } => { - let outputs = wallet.unspent_outputs(filter_options).await; - Response::OutputsData(outputs.iter().map(OutputDataDto::from).collect()) + let wallet_data = wallet.data().await; + Response::OutputsData(if let Some(filter) = filter_options { + wallet_data + .filtered_unspent_outputs(filter) + .map(OutputDataDto::from) + .collect() + } else { + wallet_data + .unspent_outputs() + .values() + .map(OutputDataDto::from) + .collect() + }) } }; Ok(response) diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index f78c3dc5ce..24b3e9a6ae 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -23,9 +23,8 @@ use iota_sdk::{ }, }, wallet::{ - types::{OutputData, TransactionWithMetadata}, - ConsolidationParams, CreateNativeTokenParams, MintNftParams, OutputsToClaim, SendNativeTokensParams, - SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, + types::OutputData, ConsolidationParams, CreateNativeTokenParams, MintNftParams, OutputsToClaim, + SendNativeTokensParams, SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, }, U256, }; @@ -401,9 +400,9 @@ pub async fn claimable_outputs_command(wallet: &Wallet) -> Result<(), Error> { .iter() .filter_map(|(output_id, unlockable)| unlockable.then_some(output_id)) { + let wallet_data = wallet.data().await; // Unwrap: for the iterated `OutputId`s this call will always return `Some(...)`. - let output_data = wallet.get_output(output_id).await.unwrap(); - let output = output_data.output; + let output = &wallet_data.get_output(output_id).unwrap().output; let kind = match output { Output::Nft(_) => "Nft", Output::Basic(_) => "Basic", @@ -572,7 +571,10 @@ pub async fn implicit_account_creation_address_command(wallet: &Wallet) -> Resul // `implicit-accounts` command pub async fn implicit_accounts_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.implicit_accounts().await, "Implicit accounts:").await + print_outputs( + wallet.data().await.implicit_accounts().cloned().collect(), + "Implicit accounts:", + ) } // `melt-native-token` command @@ -658,11 +660,12 @@ pub async fn node_info_command(wallet: &Wallet) -> Result<(), Error> { /// `output` command pub async fn output_command(wallet: &Wallet, selector: OutputSelector) -> Result<(), Error> { + let wallet_data = wallet.data().await; let output = match selector { - OutputSelector::Id(id) => wallet.get_output(&id).await, + OutputSelector::Id(id) => wallet_data.get_output(&id), OutputSelector::Index(index) => { - let mut outputs = wallet.outputs(None).await; - outputs.sort_unstable_by(outputs_ordering); + let mut outputs = wallet_data.outputs().values().collect::>(); + outputs.sort_unstable_by_key(|o| o.output_id); outputs.into_iter().nth(index) } }; @@ -678,7 +681,7 @@ pub async fn output_command(wallet: &Wallet, selector: OutputSelector) -> Result /// `outputs` command pub async fn outputs_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.outputs(None).await, "Outputs:").await + print_outputs(wallet.data().await.outputs().values().cloned().collect(), "Outputs:") } // `send` command @@ -792,11 +795,12 @@ pub async fn sync_command(wallet: &Wallet) -> Result<(), Error> { /// `transaction` command pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) -> Result<(), Error> { - let mut transactions = wallet.transactions().await; + let wallet_data = wallet.data().await; let transaction = match selector { - TransactionSelector::Id(id) => transactions.into_iter().find(|tx| tx.transaction_id == id), + TransactionSelector::Id(id) => wallet_data.transactions().find(|tx| tx.transaction_id == id), TransactionSelector::Index(index) => { - transactions.sort_unstable_by(transactions_ordering); + let mut transactions = wallet_data.transactions().collect::>(); + transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); transactions.into_iter().nth(index) } }; @@ -812,8 +816,9 @@ pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) /// `transactions` command pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result<(), Error> { - let mut transactions = wallet.transactions().await; - transactions.sort_unstable_by(transactions_ordering); + let wallet_data = wallet.data().await; + let mut transactions = wallet_data.transactions().collect::>(); + transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); if transactions.is_empty() { println_log_info!("No transactions found"); @@ -835,7 +840,10 @@ pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result /// `unspent-outputs` command pub async fn unspent_outputs_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.unspent_outputs(None).await, "Unspent outputs:").await + print_outputs( + wallet.data().await.unspent_outputs().values().cloned().collect(), + "Unspent outputs:", + ) } pub async fn vote_command(wallet: &Wallet, event_id: ParticipationEventId, answers: Vec) -> Result<(), Error> { @@ -924,7 +932,6 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { address.inner() ); - let unspent_outputs = wallet.unspent_outputs(None).await; let slot_index = wallet.client().get_slot_index().await?; let mut output_ids = Vec::new(); @@ -936,7 +943,7 @@ async fn print_wallet_address(wallet: &Wallet) -> Result<(), Error> { let mut delegations = Vec::new(); let mut anchors = Vec::new(); - for output_data in unspent_outputs { + for output_data in wallet.data().await.unspent_outputs().values() { let output_id = output_data.output_id; output_ids.push(output_id); @@ -1186,12 +1193,12 @@ pub async fn prompt_internal( Ok(PromptResponse::Reprompt) } -async fn print_outputs(mut outputs: Vec, title: &str) -> Result<(), Error> { +fn print_outputs(mut outputs: Vec, title: &str) -> Result<(), Error> { if outputs.is_empty() { println_log_info!("No outputs found"); } else { println_log_info!("{title}"); - outputs.sort_unstable_by(outputs_ordering); + outputs.sort_unstable_by_key(|o| o.output_id); for (i, output_data) in outputs.into_iter().enumerate() { println_log_info!( @@ -1206,11 +1213,3 @@ async fn print_outputs(mut outputs: Vec, title: &str) -> Result<(), Ok(()) } - -fn outputs_ordering(a: &OutputData, b: &OutputData) -> std::cmp::Ordering { - a.output_id.cmp(&b.output_id) -} - -fn transactions_ordering(a: &TransactionWithMetadata, b: &TransactionWithMetadata) -> std::cmp::Ordering { - b.timestamp.cmp(&a.timestamp) -} diff --git a/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs b/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs index bb08f13690..b578b6a56e 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs @@ -42,17 +42,22 @@ async fn main() -> Result<()> { // unlock condition and it is an `AddressUnlockCondition`, and so they are valid for consolidation. They have the // same `AddressUnlockCondition`(the address of the wallet), so they will be consolidated into one // output. - let outputs = wallet.unspent_outputs(None).await; println!("Outputs BEFORE consolidation:"); - outputs.iter().enumerate().for_each(|(i, output_data)| { - println!("OUTPUT #{i}"); - println!( - "- address: {:?}\n- amount: {:?}\n- native tokens: {:?}", - output_data.address.clone().to_bech32_unchecked("rms"), - output_data.output.amount(), - output_data.output.native_tokens() - ) - }); + wallet + .data() + .await + .unspent_outputs() + .values() + .enumerate() + .for_each(|(i, output_data)| { + println!("OUTPUT #{i}"); + println!( + "- address: {:?}\n- amount: {:?}\n- native tokens: {:?}", + output_data.address.clone().to_bech32_unchecked("rms"), + output_data.output.amount(), + output_data.output.native_tokens() + ) + }); println!("Sending consolidation transaction..."); @@ -78,17 +83,22 @@ async fn main() -> Result<()> { println!("Wallet synced"); // Outputs after consolidation - let outputs = wallet.unspent_outputs(None).await; println!("Outputs AFTER consolidation:"); - outputs.iter().enumerate().for_each(|(i, output_data)| { - println!("OUTPUT #{i}"); - println!( - "- address: {:?}\n- amount: {:?}\n- native tokens: {:?}", - output_data.address.clone().to_bech32_unchecked("rms"), - output_data.output.amount(), - output_data.output.native_tokens() - ) - }); + wallet + .data() + .await + .unspent_outputs() + .values() + .enumerate() + .for_each(|(i, output_data)| { + println!("OUTPUT #{i}"); + println!( + "- address: {:?}\n- amount: {:?}\n- native tokens: {:?}", + output_data.address.clone().to_bech32_unchecked("rms"), + output_data.output.amount(), + output_data.output.native_tokens() + ) + }); Ok(()) } diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs b/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs index 9c732c4831..e1135e3f3f 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs @@ -25,13 +25,13 @@ async fn main() -> Result<()> { // Print output ids println!("Output ids:"); - for output in wallet.outputs(None).await { + for output in wallet.data().await.outputs().values() { println!("{}", output.output_id); } // Print unspent output ids println!("Unspent output ids:"); - for output in wallet.unspent_outputs(None).await { + for output in wallet.data().await.unspent_outputs().values() { println!("{}", output.output_id); } diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs index 6d07e6cb7c..faca71ca93 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs @@ -33,13 +33,13 @@ async fn main() -> Result<()> { // Print transaction ids println!("Sent transactions:"); - for transaction in wallet.transactions().await { + for transaction in wallet.data().await.transactions() { println!("{}", transaction.transaction_id); } // Print received transaction ids println!("Received transactions:"); - for transaction in wallet.incoming_transactions().await { + for transaction in wallet.data().await.incoming_transactions() { println!("{}", transaction.transaction_id); } diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index 9387836fd1..7d6c2b96b5 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -70,12 +70,12 @@ async fn main() -> Result<()> { // We make sure that for all threads there are always inputs available to // fund the transaction, otherwise we create enough unspent outputs. let num_unspent_basic_outputs_with_send_amount = wallet - .unspent_outputs(FilterOptions { + .data() + .await + .filtered_unspent_outputs(FilterOptions { output_types: Some(vec![BasicOutput::KIND]), ..Default::default() }) - .await - .iter() .filter(|data| data.output.amount() >= SEND_AMOUNT) .count(); diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index f8a83acea3..80276c47cd 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -37,7 +37,7 @@ use crate::{ dto::FoundryOutputDto, AccountId, AnchorId, DelegationId, FoundryId, FoundryOutput, NftId, Output, OutputId, TokenId, }, - payload::signed_transaction::{dto::TransactionDto, Transaction, TransactionId}, + payload::signed_transaction::TransactionId, }, TryFromDto, }, @@ -156,6 +156,191 @@ impl WalletData { native_token_foundries: HashMap::new(), } } + + fn filter_outputs<'a>( + outputs: impl Iterator, + filter: FilterOptions, + ) -> impl Iterator { + outputs.filter(move |output| { + match &output.output { + Output::Account(account) => { + if let Some(account_ids) = &filter.account_ids { + let account_id = account.account_id_non_null(&output.output_id); + if account_ids.contains(&account_id) { + return true; + } + } + } + Output::Anchor(anchor) => { + if let Some(anchor_ids) = &filter.anchor_ids { + let anchor_id = anchor.anchor_id_non_null(&output.output_id); + if anchor_ids.contains(&anchor_id) { + return true; + } + } + } + Output::Foundry(foundry) => { + if let Some(foundry_ids) = &filter.foundry_ids { + let foundry_id = foundry.id(); + if foundry_ids.contains(&foundry_id) { + return true; + } + } + } + Output::Nft(nft) => { + if let Some(nft_ids) = &filter.nft_ids { + let nft_id = nft.nft_id_non_null(&output.output_id); + if nft_ids.contains(&nft_id) { + return true; + } + } + } + Output::Delegation(delegation) => { + if let Some(delegation_ids) = &filter.delegation_ids { + let delegation_id = delegation.delegation_id_non_null(&output.output_id); + if delegation_ids.contains(&delegation_id) { + return true; + } + } + } + _ => {} + } + + // TODO filter based on slot index + // if let Some(lower_bound_booked_timestamp) = filter.lower_bound_booked_timestamp { + // if output.metadata.milestone_timestamp_booked() < lower_bound_booked_timestamp { + // continue; + // } + // } + // if let Some(upper_bound_booked_timestamp) = filter.upper_bound_booked_timestamp { + // if output.metadata.milestone_timestamp_booked() > upper_bound_booked_timestamp { + // continue; + // } + // } + + if let Some(output_types) = &filter.output_types { + if !output_types.contains(&output.output.kind()) { + return false; + } + } + + // Include the output if we're not filtering by IDs. + if filter.account_ids.is_none() + && filter.anchor_ids.is_none() + && filter.foundry_ids.is_none() + && filter.nft_ids.is_none() + && filter.delegation_ids.is_none() + { + return true; + } + false + }) + } + + /// Returns outputs map of the wallet. + pub fn outputs(&self) -> &HashMap { + &self.outputs + } + + /// Returns unspent outputs map of the wallet. + pub fn unspent_outputs(&self) -> &HashMap { + &self.unspent_outputs + } + + /// Returns outputs of the wallet. + pub fn filtered_outputs(&self, filter: FilterOptions) -> impl Iterator { + Self::filter_outputs(self.outputs.values(), filter) + } + + /// Returns unspent outputs of the wallet. + pub fn filtered_unspent_outputs(&self, filter: FilterOptions) -> impl Iterator { + Self::filter_outputs(self.unspent_outputs.values(), filter) + } + + /// Gets the unspent account output matching the given ID. + pub fn unspent_account_output(&self, account_id: &AccountId) -> Option<&OutputData> { + self.filtered_unspent_outputs(FilterOptions { + account_ids: Some([*account_id].into()), + ..Default::default() + }) + .next() + } + + /// Gets the unspent anchor output matching the given ID. + pub fn unspent_anchor_output(&self, anchor_id: &AnchorId) -> Option<&OutputData> { + self.filtered_unspent_outputs(FilterOptions { + anchor_ids: Some([*anchor_id].into()), + ..Default::default() + }) + .next() + } + + /// Gets the unspent foundry output matching the given ID. + pub fn unspent_foundry_output(&self, foundry_id: &FoundryId) -> Option<&OutputData> { + self.filtered_unspent_outputs(FilterOptions { + foundry_ids: Some([*foundry_id].into()), + ..Default::default() + }) + .next() + } + + /// Gets the unspent nft output matching the given ID. + pub fn unspent_nft_output(&self, nft_id: &NftId) -> Option<&OutputData> { + self.filtered_unspent_outputs(FilterOptions { + nft_ids: Some([*nft_id].into()), + ..Default::default() + }) + .next() + } + + /// Gets the unspent delegation output matching the given ID. + pub fn unspent_delegation_output(&self, delegation_id: &DelegationId) -> Option<&OutputData> { + self.filtered_unspent_outputs(FilterOptions { + delegation_ids: Some([*delegation_id].into()), + ..Default::default() + }) + .next() + } + + /// Returns implicit accounts of the wallet. + pub fn implicit_accounts(&self) -> impl Iterator { + self.unspent_outputs + .values() + .filter(|output_data| output_data.output.is_implicit_account()) + } + + /// Get the [`OutputData`] of an output stored in the wallet. + pub fn get_output(&self, output_id: &OutputId) -> Option<&OutputData> { + self.outputs.get(output_id) + } + + /// Get the [`TransactionWithMetadata`] of a transaction stored in the wallet. + pub fn get_transaction(&self, transaction_id: &TransactionId) -> Option<&TransactionWithMetadata> { + self.transactions.get(transaction_id) + } + + /// Get the transaction with inputs of an incoming transaction stored in the wallet. + /// List might not be complete, if the node pruned the data already + pub fn get_incoming_transaction(&self, transaction_id: &TransactionId) -> Option<&TransactionWithMetadata> { + self.incoming_transactions.get(transaction_id) + } + + /// Returns all incoming transactions of the wallet + pub fn incoming_transactions(&self) -> impl Iterator { + self.incoming_transactions.values() + } + + /// Returns all transactions of the wallet + pub fn transactions(&self) -> impl Iterator { + self.transactions.values() + } + + /// Returns all pending transactions of the wallet + pub fn pending_transactions(&self) -> impl Iterator { + self.pending_transactions + .iter() + .filter_map(|transaction_id| self.get_transaction(transaction_id)) + } } impl Wallet @@ -237,7 +422,7 @@ where self.inner.emit(wallet_event).await } - pub(crate) async fn data(&self) -> tokio::sync::RwLockReadGuard<'_, WalletData> { + pub async fn data(&self) -> tokio::sync::RwLockReadGuard<'_, WalletData> { self.data.read().await } @@ -278,217 +463,6 @@ where pub async fn bip_path(&self) -> Option { self.data().await.bip_path } - - /// Get the [`OutputData`] of an output stored in the wallet. - pub async fn get_output(&self, output_id: &OutputId) -> Option { - self.data().await.outputs.get(output_id).cloned() - } - - /// Get the [`TransactionWithMetadata`] of a transaction stored in the wallet. - pub async fn get_transaction(&self, transaction_id: &TransactionId) -> Option { - self.data().await.transactions.get(transaction_id).cloned() - } - - /// Get the transaction with inputs of an incoming transaction stored in the wallet. - /// List might not be complete, if the node pruned the data already - pub async fn get_incoming_transaction(&self, transaction_id: &TransactionId) -> Option { - self.data().await.incoming_transactions.get(transaction_id).cloned() - } - - fn filter_outputs<'a>( - &self, - outputs: impl Iterator, - filter: impl Into>, - ) -> Vec { - let filter = filter.into(); - - if let Some(filter) = filter { - let mut filtered_outputs = Vec::new(); - - for output in outputs { - match &output.output { - Output::Account(account) => { - if let Some(account_ids) = &filter.account_ids { - let account_id = account.account_id_non_null(&output.output_id); - if account_ids.contains(&account_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - Output::Anchor(anchor) => { - if let Some(anchor_ids) = &filter.anchor_ids { - let anchor_id = anchor.anchor_id_non_null(&output.output_id); - if anchor_ids.contains(&anchor_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - Output::Foundry(foundry) => { - if let Some(foundry_ids) = &filter.foundry_ids { - let foundry_id = foundry.id(); - if foundry_ids.contains(&foundry_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - Output::Nft(nft) => { - if let Some(nft_ids) = &filter.nft_ids { - let nft_id = nft.nft_id_non_null(&output.output_id); - if nft_ids.contains(&nft_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - Output::Delegation(delegation) => { - if let Some(delegation_ids) = &filter.delegation_ids { - let delegation_id = delegation.delegation_id_non_null(&output.output_id); - if delegation_ids.contains(&delegation_id) { - filtered_outputs.push(output.clone()); - continue; - } - } - } - _ => {} - } - - // TODO filter based on slot index - // if let Some(lower_bound_booked_timestamp) = filter.lower_bound_booked_timestamp { - // if output.metadata.milestone_timestamp_booked() < lower_bound_booked_timestamp { - // continue; - // } - // } - // if let Some(upper_bound_booked_timestamp) = filter.upper_bound_booked_timestamp { - // if output.metadata.milestone_timestamp_booked() > upper_bound_booked_timestamp { - // continue; - // } - // } - - if let Some(output_types) = &filter.output_types { - if !output_types.contains(&output.output.kind()) { - continue; - } - } - - // Include the output if we're not filtering by IDs. - if filter.account_ids.is_none() - && filter.anchor_ids.is_none() - && filter.foundry_ids.is_none() - && filter.nft_ids.is_none() - && filter.delegation_ids.is_none() - { - filtered_outputs.push(output.clone()); - } - } - - filtered_outputs - } else { - outputs.cloned().collect() - } - } - - /// Returns outputs of the wallet. - pub async fn outputs(&self, filter: impl Into> + Send) -> Vec { - self.filter_outputs(self.data().await.outputs.values(), filter) - } - - /// Returns unspent outputs of the wallet. - pub async fn unspent_outputs(&self, filter: impl Into> + Send) -> Vec { - self.filter_outputs(self.data().await.unspent_outputs.values(), filter) - } - - /// Gets the unspent account output matching the given ID. - pub async fn unspent_account_output(&self, account_id: &AccountId) -> Option { - self.unspent_outputs(FilterOptions { - account_ids: Some([*account_id].into()), - ..Default::default() - }) - .await - .first() - .cloned() - } - - /// Gets the unspent anchor output matching the given ID. - pub async fn unspent_anchor_output(&self, anchor_id: &AnchorId) -> Option { - self.unspent_outputs(FilterOptions { - anchor_ids: Some([*anchor_id].into()), - ..Default::default() - }) - .await - .first() - .cloned() - } - - /// Gets the unspent foundry output matching the given ID. - pub async fn unspent_foundry_output(&self, foundry_id: &FoundryId) -> Option { - self.unspent_outputs(FilterOptions { - foundry_ids: Some([*foundry_id].into()), - ..Default::default() - }) - .await - .first() - .cloned() - } - - /// Gets the unspent nft output matching the given ID. - pub async fn unspent_nft_output(&self, nft_id: &NftId) -> Option { - self.unspent_outputs(FilterOptions { - nft_ids: Some([*nft_id].into()), - ..Default::default() - }) - .await - .first() - .cloned() - } - - /// Gets the unspent delegation output matching the given ID. - pub async fn unspent_delegation_output(&self, delegation_id: &DelegationId) -> Option { - self.unspent_outputs(FilterOptions { - delegation_ids: Some([*delegation_id].into()), - ..Default::default() - }) - .await - .first() - .cloned() - } - - /// Returns implicit accounts of the wallet. - pub async fn implicit_accounts(&self) -> Vec { - self.data() - .await - .unspent_outputs - .values() - .filter(|output_data| output_data.output.is_implicit_account()) - .cloned() - .collect() - } - - /// Returns all incoming transactions of the wallet - pub async fn incoming_transactions(&self) -> Vec { - self.data().await.incoming_transactions.values().cloned().collect() - } - - /// Returns all transactions of the wallet - pub async fn transactions(&self) -> Vec { - self.data().await.transactions.values().cloned().collect() - } - - /// Returns all pending transactions of the wallet - pub async fn pending_transactions(&self) -> Vec { - let mut transactions = Vec::new(); - let wallet_data = self.data().await; - - for transaction_id in &wallet_data.pending_transactions { - if let Some(transaction) = wallet_data.transactions.get(transaction_id) { - transactions.push(transaction.clone()); - } - } - - transactions - } } impl WalletInner { diff --git a/sdk/src/wallet/operations/participation/mod.rs b/sdk/src/wallet/operations/participation/mod.rs index cd7154580d..58d4a7588e 100644 --- a/sdk/src/wallet/operations/participation/mod.rs +++ b/sdk/src/wallet/operations/participation/mod.rs @@ -71,17 +71,14 @@ where "[get_participation_overview] restored_spent_cached_outputs_len: {}", restored_spent_cached_outputs_len ); - let outputs = self.outputs(None).await; - let participation_outputs = outputs - .into_iter() - .filter(|output_data| { - is_valid_participation_output(&output_data.output) + let wallet_data = self.data().await; + let participation_outputs = wallet_data.outputs().values().filter(|output_data| { + is_valid_participation_output(&output_data.output) // Check that the metadata exists, because otherwise we aren't participating for anything && output_data.output.features().and_then(|f| f.metadata()).is_some() // Don't add spent cached outputs, we have their data already and it can't change anymore && !spent_cached_outputs.contains_key(&output_data.output_id) - }) - .collect::>(); + }); let mut events = HashMap::new(); let mut spent_outputs = HashSet::new(); @@ -229,9 +226,10 @@ where pub async fn get_voting_output(&self) -> Result> { log::debug!("[get_voting_output]"); Ok(self - .unspent_outputs(None) + .data() .await - .iter() + .unspent_outputs() + .values() .filter(|output_data| is_valid_participation_output(&output_data.output)) .max_by_key(|output_data| output_data.output.amount()) .cloned()) diff --git a/sdk/src/wallet/operations/syncing/mod.rs b/sdk/src/wallet/operations/syncing/mod.rs index abbbbb4d4e..7c7e9da05f 100644 --- a/sdk/src/wallet/operations/syncing/mod.rs +++ b/sdk/src/wallet/operations/syncing/mod.rs @@ -98,11 +98,12 @@ where let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { address: self.address().await, output_ids: self - .unspent_outputs(None) + .data() .await - .into_iter() + .unspent_outputs() + .values() .map(|data| data.output_id) - .collect::>(), + .collect(), internal: false, key_index: 0, }; diff --git a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs index 11e045c813..da441e0e0b 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs @@ -95,7 +95,6 @@ where { log::debug!("[TRANSACTION] prepare_send_nft"); - let unspent_outputs = self.unspent_outputs(None).await; let token_supply = self.client().get_token_supply().await?; let mut outputs = Vec::new(); @@ -104,7 +103,7 @@ where self.client().bech32_hrp_matches(address.hrp()).await?; // Find nft output from the inputs - if let Some(nft_output_data) = unspent_outputs.iter().find(|o| { + if let Some(nft_output_data) = self.data().await.unspent_outputs().values().find(|o| { if let Output::Nft(nft_output) = &o.output { nft_id == nft_output.nft_id_non_null(&o.output_id) } else { diff --git a/sdk/src/wallet/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs index 3c0507d4c5..808daed5cb 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -253,7 +253,7 @@ where ) } else { // Transition an existing NFT output - let unspent_nft_output = self.unspent_nft_output(nft_id).await; + let unspent_nft_output = self.data().await.unspent_nft_output(nft_id).cloned(); // Find nft output from the inputs let mut first_output_builder = if let Some(nft_output_data) = &unspent_nft_output { diff --git a/sdk/tests/wallet/consolidation.rs b/sdk/tests/wallet/consolidation.rs index 64b92e0753..b428c2377c 100644 --- a/sdk/tests/wallet/consolidation.rs +++ b/sdk/tests/wallet/consolidation.rs @@ -31,7 +31,7 @@ async fn consolidation() -> Result<()> { let balance = wallet_1.sync(None).await.unwrap(); assert_eq!(balance.base_coin().available(), 10 * amount); - assert_eq!(wallet_1.unspent_outputs(None).await.len(), 10); + assert_eq!(wallet_1.data().await.unspent_outputs().len(), 10); let tx = wallet_1 .consolidate_outputs(ConsolidationParams::new().with_force(true)) @@ -44,7 +44,7 @@ async fn consolidation() -> Result<()> { // Balance still the same assert_eq!(balance.base_coin().available(), 10 * amount); // Only one unspent output - assert_eq!(wallet_1.unspent_outputs(None).await.len(), 1); + assert_eq!(wallet_1.data().await.unspent_outputs().len(), 1); tear_down(storage_path_0)?; tear_down(storage_path_1)?; diff --git a/sdk/tests/wallet/output_preparation.rs b/sdk/tests/wallet/output_preparation.rs index 38063b95db..f96df60c52 100644 --- a/sdk/tests/wallet/output_preparation.rs +++ b/sdk/tests/wallet/output_preparation.rs @@ -766,14 +766,22 @@ async fn prepare_output_only_single_nft() -> Result<()> { let balance = wallet_1.sync(None).await?; assert_eq!(balance.nfts().len(), 1); - let nft_data = &wallet_1.unspent_outputs(None).await[0]; + let nft_amount = wallet_1 + .data() + .await + .unspent_outputs() + .values() + .next() + .unwrap() + .output + .amount(); let nft_id = *balance.nfts().first().unwrap(); // Send NFT back to first wallet let output = wallet_1 .prepare_output( OutputParams { recipient_address: wallet_0_address, - amount: nft_data.output.amount(), + amount: nft_amount, assets: Some(Assets { native_tokens: None, nft_id: Some(nft_id), From 4e7324cec24b0f94eb552a31a8d4a492e8b71547 Mon Sep 17 00:00:00 2001 From: Alex Coats Date: Thu, 30 Nov 2023 12:42:29 -0500 Subject: [PATCH 2/2] PR suggestions --- bindings/core/src/method_handler/wallet.rs | 2 ++ cli/src/wallet_cli/mod.rs | 6 +++--- .../how_tos/accounts_and_addresses/list_transactions.rs | 8 ++++---- sdk/src/wallet/core/mod.rs | 8 ++++---- sdk/src/wallet/operations/syncing/mod.rs | 8 +------- .../wallet/operations/transaction/high_level/send_nft.rs | 8 +------- 6 files changed, 15 insertions(+), 25 deletions(-) diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index f3779d0288..efe5f13a48 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -228,6 +228,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .data() .await .incoming_transactions() + .values() .map(TransactionWithMetadataDto::from) .collect(), ), @@ -409,6 +410,7 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM .data() .await .transactions() + .values() .map(TransactionWithMetadataDto::from) .collect(), ), diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 9bbd4ed6fe..e23a7ce614 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -823,9 +823,9 @@ pub async fn sync_command(wallet: &Wallet) -> Result<(), Error> { pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) -> Result<(), Error> { let wallet_data = wallet.data().await; let transaction = match selector { - TransactionSelector::Id(id) => wallet_data.transactions().find(|tx| tx.transaction_id == id), + TransactionSelector::Id(id) => wallet_data.get_transaction(&id), TransactionSelector::Index(index) => { - let mut transactions = wallet_data.transactions().collect::>(); + let mut transactions = wallet_data.transactions().values().collect::>(); transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); transactions.into_iter().nth(index) } @@ -843,7 +843,7 @@ pub async fn transaction_command(wallet: &Wallet, selector: TransactionSelector) /// `transactions` command pub async fn transactions_command(wallet: &Wallet, show_details: bool) -> Result<(), Error> { let wallet_data = wallet.data().await; - let mut transactions = wallet_data.transactions().collect::>(); + let mut transactions = wallet_data.transactions().values().collect::>(); transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); if transactions.is_empty() { diff --git a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs index faca71ca93..08823fbaa5 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs @@ -33,14 +33,14 @@ async fn main() -> Result<()> { // Print transaction ids println!("Sent transactions:"); - for transaction in wallet.data().await.transactions() { - println!("{}", transaction.transaction_id); + for transaction_id in wallet.data().await.transactions().keys() { + println!("{}", transaction_id); } // Print received transaction ids println!("Received transactions:"); - for transaction in wallet.data().await.incoming_transactions() { - println!("{}", transaction.transaction_id); + for transaction_id in wallet.data().await.incoming_transactions().keys() { + println!("{}", transaction_id); } Ok(()) diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index bd504d6681..661d2feb2d 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -331,13 +331,13 @@ impl WalletData { } /// Returns all incoming transactions of the wallet - pub fn incoming_transactions(&self) -> impl Iterator { - self.incoming_transactions.values() + pub fn incoming_transactions(&self) -> &HashMap { + &self.incoming_transactions } /// Returns all transactions of the wallet - pub fn transactions(&self) -> impl Iterator { - self.transactions.values() + pub fn transactions(&self) -> &HashMap { + &self.transactions } /// Returns all pending transactions of the wallet diff --git a/sdk/src/wallet/operations/syncing/mod.rs b/sdk/src/wallet/operations/syncing/mod.rs index 029fcd28b1..464ce251ab 100644 --- a/sdk/src/wallet/operations/syncing/mod.rs +++ b/sdk/src/wallet/operations/syncing/mod.rs @@ -97,13 +97,7 @@ where let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { address: self.address().await, - output_ids: self - .data() - .await - .unspent_outputs() - .values() - .map(|data| data.output_id) - .collect(), + output_ids: self.data().await.unspent_outputs().keys().copied().collect(), internal: false, key_index: 0, }; diff --git a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs index fe255c5b7d..cc49132275 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs @@ -101,13 +101,7 @@ where self.client().bech32_hrp_matches(address.hrp()).await?; // Find nft output from the inputs - if let Some(nft_output_data) = self.data().await.unspent_outputs().values().find(|o| { - if let Output::Nft(nft_output) = &o.output { - nft_id == nft_output.nft_id_non_null(&o.output_id) - } else { - false - } - }) { + if let Some(nft_output_data) = self.data().await.unspent_nft_output(&nft_id) { if let Output::Nft(nft_output) = &nft_output_data.output { // Set the nft id and new address unlock condition let nft_builder = NftOutputBuilder::from(nft_output)