use std::collections::HashMap;
use crate::{
client::secret::SecretManage,
types::block::output::{OutputId, OutputMetadata},
wallet::account::{
operations::syncing::options::SyncOptions,
types::{address::AddressWithUnspentOutputs, InclusionState, OutputData, Transaction},
Account, AccountAddress,
},
};
#[cfg(feature = "events")]
use crate::{
types::{api::core::response::OutputWithMetadataResponse, block::payload::transaction::dto::TransactionPayloadDto},
wallet::{
account::types::OutputDataDto,
events::types::{NewOutputEvent, SpentOutputEvent, TransactionInclusionEvent, WalletEvent},
},
};
impl<S: 'static + SecretManage> Account<S>
where
crate::wallet::Error: From<S::Error>,
{
pub async fn set_alias(&self, alias: &str) -> crate::wallet::Result<()> {
let mut account_details = self.details_mut().await;
account_details.alias = alias.to_string();
#[cfg(feature = "storage")]
self.save(Some(&account_details)).await?;
Ok(())
}
pub(crate) async fn update_account(
&self,
addresses_with_unspent_outputs: Vec<AddressWithUnspentOutputs>,
unspent_outputs: Vec<OutputData>,
spent_or_unsynced_output_metadata_map: HashMap<OutputId, Option<OutputMetadata>>,
options: &SyncOptions,
) -> crate::wallet::Result<()> {
log::debug!("[SYNC] Update account with new synced transactions");
let network_id = self.client().get_network_id().await?;
let mut account_details = self.details_mut().await;
#[cfg(feature = "events")]
let account_index = account_details.index;
for address_with_unspent_outputs in addresses_with_unspent_outputs.iter() {
if address_with_unspent_outputs.internal {
let position = account_details
.internal_addresses
.binary_search_by_key(
&(
address_with_unspent_outputs.key_index,
address_with_unspent_outputs.internal,
),
|a| (a.key_index, a.internal),
)
.map_err(|_| {
crate::wallet::Error::AddressNotFoundInAccount(address_with_unspent_outputs.address)
})?;
account_details.internal_addresses[position].used = true;
} else {
let position = account_details
.public_addresses
.binary_search_by_key(
&(
address_with_unspent_outputs.key_index,
address_with_unspent_outputs.internal,
),
|a| (a.key_index, a.internal),
)
.map_err(|_| {
crate::wallet::Error::AddressNotFoundInAccount(address_with_unspent_outputs.address)
})?;
account_details.public_addresses[position].used = true;
}
}
account_details.addresses_with_unspent_outputs.retain(|a| {
if a.internal {
a.key_index < options.address_start_index_internal
} else {
a.key_index < options.address_start_index
}
});
account_details
.addresses_with_unspent_outputs
.extend(addresses_with_unspent_outputs);
for (output_id, output_metadata_response_opt) in spent_or_unsynced_output_metadata_map {
if let Some(output_metadata_response) = output_metadata_response_opt {
if output_metadata_response.is_spent() {
account_details.unspent_outputs.remove(&output_id);
if let Some(output_data) = account_details.outputs.get_mut(&output_id) {
output_data.metadata = output_metadata_response;
}
} else {
continue;
}
}
if let Some(output) = account_details.outputs.get(&output_id) {
if output.network_id == network_id {
log::debug!("[SYNC] Spent output {}", output_id);
account_details.locked_outputs.remove(&output_id);
account_details.unspent_outputs.remove(&output_id);
if let Some(output_data) = account_details.outputs.get_mut(&output_id) {
output_data.metadata.set_spent(true);
output_data.is_spent = true;
#[cfg(feature = "events")]
{
self.emit(
account_index,
WalletEvent::SpentOutput(Box::new(SpentOutputEvent {
output: OutputDataDto::from(&*output_data),
})),
)
.await;
}
}
}
}
}
for output_data in unspent_outputs {
if account_details
.outputs
.insert(output_data.output_id, output_data.clone())
.is_none()
{
#[cfg(feature = "events")]
{
let transaction = account_details
.incoming_transactions
.get(output_data.output_id.transaction_id());
self.emit(
account_index,
WalletEvent::NewOutput(Box::new(NewOutputEvent {
output: OutputDataDto::from(&output_data),
transaction: transaction.as_ref().map(|tx| TransactionPayloadDto::from(&tx.payload)),
transaction_inputs: transaction.as_ref().map(|tx| {
tx.inputs
.clone()
.into_iter()
.map(OutputWithMetadataResponse::from)
.collect()
}),
})),
)
.await;
}
};
if !output_data.is_spent {
account_details
.unspent_outputs
.insert(output_data.output_id, output_data);
}
}
#[cfg(feature = "storage")]
{
log::debug!(
"[SYNC] storing account {} with new synced data",
account_details.alias()
);
self.save(Some(&account_details)).await?;
}
Ok(())
}
pub(crate) async fn update_account_with_transactions(
&self,
updated_transactions: Vec<Transaction>,
spent_output_ids: Vec<OutputId>,
output_ids_to_unlock: Vec<OutputId>,
) -> crate::wallet::Result<()> {
log::debug!("[SYNC] Update account with new synced transactions");
let mut account_details = self.details_mut().await;
for transaction in updated_transactions {
match transaction.inclusion_state {
InclusionState::Confirmed | InclusionState::Conflicting | InclusionState::UnknownPruned => {
let transaction_id = transaction.payload.id();
account_details.pending_transactions.remove(&transaction_id);
log::debug!(
"[SYNC] inclusion_state of {transaction_id} changed to {:?}",
transaction.inclusion_state
);
#[cfg(feature = "events")]
{
self.emit(
account_details.index,
WalletEvent::TransactionInclusion(TransactionInclusionEvent {
transaction_id,
inclusion_state: transaction.inclusion_state,
}),
)
.await;
}
}
_ => {}
}
account_details
.transactions
.insert(transaction.payload.id(), transaction.clone());
}
for output_to_unlock in &spent_output_ids {
if let Some(output) = account_details.outputs.get_mut(output_to_unlock) {
output.is_spent = true;
}
account_details.locked_outputs.remove(output_to_unlock);
account_details.unspent_outputs.remove(output_to_unlock);
log::debug!("[SYNC] Unlocked spent output {}", output_to_unlock);
}
for output_to_unlock in &output_ids_to_unlock {
account_details.locked_outputs.remove(output_to_unlock);
log::debug!(
"[SYNC] Unlocked unspent output {} because of a conflicting transaction",
output_to_unlock
);
}
#[cfg(feature = "storage")]
{
log::debug!(
"[SYNC] storing account {} with new synced transactions",
account_details.alias()
);
self.save(Some(&account_details)).await?;
}
Ok(())
}
pub(crate) async fn update_account_addresses(
&self,
internal: bool,
new_addresses: Vec<AccountAddress>,
) -> crate::wallet::Result<()> {
log::debug!("[update_account_addresses]");
let mut account_details = self.details_mut().await;
if internal {
account_details.internal_addresses.extend(new_addresses);
} else {
account_details.public_addresses.extend(new_addresses);
};
#[cfg(feature = "storage")]
{
log::debug!("[update_account_addresses] storing account {}", account_details.index());
self.save(Some(&account_details)).await?;
}
Ok(())
}
pub(crate) async fn update_account_bech32_hrp(&mut self) -> crate::wallet::Result<()> {
let bech32_hrp = self.client().get_bech32_hrp().await?;
log::debug!("[UPDATE ACCOUNT WITH BECH32 HRP] new bech32_hrp: {}", bech32_hrp);
let mut account_details = self.details_mut().await;
for address in &mut account_details.addresses_with_unspent_outputs {
address.address.hrp = bech32_hrp;
}
for address in &mut account_details.public_addresses {
address.address.hrp = bech32_hrp;
}
for address in &mut account_details.internal_addresses {
address.address.hrp = bech32_hrp;
}
account_details.inaccessible_incoming_transactions.clear();
#[cfg(feature = "storage")]
{
log::debug!(
"[SYNC] storing account {} after updating it with new bech32 hrp",
account_details.alias()
);
self.save(Some(&account_details)).await?;
}
Ok(())
}
}