use std::collections::HashMap;
use crate::cache::CacheStore;
use super::types::EnhancedTransaction;
pub async fn enrich_token_standards<C: CacheStore + ?Sized>(
cache: &C,
txs: &mut [EnhancedTransaction],
) {
let mut distinct_mints: Vec<String> = Vec::new();
for tx in txs.iter() {
for t in &tx.token_transfers {
if !distinct_mints.contains(&t.mint) {
distinct_mints.push(t.mint.clone());
}
}
}
if distinct_mints.is_empty() {
return;
}
let batch = cache
.get_asset_batch(&distinct_mints)
.await
.unwrap_or_else(|_| distinct_mints.iter().map(|_| None).collect());
let mut lookup: HashMap<String, String> = HashMap::new();
for (mint, entry) in distinct_mints.iter().zip(batch) {
if let Some(asset) = entry {
if let Some(ts) = asset.content.metadata.token_standard.clone() {
lookup.insert(mint.clone(), ts);
}
}
}
if lookup.is_empty() {
return;
}
for tx in txs.iter_mut() {
for t in &mut tx.token_transfers {
if t.token_standard.is_none() {
if let Some(ts) = lookup.get(&t.mint) {
t.token_standard = Some(ts.clone());
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cache::MemoryCache;
use crate::das::types::{DasAsset, DasContent, DasMetadata};
use crate::enhanced::types::{EnhancedEvents, EnhancedTokenTransfer, EnhancedTransaction};
fn tx_with_transfer(mint: &str) -> EnhancedTransaction {
EnhancedTransaction {
signature: "S".into(),
slot: 1,
timestamp: None,
tx_type: "TRANSFER".into(),
source: "SYSTEM_PROGRAM".into(),
fee: 0,
fee_payer: "F".into(),
description: String::new(),
native_transfers: vec![],
token_transfers: vec![EnhancedTokenTransfer {
from_user_account: None,
to_user_account: None,
from_token_account: None,
to_token_account: None,
mint: mint.into(),
token_amount: 1,
token_standard: None,
}],
instructions: vec![],
account_data: vec![],
events: EnhancedEvents::default(),
lighthouse_data: None,
transaction_error: None,
}
}
fn asset_with_token_standard(id: &str, ts: &str) -> DasAsset {
DasAsset {
id: id.into(),
interface: "FungibleToken".into(),
content: DasContent {
metadata: DasMetadata {
token_standard: Some(ts.into()),
..Default::default()
},
..Default::default()
},
..Default::default()
}
}
#[tokio::test]
async fn enriches_from_cache() {
let cache = MemoryCache::new();
cache
.put_asset(asset_with_token_standard("MINT_A", "Fungible"))
.await
.unwrap();
let mut txs = vec![tx_with_transfer("MINT_A")];
enrich_token_standards(&cache, &mut txs).await;
assert_eq!(
txs[0].token_transfers[0].token_standard.as_deref(),
Some("Fungible")
);
}
#[tokio::test]
async fn uncached_mints_stay_none() {
let cache = MemoryCache::new();
let mut txs = vec![tx_with_transfer("UNKNOWN_MINT")];
enrich_token_standards(&cache, &mut txs).await;
assert!(txs[0].token_transfers[0].token_standard.is_none());
}
#[tokio::test]
async fn does_not_overwrite_pre_populated_standard() {
let cache = MemoryCache::new();
cache
.put_asset(asset_with_token_standard("MINT_A", "Fungible"))
.await
.unwrap();
let mut tx = tx_with_transfer("MINT_A");
tx.token_transfers[0].token_standard = Some("ProgrammableNonFungible".into());
let mut txs = vec![tx];
enrich_token_standards(&cache, &mut txs).await;
assert_eq!(
txs[0].token_transfers[0].token_standard.as_deref(),
Some("ProgrammableNonFungible")
);
}
}