use std::collections::{BTreeSet, HashMap};
use solana_transaction_status::{
EncodedConfirmedTransactionWithStatusMeta, EncodedTransaction, UiMessage,
option_serializer::OptionSerializer,
};
use crate::output::{SolChange, TokenChange, Transaction};
pub fn transaction_from_encoded(
confirmed: EncodedConfirmedTransactionWithStatusMeta,
signature: &str,
slot: u64,
) -> Option<Transaction> {
let block_time = confirmed.block_time.map(|t| t as u64);
let inner = confirmed.transaction;
let meta = inner.meta.as_ref()?;
let error = meta.err.as_ref().map(|e| e.to_string());
let static_keys: Vec<String> = match &inner.transaction {
EncodedTransaction::Json(ui_tx) => match &ui_tx.message {
UiMessage::Raw(raw) => raw.account_keys.clone(),
UiMessage::Parsed(parsed) => parsed
.account_keys
.iter()
.map(|k| k.pubkey.clone())
.collect(),
},
_ => return None,
};
let mut keys = static_keys;
if let OptionSerializer::Some(addresses) = &meta.loaded_addresses {
keys.extend(addresses.writable.iter().cloned());
keys.extend(addresses.readonly.iter().cloned());
}
let sol_changes: Vec<SolChange> = keys
.iter()
.enumerate()
.filter_map(|(i, pubkey)| {
let pre = *meta.pre_balances.get(i)?;
let post = *meta.post_balances.get(i)?;
if pre == post {
return None;
}
Some(SolChange {
pubkey: pubkey.clone(),
pre_lamports: pre,
post_lamports: post,
})
})
.collect();
let empty = Vec::new();
let pre_tokens = match &meta.pre_token_balances {
OptionSerializer::Some(v) => v,
_ => &empty,
};
let post_tokens = match &meta.post_token_balances {
OptionSerializer::Some(v) => v,
_ => &empty,
};
let mut pre_map: HashMap<u8, (u64, String, String, u8)> = HashMap::new();
for t in pre_tokens {
let amount = t.ui_token_amount.amount.parse::<u64>().unwrap_or(0);
let owner = match &t.owner {
OptionSerializer::Some(o) => o.clone(),
_ => String::new(),
};
pre_map.insert(
t.account_index,
(amount, t.mint.clone(), owner, t.ui_token_amount.decimals),
);
}
let mut post_map: HashMap<u8, (u64, String, String, u8)> = HashMap::new();
for t in post_tokens {
let amount = t.ui_token_amount.amount.parse::<u64>().unwrap_or(0);
let owner = match &t.owner {
OptionSerializer::Some(o) => o.clone(),
_ => String::new(),
};
post_map.insert(
t.account_index,
(amount, t.mint.clone(), owner, t.ui_token_amount.decimals),
);
}
let mut all_indices: BTreeSet<u8> = BTreeSet::new();
all_indices.extend(pre_map.keys().copied());
all_indices.extend(post_map.keys().copied());
let token_changes: Vec<TokenChange> = all_indices
.into_iter()
.filter_map(|idx| {
let pubkey = keys.get(idx as usize)?.clone();
let (pre_amount, mint, owner, decimals) = if let Some(info) = pre_map.get(&idx) {
(info.0, info.1.clone(), info.2.clone(), info.3)
} else {
let info = post_map.get(&idx)?;
(0, info.1.clone(), info.2.clone(), info.3)
};
let post_amount = post_map.get(&idx).map(|i| i.0).unwrap_or(0);
if pre_amount == post_amount {
return None;
}
Some(TokenChange {
pubkey,
mint,
owner,
pre_amount,
post_amount,
decimals,
})
})
.collect();
Some(Transaction {
slot,
timestamp: block_time,
signature: signature.to_string(),
success: error.is_none(),
error,
sol_changes,
token_changes,
logs: Vec::new(),
account_diffs: Vec::new(),
})
}