1use crate::client::RpcClient;
2use crate::error::{CirclesRpcError, Result};
3use crate::methods::QueryMethods;
4use crate::paged_query::{PagedFetch, PagedQuery};
5use circles_types::{
6 Address, Conjunction, FilterPredicate, PagedQueryParams, PagedResult, SortOrder,
7 TransactionHistoryRow, U256,
8};
9use circles_utils::converter::{
10 atto_circles_to_atto_crc, atto_circles_to_atto_static_circles, atto_circles_to_circles,
11};
12use serde::{Deserialize, Serialize};
13use std::pin::Pin;
14use std::str::FromStr;
15use std::sync::Arc;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18#[serde(rename_all = "camelCase")]
19struct RawTransactionHistoryRow {
20 block_number: u64,
21 timestamp: u64,
22 transaction_index: u32,
23 log_index: u32,
24 transaction_hash: alloy_primitives::TxHash,
25 version: u32,
26 from: Address,
27 to: Address,
28 id: String,
29 token_address: Address,
30 value: String,
31}
32
33fn enrich_transaction_row(row: RawTransactionHistoryRow) -> Result<TransactionHistoryRow> {
34 let atto_circles =
35 U256::from_str(&row.value).map_err(|e| CirclesRpcError::InvalidResponse {
36 message: format!("invalid transfer summary value: {e}"),
37 })?;
38 let circles = atto_circles_to_circles(atto_circles);
39 let atto_crc = atto_circles_to_atto_crc(atto_circles, row.timestamp);
40 let crc = atto_circles_to_circles(atto_crc);
41 let static_atto_circles =
42 atto_circles_to_atto_static_circles(atto_circles, Some(row.timestamp));
43 let static_circles = atto_circles_to_circles(static_atto_circles);
44
45 Ok(TransactionHistoryRow {
46 block_number: row.block_number,
47 timestamp: row.timestamp,
48 transaction_index: row.transaction_index,
49 log_index: row.log_index,
50 transaction_hash: row.transaction_hash,
51 version: row.version,
52 from: row.from,
53 to: row.to,
54 id: row.id,
55 token_address: row.token_address,
56 value: row.value,
57 circles: Some(circles),
58 atto_circles: Some(atto_circles),
59 static_circles: Some(static_circles),
60 static_atto_circles: Some(static_atto_circles),
61 crc: Some(crc),
62 atto_crc: Some(atto_crc),
63 })
64}
65
66#[derive(Clone, Debug)]
68pub struct TransactionMethods {
69 client: RpcClient,
70}
71
72impl TransactionMethods {
73 pub fn new(client: RpcClient) -> Self {
75 Self { client }
76 }
77
78 pub fn get_transaction_history(
80 &self,
81 avatar: Address,
82 limit: u32,
83 sort_order: SortOrder,
84 ) -> PagedQuery<TransactionHistoryRow> {
85 let params = PagedQueryParams {
86 namespace: "V_Crc".into(),
87 table: "TransferSummary".into(),
88 sort_order,
89 columns: vec![
90 "blockNumber".into(),
91 "timestamp".into(),
92 "transactionIndex".into(),
93 "logIndex".into(),
94 "transactionHash".into(),
95 "version".into(),
96 "from".into(),
97 "to".into(),
98 "id".into(),
99 "tokenAddress".into(),
100 "value".into(),
101 ],
102 filter: Some(vec![
103 Conjunction::and(vec![
104 FilterPredicate::equals("version".into(), 2).into(),
105 Conjunction::or(vec![
106 FilterPredicate::equals("from".into(), format!("{avatar:#x}")).into(),
107 FilterPredicate::equals("to".into(), format!("{avatar:#x}")).into(),
108 ])
109 .into(),
110 ])
111 .into(),
112 ]),
113 cursor_columns: None,
114 order_columns: None,
115 limit,
116 };
117
118 let client = self.client.clone();
119 let fetch: PagedFetch<TransactionHistoryRow> = Arc::new(move |params: PagedQueryParams| {
120 let client = client.clone();
121 Box::pin(async move {
122 let raw = QueryMethods::new(client)
123 .paged_query::<RawTransactionHistoryRow>(params)
124 .await?;
125 let rows = raw
126 .results
127 .into_iter()
128 .map(enrich_transaction_row)
129 .collect::<Result<Vec<_>>>()?;
130 Ok(PagedResult {
131 limit: raw.limit,
132 size: rows.len() as u32,
133 first_cursor: raw.first_cursor,
134 last_cursor: raw.last_cursor,
135 sort_order: raw.sort_order,
136 has_more: raw.has_more,
137 results: rows,
138 })
139 })
140 as Pin<
141 Box<
142 dyn std::future::Future<Output = Result<PagedResult<TransactionHistoryRow>>>
143 + Send,
144 >,
145 >
146 });
147
148 PagedQuery::new(fetch, params)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 fn methods() -> TransactionMethods {
157 let url = "https://rpc.example.com".parse().expect("valid url");
158 TransactionMethods::new(RpcClient::http(url))
159 }
160
161 #[test]
162 fn transaction_history_query_uses_transfer_summary_view() {
163 let query =
164 methods().get_transaction_history(Address::repeat_byte(0x11), 50, SortOrder::DESC);
165
166 assert_eq!(query.params.namespace, "V_Crc");
167 assert_eq!(query.params.table, "TransferSummary");
168 assert_eq!(query.params.limit, 50);
169 }
170
171 #[test]
172 fn enrich_transaction_row_populates_amount_fields() {
173 let row = enrich_transaction_row(RawTransactionHistoryRow {
174 block_number: 1,
175 timestamp: 1_700_000_000,
176 transaction_index: 2,
177 log_index: 3,
178 transaction_hash: alloy_primitives::TxHash::ZERO,
179 version: 2,
180 from: Address::repeat_byte(0x11),
181 to: Address::repeat_byte(0x22),
182 id: "1".into(),
183 token_address: Address::repeat_byte(0x33),
184 value: "1000000000000000000".into(),
185 })
186 .expect("enrich");
187
188 assert_eq!(
189 row.atto_circles,
190 Some(U256::from(1_000_000_000_000_000_000u64))
191 );
192 assert!(row.circles.expect("circles") > 0.0);
193 assert!(row.static_circles.expect("static circles") > 0.0);
194 assert!(row.crc.expect("crc") > 0.0);
195 assert!(row.static_atto_circles.expect("static atto") > U256::ZERO);
196 assert!(row.atto_crc.expect("atto crc") > U256::ZERO);
197 }
198}