1use crate::{Error, GoldRushClient};
2use crate::models::transactions::{TransactionsResponse, TransactionResponse};
3use reqwest::Method;
4
5#[derive(Debug, Clone, Default)]
7pub struct TxOptions {
8 pub page_number: Option<u32>,
10
11 pub page_size: Option<u32>,
13
14 pub quote_currency: Option<String>,
16
17 pub with_log_events: Option<bool>,
19
20 pub starting_block: Option<u64>,
22
23 pub ending_block: Option<u64>,
25}
26
27impl TxOptions {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn page_number(mut self, page: u32) -> Self {
35 self.page_number = Some(page);
36 self
37 }
38
39 pub fn page_size(mut self, size: u32) -> Self {
41 self.page_size = Some(size);
42 self
43 }
44
45 pub fn quote_currency<S: Into<String>>(mut self, currency: S) -> Self {
47 self.quote_currency = Some(currency.into());
48 self
49 }
50
51 pub fn with_log_events(mut self, include_logs: bool) -> Self {
53 self.with_log_events = Some(include_logs);
54 self
55 }
56
57 pub fn starting_block(mut self, block: u64) -> Self {
59 self.starting_block = Some(block);
60 self
61 }
62
63 pub fn ending_block(mut self, block: u64) -> Self {
65 self.ending_block = Some(block);
66 self
67 }
68}
69
70impl GoldRushClient {
71 pub async fn get_all_transactions_for_address(
100 &self,
101 chain_name: &str,
102 address: &str,
103 options: Option<TxOptions>,
104 ) -> Result<TransactionsResponse, Error> {
105 let path = format!("/v1/{}/address/{}/transactions_v2/", chain_name, address);
107
108 let mut builder = self.build_request(Method::GET, &path);
109
110 if let Some(opts) = options {
112 if let Some(page_num) = opts.page_number {
113 builder = builder.query(&[("page-number", page_num.to_string())]);
114 }
115 if let Some(page_sz) = opts.page_size {
116 builder = builder.query(&[("page-size", page_sz.to_string())]);
117 }
118 if let Some(currency) = opts.quote_currency {
119 builder = builder.query(&[("quote-currency", currency)]);
120 }
121 if let Some(with_logs) = opts.with_log_events {
122 builder = builder.query(&[("with-log-events", with_logs.to_string())]);
123 }
124 if let Some(start_block) = opts.starting_block {
125 builder = builder.query(&[("starting-block", start_block.to_string())]);
126 }
127 if let Some(end_block) = opts.ending_block {
128 builder = builder.query(&[("ending-block", end_block.to_string())]);
129 }
130 }
131
132 self.send_with_retry(builder).await
133 }
134
135 pub async fn get_transaction(
158 &self,
159 chain_name: &str,
160 tx_hash: &str,
161 ) -> Result<TransactionResponse, Error> {
162 let path = format!("/v1/{}/transaction_v2/{}/", chain_name, tx_hash);
164
165 let builder = self.build_request(Method::GET, &path);
166 self.send_with_retry(builder).await
167 }
168
169 pub async fn get_transactions_between_addresses(
196 &self,
197 chain_name: &str,
198 from_address: &str,
199 to_address: &str,
200 options: Option<TxOptions>,
201 ) -> Result<TransactionsResponse, Error> {
202 let path = format!(
204 "/v1/{}/bulk/transactions/{}/{}/",
205 chain_name,
206 from_address,
207 to_address
208 );
209
210 let mut builder = self.build_request(Method::GET, &path);
211
212 if let Some(opts) = options {
213 if let Some(page_num) = opts.page_number {
214 builder = builder.query(&[("page-number", page_num.to_string())]);
215 }
216 if let Some(page_sz) = opts.page_size {
217 builder = builder.query(&[("page-size", page_sz.to_string())]);
218 }
219 if let Some(currency) = opts.quote_currency {
220 builder = builder.query(&[("quote-currency", currency)]);
221 }
222 }
223
224 self.send_with_retry(builder).await
225 }
226}
227
228pub struct TransactionsPageIter<'a> {
230 client: &'a GoldRushClient,
231 chain_name: String,
232 address: String,
233 options: TxOptions,
234 finished: bool,
235}
236
237impl<'a> TransactionsPageIter<'a> {
238 pub fn new<C: Into<String>, A: Into<String>>(
240 client: &'a GoldRushClient,
241 chain_name: C,
242 address: A,
243 options: TxOptions,
244 ) -> Self {
245 Self {
246 client,
247 chain_name: chain_name.into(),
248 address: address.into(),
249 options,
250 finished: false,
251 }
252 }
253
254 pub async fn next(
256 &mut self,
257 ) -> Result<Option<Vec<crate::models::transactions::TransactionItem>>, Error> {
258 if self.finished {
259 return Ok(None);
260 }
261
262 let resp = self
263 .client
264 .get_all_transactions_for_address(&self.chain_name, &self.address, Some(self.options.clone()))
265 .await?;
266
267 if let Some(data) = resp.data {
268 let items = data.items;
269 if items.is_empty() || !resp.pagination.as_ref().and_then(|p| p.has_more).unwrap_or(false) {
270 self.finished = true;
271 } else if let Some(pagination) = resp.pagination {
272 if let Some(next_page) = pagination.page_number.map(|n| n + 1) {
273 self.options.page_number = Some(next_page);
274 } else {
275 self.finished = true;
276 }
277 }
278 Ok(Some(items))
279 } else {
280 self.finished = true;
281 Ok(None)
282 }
283 }
284
285 pub fn has_more(&self) -> bool {
287 !self.finished
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294 use serde_json::json;
295
296 #[test]
297 fn test_tx_options_builder() {
298 let options = TxOptions::new()
299 .page_size(25)
300 .quote_currency("USD")
301 .with_log_events(true)
302 .starting_block(18000000);
303
304 assert_eq!(options.page_size, Some(25));
305 assert_eq!(options.quote_currency, Some("USD".to_string()));
306 assert_eq!(options.with_log_events, Some(true));
307 assert_eq!(options.starting_block, Some(18000000));
308 }
309
310 #[test]
311 fn test_deserialize_transaction_response() {
312 let json_data = json!({
313 "data": {
314 "tx_hash": "0x123abc...",
315 "from_address": "0xfrom...",
316 "to_address": "0xto...",
317 "value": "1000000000000000000",
318 "successful": true,
319 "block_height": 18500000,
320 "gas_used": 21000,
321 "fees_paid": "420000000000000"
322 },
323 "error": null
324 });
325
326 let response: TransactionResponse = serde_json::from_value(json_data).unwrap();
327 assert!(response.data.is_some());
328
329 let tx = response.data.unwrap();
330 assert_eq!(tx.tx_hash, "0x123abc...");
331 assert_eq!(tx.successful, Some(true));
332 assert_eq!(tx.block_height, Some(18500000));
333 }
334
335 #[test]
336 fn test_deserialize_transactions_response() {
337 let json_data = json!({
338 "data": {
339 "address": "0x123",
340 "chain_id": 1,
341 "items": [{
342 "tx_hash": "0x123abc...",
343 "from_address": "0xfrom...",
344 "to_address": "0xto...",
345 "value": "1000000000000000000",
346 "successful": true
347 }]
348 },
349 "pagination": {
350 "has_more": true,
351 "page_number": 0,
352 "page_size": 100
353 }
354 });
355
356 let response: TransactionsResponse = serde_json::from_value(json_data).unwrap();
357 assert!(response.data.is_some());
358
359 let data = response.data.unwrap();
360 assert_eq!(data.items.len(), 1);
361 assert_eq!(data.items[0].tx_hash, "0x123abc...");
362 }
363}