1use alloy_primitives::{Address, B256, keccak256};
2use alloy_provider::Provider;
3use alloy_rpc_types::{BlockNumberOrTag, Filter};
4use eyre::Result;
5use foundry_cli::{opts::RpcOpts, utils::LoadConfig};
6use foundry_common::{provider::ProviderBuilder, shell};
7use serde_json::json;
8use std::sync::LazyLock;
9use tempo_alloy::TempoNetwork;
10use tempo_primitives::TempoAddressExt;
11
12static TRANSFER_TOPIC: LazyLock<B256> =
13 LazyLock::new(|| keccak256(b"Transfer(address,address,uint256)"));
14
15pub(super) async fn run(
16 addr: Address,
17 token: Option<Address>,
18 from_block: Option<u64>,
19 rpc: RpcOpts,
20) -> Result<()> {
21 if !addr.is_virtual() {
22 eyre::bail!("{addr} is not a virtual address");
23 }
24
25 let config = rpc.load_config()?;
26 let provider = ProviderBuilder::<TempoNetwork>::from_config(&config)?.build()?;
27
28 let to_topic: B256 = {
31 let mut buf = [0u8; 32];
32 buf[12..].copy_from_slice(addr.as_slice());
33 buf.into()
34 };
35
36 let start = from_block.map(BlockNumberOrTag::Number).unwrap_or(BlockNumberOrTag::Latest);
37
38 let mut filter =
39 Filter::new().event_signature(*TRANSFER_TOPIC).topic2(to_topic).from_block(start);
40
41 if let Some(tok) = token {
42 filter = filter.address(tok);
43 }
44
45 if !shell::is_json() {
46 sh_println!("Watching transfers to {addr}... (Ctrl-C to stop)")?;
47 }
48
49 let logs = provider.get_logs(&filter).await?;
51 for log in &logs {
52 print_transfer_log(log)?;
53 }
54
55 let mut last_block = provider.get_block_number().await?;
57 loop {
58 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
59 let current = provider.get_block_number().await?;
60 if current > last_block {
61 let poll_filter = filter.clone().from_block(last_block + 1).to_block(current);
62 let new_logs = provider.get_logs(&poll_filter).await?;
63 for log in &new_logs {
64 print_transfer_log(log)?;
65 }
66 last_block = current;
67 }
68 }
69}
70
71fn print_transfer_log(log: &alloy_rpc_types::Log) -> Result<()> {
72 let block = log.block_number.unwrap_or(0);
73 let tx = log.transaction_hash.unwrap_or_default();
74 let token = log.address();
75
76 let from = log.topics().get(1).map(|t| {
78 let mut addr = [0u8; 20];
79 addr.copy_from_slice(&t[12..]);
80 Address::from(addr)
81 });
82
83 let amount = if log.data().data.len() >= 32 {
85 alloy_primitives::U256::from_be_slice(&log.data().data[..32])
86 } else {
87 alloy_primitives::U256::ZERO
88 };
89
90 if shell::is_json() {
91 sh_println!(
92 "{}",
93 serde_json::to_string(&json!({
94 "block": block,
95 "tx": format!("{tx}"),
96 "token": format!("{token}"),
97 "from": from.map(|a| format!("{a}")).unwrap_or_default(),
98 "amount": amount.to_string(),
99 }))?
100 )?;
101 } else {
102 sh_println!(
103 "block={block} tx={tx} token={token} from={} amount={amount}",
104 from.map(|a| a.to_string()).unwrap_or_default(),
105 )?;
106 }
107 Ok(())
108}