lighter_rust/api/
transaction.rs

1use crate::client::SignerClient;
2use crate::error::Result;
3use crate::models::ApiResponse;
4use chrono::{DateTime, Utc};
5use serde::{Deserialize, Serialize};
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Transaction {
9    pub id: String,
10    pub hash: String,
11    pub block_number: u64,
12    pub block_hash: String,
13    pub transaction_index: u32,
14    pub from_address: String,
15    pub to_address: Option<String>,
16    pub value: String,
17    pub gas_used: String,
18    pub gas_price: String,
19    pub status: TransactionStatus,
20    pub timestamp: DateTime<Utc>,
21    pub confirmations: u32,
22}
23
24#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
25#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
26pub enum TransactionStatus {
27    Pending,
28    Confirmed,
29    Failed,
30    Reverted,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct Block {
35    pub number: u64,
36    pub hash: String,
37    pub parent_hash: String,
38    pub timestamp: DateTime<Utc>,
39    pub transaction_count: u32,
40    pub gas_used: String,
41    pub gas_limit: String,
42    pub miner: String,
43}
44
45#[derive(Debug)]
46pub struct TransactionApi {
47    client: SignerClient,
48}
49
50impl TransactionApi {
51    pub fn new(client: SignerClient) -> Self {
52        Self { client }
53    }
54
55    pub async fn get_transaction(&self, tx_hash: &str) -> Result<Transaction> {
56        let response: ApiResponse<Transaction> = self
57            .client
58            .api_client()
59            .get(&format!("/transactions/{}", tx_hash))
60            .await?;
61
62        match response.data {
63            Some(transaction) => Ok(transaction),
64            None => Err(crate::error::LighterError::Api {
65                status: 404,
66                message: response
67                    .error
68                    .unwrap_or_else(|| "Transaction not found".to_string()),
69            }),
70        }
71    }
72
73    pub async fn get_transactions(
74        &self,
75        address: &str,
76        page: Option<u32>,
77        limit: Option<u32>,
78    ) -> Result<Vec<Transaction>> {
79        let mut query_params = vec![format!("address={}", address)];
80
81        if let Some(page) = page {
82            query_params.push(format!("page={}", page));
83        }
84        if let Some(limit) = limit {
85            query_params.push(format!("limit={}", limit));
86        }
87
88        let endpoint = format!("/transactions?{}", query_params.join("&"));
89
90        let response: ApiResponse<Vec<Transaction>> =
91            self.client.api_client().get(&endpoint).await?;
92
93        match response.data {
94            Some(transactions) => Ok(transactions),
95            None => Err(crate::error::LighterError::Api {
96                status: 500,
97                message: response
98                    .error
99                    .unwrap_or_else(|| "Failed to fetch transactions".to_string()),
100            }),
101        }
102    }
103
104    pub async fn get_block(&self, block_number: u64) -> Result<Block> {
105        let response: ApiResponse<Block> = self
106            .client
107            .api_client()
108            .get(&format!("/blocks/{}", block_number))
109            .await?;
110
111        match response.data {
112            Some(block) => Ok(block),
113            None => Err(crate::error::LighterError::Api {
114                status: 404,
115                message: response
116                    .error
117                    .unwrap_or_else(|| "Block not found".to_string()),
118            }),
119        }
120    }
121
122    pub async fn get_latest_block(&self) -> Result<Block> {
123        let response: ApiResponse<Block> = self.client.api_client().get("/blocks/latest").await?;
124
125        match response.data {
126            Some(block) => Ok(block),
127            None => Err(crate::error::LighterError::Api {
128                status: 500,
129                message: response
130                    .error
131                    .unwrap_or_else(|| "Failed to fetch latest block".to_string()),
132            }),
133        }
134    }
135
136    pub async fn wait_for_confirmation(
137        &self,
138        tx_hash: &str,
139        required_confirmations: u32,
140    ) -> Result<Transaction> {
141        let mut attempts = 0;
142        let max_attempts = 60; // 5 minutes with 5-second intervals
143
144        loop {
145            if attempts >= max_attempts {
146                return Err(crate::error::LighterError::Unknown(format!(
147                    "Transaction {} not confirmed after {} attempts",
148                    tx_hash, max_attempts
149                )));
150            }
151
152            match self.get_transaction(tx_hash).await {
153                Ok(tx) => {
154                    if tx.confirmations >= required_confirmations {
155                        return Ok(tx);
156                    }
157                }
158                Err(e) => {
159                    if attempts == 0 {
160                        return Err(e);
161                    }
162                }
163            }
164
165            tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
166            attempts += 1;
167        }
168    }
169}