From 1f6ff33bae41b66b3cb3ffe9ea19ad53a080999e Mon Sep 17 00:00:00 2001 From: DaughterOfMars Date: Thu, 30 Nov 2023 15:13:25 -0500 Subject: [PATCH] Allow some wallet data to be accessible by reference (#1582) * allow some wallet data to be accessible by reference * PR suggestions --------- Co-authored-by: Thibault Martinez --- bindings/core/src/method_handler/wallet.rs | 116 +++-- cli/src/wallet_cli/mod.rs | 57 ++- .../consolidate_outputs.rs | 50 ++- .../accounts_and_addresses/list_outputs.rs | 4 +- .../list_transactions.rs | 8 +- sdk/examples/wallet/spammer.rs | 6 +- sdk/src/wallet/core/mod.rs | 416 ++++++++---------- .../wallet/operations/participation/mod.rs | 11 +- sdk/src/wallet/operations/syncing/mod.rs | 7 +- .../transaction/high_level/send_nft.rs | 10 +- .../operations/transaction/prepare_output.rs | 2 +- sdk/tests/wallet/consolidation.rs | 4 +- sdk/tests/wallet/output_preparation.rs | 12 +- 13 files changed, 357 insertions(+), 346 deletions(-) diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index ffdb7b9331..efe5f13a48 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -17,8 +17,7 @@ use crate::{method::WalletMethod, response::Response}; pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletMethod) -> crate::Result { let response = match method { WalletMethod::Accounts => { - let accounts = wallet.accounts().await; - Response::OutputsData(accounts.iter().map(OutputDataDto::from).collect()) + Response::OutputsData(wallet.data().await.accounts().map(OutputDataDto::from).collect()) } #[cfg(feature = "stronghold")] WalletMethod::Backup { destination, password } => { @@ -154,18 +153,22 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let output = wallet.get_foundry_output(token_id).await?; Response::Output(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?; @@ -191,10 +194,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?; @@ -204,26 +211,43 @@ 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 => Response::OutputsData( + wallet + .data() + .await + .implicit_accounts() + .map(OutputDataDto::from) + .collect(), + ), WalletMethod::PrepareImplicitAccountTransition { output_id } => { let data = wallet.prepare_implicit_account_transition(&output_id).await?; Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) } - 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::IncomingTransactions => Response::Transactions( + wallet + .data() + .await + .incoming_transactions() + .values() + .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)) @@ -381,13 +405,29 @@ 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() + .values() + .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 9600f81abf..e23a7ce614 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -23,9 +23,8 @@ use iota_sdk::{ }, utils::ConvertTo, wallet::{ - types::{OutputData, TransactionWithMetadata}, - ConsolidationParams, CreateNativeTokenParams, MintNftParams, OutputsToClaim, SendNativeTokenParams, - SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, + types::OutputData, ConsolidationParams, CreateNativeTokenParams, MintNftParams, OutputsToClaim, + SendNativeTokenParams, SendNftParams, SendParams, SyncOptions, TransactionOptions, Wallet, }, U256, }; @@ -313,7 +312,7 @@ impl FromStr for OutputSelector { // `accounts` command pub async fn accounts_command(wallet: &Wallet) -> Result<(), Error> { - print_outputs(wallet.accounts().await, "Accounts:").await + print_outputs(wallet.data().await.accounts().cloned().collect(), "Accounts:") } // `address` command @@ -413,9 +412,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", @@ -598,7 +597,10 @@ pub async fn implicit_account_transition_command(wallet: &Wallet, output_id: Out // `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 @@ -684,11 +686,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) } }; @@ -704,7 +707,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 @@ -818,11 +821,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.get_transaction(&id), TransactionSelector::Index(index) => { - transactions.sort_unstable_by(transactions_ordering); + let mut transactions = wallet_data.transactions().values().collect::>(); + transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); transactions.into_iter().nth(index) } }; @@ -838,8 +842,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().values().collect::>(); + transactions.sort_unstable_by(|a, b| b.timestamp.cmp(&a.timestamp)); if transactions.is_empty() { println_log_info!("No transactions found"); @@ -861,7 +866,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> { @@ -950,7 +958,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(); @@ -962,7 +969,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); @@ -1216,12 +1223,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() { let kind_str = if output_data.output.is_implicit_account() { @@ -1242,11 +1249,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 9f4b013824..6280965836 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/consolidate_outputs.rs @@ -48,17 +48,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_token() - ) - }); + 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_token() + ) + }); println!("Sending consolidation transaction..."); @@ -84,17 +89,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_token() - ) - }); + 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_token() + ) + }); 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 76eb7ca140..08b416c09f 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_outputs.rs @@ -30,13 +30,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 b1b46d88ec..3b416eabad 100644 --- a/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs +++ b/sdk/examples/how_tos/accounts_and_addresses/list_transactions.rs @@ -38,14 +38,14 @@ async fn main() -> Result<()> { // Print transaction ids println!("Sent transactions:"); - for transaction in wallet.transactions().await { - 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.incoming_transactions().await { - println!("{}", transaction.transaction_id); + for transaction_id in wallet.data().await.incoming_transactions().keys() { + println!("{}", transaction_id); } Ok(()) diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index 370c1b01d8..278ffd8608 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -74,12 +74,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 158784e362..661d2feb2d 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -154,6 +154,198 @@ 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()) + } + + /// Returns accounts of the wallet. + pub fn accounts(&self) -> impl Iterator { + self.unspent_outputs + .values() + .filter(|output_data| output_data.output.is_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) -> &HashMap { + &self.incoming_transactions + } + + /// Returns all transactions of the wallet + pub fn transactions(&self) -> &HashMap { + &self.transactions + } + + /// 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 @@ -235,7 +427,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 } @@ -276,228 +468,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 accounts of the wallet. - pub async fn accounts(&self) -> Vec { - self.data() - .await - .unspent_outputs - .values() - .filter(|output_data| output_data.output.is_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 9c62becce6..481881c214 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(); diff --git a/sdk/src/wallet/operations/syncing/mod.rs b/sdk/src/wallet/operations/syncing/mod.rs index a19b66b265..464ce251ab 100644 --- a/sdk/src/wallet/operations/syncing/mod.rs +++ b/sdk/src/wallet/operations/syncing/mod.rs @@ -97,12 +97,7 @@ where let wallet_address_with_unspent_outputs = AddressWithUnspentOutputs { address: self.address().await, - output_ids: self - .unspent_outputs(None) - .await - .into_iter() - .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 33212d22a5..cc49132275 100644 --- a/sdk/src/wallet/operations/transaction/high_level/send_nft.rs +++ b/sdk/src/wallet/operations/transaction/high_level/send_nft.rs @@ -95,21 +95,13 @@ where { log::debug!("[TRANSACTION] prepare_send_nft"); - let unspent_outputs = self.unspent_outputs(None).await; - let mut outputs = Vec::new(); for SendNftParams { address, nft_id } in params { 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 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) diff --git a/sdk/src/wallet/operations/transaction/prepare_output.rs b/sdk/src/wallet/operations/transaction/prepare_output.rs index 6f4c33b38e..70d832d510 100644 --- a/sdk/src/wallet/operations/transaction/prepare_output.rs +++ b/sdk/src/wallet/operations/transaction/prepare_output.rs @@ -245,7 +245,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 4ab33ba562..9eccccaae6 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 { nft_id: Some(nft_id) }), features: None, unlocks: None,