lighter_rust/api/
account.rs1use 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; 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}