lighter_rust/api/
account.rs

1use crate::client::SignerClient;
2use crate::error::Result;
3use crate::models::{Account, AccountStats, AccountTier, AccountTierSwitchRequest, ApiResponse};
4use serde_json::json;
5use tracing::{debug, info, instrument, warn};
6
7#[derive(Debug)]
8pub struct AccountApi {
9    client: SignerClient,
10}
11
12impl AccountApi {
13    pub fn new(client: SignerClient) -> Self {
14        Self { client }
15    }
16
17    #[instrument(skip(self))]
18    pub async fn get_account(&self) -> Result<Account> {
19        debug!("Fetching account information");
20
21        let response: ApiResponse<Account> = self.client.api_client().get("/account").await?;
22
23        match response.data {
24            Some(account) => {
25                info!("Successfully retrieved account: {}", account.id);
26                debug!(
27                    "Account tier: {:?}, Balances: {}",
28                    account.tier,
29                    account.balances.len()
30                );
31                Ok(account)
32            }
33            None => {
34                warn!("Account not found in response");
35                Err(crate::error::LighterError::Api {
36                    status: 404,
37                    message: response
38                        .error
39                        .unwrap_or_else(|| "Account not found".to_string()),
40                })
41            }
42        }
43    }
44
45    pub async fn get_account_stats(&self) -> Result<AccountStats> {
46        let response: ApiResponse<AccountStats> =
47            self.client.api_client().get("/account/stats").await?;
48
49        match response.data {
50            Some(stats) => Ok(stats),
51            None => Err(crate::error::LighterError::Api {
52                status: 404,
53                message: response
54                    .error
55                    .unwrap_or_else(|| "Account stats not found".to_string()),
56            }),
57        }
58    }
59
60    pub async fn change_account_tier(&self, target_tier: AccountTier) -> Result<()> {
61        let nonce = self.client.generate_nonce()?;
62
63        let payload = json!({
64            "target_tier": target_tier,
65            "nonce": nonce
66        });
67
68        let signature = self
69            .client
70            .signer()
71            .sign_message(&serde_json::to_string(&payload).unwrap())?;
72
73        let request = AccountTierSwitchRequest {
74            target_tier,
75            signature,
76            nonce,
77        };
78
79        let response: ApiResponse<()> = self
80            .client
81            .api_client()
82            .post("/account/change-tier", Some(request))
83            .await?;
84
85        if !response.success {
86            return Err(crate::error::LighterError::AccountTierSwitch(
87                response
88                    .error
89                    .unwrap_or_else(|| "Failed to change account tier".to_string()),
90            ));
91        }
92
93        Ok(())
94    }
95
96    pub async fn can_switch_tier(&self) -> Result<bool> {
97        let account = self.get_account().await?;
98
99        let has_positions = !account.positions.is_empty();
100        let has_open_orders = false; // TODO: Check for open orders
101
102        if has_positions || has_open_orders {
103            return Ok(false);
104        }
105
106        if let Some(allowed_at) = account.tier_switch_allowed_at {
107            let now = chrono::Utc::now();
108            Ok(now >= allowed_at)
109        } else {
110            Ok(true)
111        }
112    }
113
114    pub async fn get_balances(&self) -> Result<Vec<crate::models::Balance>> {
115        let account = self.get_account().await?;
116        Ok(account.balances)
117    }
118
119    pub async fn get_positions(&self) -> Result<Vec<crate::models::Position>> {
120        let account = self.get_account().await?;
121        Ok(account.positions)
122    }
123}