1use std::collections::HashMap;
2use serde::{Deserialize, Serialize};
3use solana_pubkey::Pubkey;
4use crate::common::order_status::OrderStatus;
5use crate::common::order_type::OrderType;
6use crate::common::side::Side;
7use crate::common::tif::TimeInForce;
8use crate::transaction::ActionMeta;
9
10#[derive(Clone, Debug, Serialize, Deserialize)]
15pub struct Faucet {
16 #[serde(with = "crate::msgs::serde_pubkey", rename = "u")]
17 pub user: Pubkey,
18 pub amount: Option<f64>,
19
20 #[serde(skip)]
21 pub meta: ActionMeta,
22}
23
24
25#[derive(Clone, Debug, Serialize, Deserialize)]
30pub struct WhitelistFaucet {
31 #[serde(with = "crate::msgs::serde_pubkey")]
32 pub target: Pubkey,
33 pub whitelist: bool,
34
35 #[serde(skip)]
36 pub meta: ActionMeta,
37}
38
39#[derive(Clone, Debug, Serialize, Deserialize)]
44pub struct AgentWalletCreation {
45 #[serde(with = "crate::msgs::serde_pubkey", rename = "a")]
46 pub agent: Pubkey,
47 #[serde(rename = "d")]
48 pub delete: bool,
49
50 #[serde(skip)]
51 pub meta: ActionMeta,
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
59pub struct UpdateUserSettings {
60 #[serde(rename = "m")]
61 pub max_leverage: HashMap<String, f64>,
62
63 #[serde(skip)]
64 pub meta: ActionMeta,
65}
66
67
68#[derive(Debug, Clone, Default, Deserialize)]
74#[allow(unused)]
75pub struct Margin {
76 #[serde(rename = "totalBalance")]
77 pub total_balance: f64,
78 #[serde(rename = "availableBalance")]
79 pub available_balance: f64,
80 #[serde(rename = "marginUsed")]
81 pub margin_used: f64,
82 pub notional: f64,
83 #[serde(rename = "realizedPnl")]
84 pub realized_pnl: f64,
85 #[serde(rename = "unrealizedPnl")]
86 pub unrealized_pnl: f64,
87 pub fees: f64,
88 pub funding: f64,
89}
90
91#[derive(Debug, Clone, Deserialize)]
101#[allow(unused)]
102pub struct PositionInfo {
103 #[serde(alias = "coin")]
104 pub symbol: String,
105 pub size: f64,
106 pub price: f64,
107 #[serde(rename = "fairPrice", default)]
108 pub fair_price: f64,
109 #[serde(default)]
110 pub notional: f64,
111 #[serde(rename = "realizedPnl", default)]
112 pub realized_pnl: f64,
113 #[serde(rename = "unrealizedPnl", default)]
114 pub unrealized_pnl: f64,
115 #[serde(default)]
116 pub leverage: f64,
117 #[serde(rename = "liquidationPrice", default)]
118 pub liquidation_price: f64,
119 #[serde(rename = "maintenanceMargin", default)]
120 pub maintenance_margin: f64,
121}
122
123#[derive(Debug, Serialize, Deserialize, Clone)]
128#[serde(rename_all = "camelCase")]
129pub struct TriggerSpec {
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub is_above: Option<bool>,
132 pub px: f64,
133 #[serde(skip_serializing_if = "Option::is_none")]
134 pub lim: Option<f64>,
135 pub oco: Option<String>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub px_hi: Option<f64>,
138 #[serde(skip_serializing_if = "Option::is_none")]
139 pub lim_hi: Option<f64>,
140 #[serde(rename = "trb", skip_serializing_if = "Option::is_none")]
141 pub trail_bps: Option<u32>,
142 #[serde(rename = "stb", skip_serializing_if = "Option::is_none")]
143 pub step_bps: Option<u32>,
144}
145
146#[derive(Debug, Clone, Deserialize)]
152#[allow(unused)]
153pub struct OrderState {
154 #[serde(rename = "ot")]
155 pub order_type: OrderType,
156 pub status: OrderStatus,
157 #[serde(rename = "sym")]
158 pub symbol: String,
159 #[serde(rename = "oid")]
160 pub order_id: String,
161 #[serde(rename = "px")]
162 pub price: f64,
163 #[serde(rename = "origSz")]
164 pub original_size: f64,
165 #[serde(rename = "sz")]
166 pub signed_size: f64,
167 #[serde(rename = "fillSz")]
168 pub filled_size: f64,
169 pub vwap: f64,
170 pub tif: TimeInForce,
171 #[serde(rename = "r")]
172 pub reduce_only: bool,
173 #[serde(rename = "mk")]
174 pub maker: bool,
175 #[serde(default)]
176 pub trigger: Option<TriggerSpec>,
177 #[serde(rename = "ts")]
178 pub timestamp: u64,
179 #[serde(skip_serializing_if = "Option::is_none")]
180 pub reason: Option<String>,
181}
182
183impl OrderState {
184 pub fn side(&self) -> Side {
186 if self.signed_size < 0.0 {
187 Side::Sell
188 } else {
189 Side::Buy
190 }
191 }
192
193 pub fn amount(&self) -> f64 {
195 self.signed_size.abs()
196 }
197}
198
199#[derive(Debug, Clone, Deserialize)]
204#[allow(unused)]
205pub struct Fill {
206 pub timestamp: u64,
207 #[serde(alias = "coin")]
208 pub symbol: String,
209 #[serde(rename = "orderId")]
210 pub order_id: String,
211 pub price: f64,
212 pub size: f64,
213 #[serde(rename = "isBuy")]
214 pub side: Side,
215 #[serde(rename = "maker", default)]
216 pub is_maker: bool,
217 #[serde(rename = "counterpartyHint", default)]
218 pub cpty: String,
219 pub reason: Option<String>,
220}
221
222#[derive(Debug, Clone, Deserialize)]
227#[allow(unused)]
228pub struct LeverageSetting {
229 #[serde(alias = "coin")]
230 pub symbol: String,
231 pub leverage: f64,
232}
233
234#[derive(Debug, Clone, Deserialize)]
241#[serde(rename_all = "camelCase")]
242#[allow(unused)]
243pub struct AccountData {
244 pub positions: Vec<PositionInfo>,
245 pub open_orders: Vec<OrderState>,
246 pub margin: Margin,
247 pub leverage_settings: Vec<LeverageSetting>,
248}
249
250
251#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn test_order_state_rejected_risk_limit() {
261 let json = r#"{
262 "ts": 1770918312787284000,
263 "ot": "limit",
264 "status": "rejectedRiskLimit",
265 "sym": "BTC-USD",
266 "oid": "EF2bxQ5pp3CDFAwRi44ExXb32sRmByByYxjwLYBfvRKQ",
267 "px": 100001.37,
268 "origSz": -0.02474,
269 "sz": -0.02474,
270 "fillSz": 0.0,
271 "vwap": 0.0,
272 "mk": true,
273 "r": false,
274 "tif": "gtc",
275 "reason": "no oracle / fair price reference yet for: BTC-USD"
276 }"#;
277
278 let order: OrderState = serde_json::from_str(json).unwrap();
279
280 assert_eq!(order.symbol, "BTC-USD");
281 assert_eq!(order.order_id, "EF2bxQ5pp3CDFAwRi44ExXb32sRmByByYxjwLYBfvRKQ");
282 assert_eq!(order.status, OrderStatus::RejectedRiskLimit);
283 assert!(order.signed_size < 0.0);
284 assert!((order.price - 100001.37).abs() < 1e-6);
285 assert!((order.original_size.abs() - 0.02474).abs() < 1e-8);
286 assert!((order.signed_size.abs() - 0.02474).abs() < 1e-8);
287 assert_eq!(order.filled_size, 0.0);
288 assert!(order.maker);
289 assert_eq!(order.timestamp, 1770918312787284000);
290 assert_eq!(
291 order.reason.as_deref(),
292 Some("no oracle / fair price reference yet for: BTC-USD")
293 );
294
295 assert!(order.status.is_terminal());
297 assert!(order.status.is_rejected());
298 }
299
300}