df_hyperliquid_core/trade/
answer.rs1use crate::client::transport::EXCHANGE;
6use crate::client::{Accepted, ApiError, ExchangeResponse, Status, Transport, exchange};
7use crate::trade::order::Cloid;
8use crate::utils::Dec;
9use serde::Deserialize;
10use serde_json::Value;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum OrderAnswer {
15 Resting { oid: u64, cloid: Option<Cloid> },
17 Filled { oid: u64, total_sz: Dec, avg_px: Dec, cloid: Option<Cloid> },
20 WaitingForTrigger,
22 WaitingForFill,
24 Refused(String),
26 Unknown(String),
28}
29
30#[derive(Deserialize)]
31#[serde(rename_all = "camelCase")]
32struct Rested {
33 oid: u64,
34 #[serde(default)]
35 cloid: Option<Cloid>,
36}
37
38#[derive(Deserialize)]
39#[serde(rename_all = "camelCase")]
40struct Filled {
41 oid: u64,
42 total_sz: Dec,
43 avg_px: Dec,
44 #[serde(default)]
45 cloid: Option<Cloid>,
46}
47
48impl OrderAnswer {
49 pub fn read(status: &Status) -> Result<OrderAnswer, String> {
52 let value = match status {
53 Status::Error(reason) => return Ok(OrderAnswer::Refused(reason.clone())),
54 Status::Ok(value) => value,
55 };
56 let shape = |error: serde_json::Error| format!("{error} in {value}");
57 match value {
58 Value::String(word) => Ok(match word.as_str() {
59 "waitingForTrigger" => OrderAnswer::WaitingForTrigger,
60 "waitingForFill" => OrderAnswer::WaitingForFill,
61 other => OrderAnswer::Unknown(other.to_owned()),
62 }),
63 Value::Object(entry) => {
64 if let Some(resting) = entry.get("resting") {
65 let Rested { oid, cloid } = Rested::deserialize(resting).map_err(shape)?;
66 return Ok(OrderAnswer::Resting { oid, cloid });
67 }
68 if let Some(filled) = entry.get("filled") {
69 let Filled { oid, total_sz, avg_px, cloid } = Filled::deserialize(filled).map_err(shape)?;
70 return Ok(OrderAnswer::Filled { oid, total_sz, avg_px, cloid });
71 }
72 Ok(OrderAnswer::Unknown(value.to_string()))
73 }
74 other => Err(format!("a status that is neither a word nor an object: {other}")),
75 }
76 }
77
78 #[must_use]
80 pub const fn oid(&self) -> Option<u64> {
81 match self {
82 OrderAnswer::Resting { oid, .. } | OrderAnswer::Filled { oid, .. } => Some(*oid),
83 _ => None,
84 }
85 }
86
87 #[must_use]
89 pub const fn is_placed(&self) -> bool {
90 matches!(
91 self,
92 OrderAnswer::Resting { .. }
93 | OrderAnswer::Filled { .. }
94 | OrderAnswer::WaitingForTrigger
95 | OrderAnswer::WaitingForFill
96 )
97 }
98
99 #[must_use]
100 pub fn refusal(&self) -> Option<&str> {
101 match self {
102 OrderAnswer::Refused(reason) => Some(reason),
103 _ => None,
104 }
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
110pub enum CancelAnswer {
111 Cancelled,
112 Refused(String),
114}
115
116impl CancelAnswer {
117 #[must_use]
118 pub fn read(status: &Status) -> CancelAnswer {
119 match status {
120 Status::Error(reason) => CancelAnswer::Refused(reason.clone()),
121 Status::Ok(Value::String(word)) if word == "success" => CancelAnswer::Cancelled,
122 Status::Ok(Value::String(word)) => CancelAnswer::Refused(word.clone()),
123 Status::Ok(other) => CancelAnswer::Refused(other.to_string()),
124 }
125 }
126}
127
128#[derive(Debug, Clone, PartialEq, Eq)]
130pub enum TwapAnswer {
131 Running {
133 twap_id: u64,
134 },
135 Refused(String),
136}
137
138fn unreadable(detail: String) -> ApiError {
139 ApiError::Unreadable { path: EXCHANGE, detail }
140}
141
142fn per_item<T>(
144 accepted: &Accepted,
145 sent: usize,
146 read: impl Fn(&Status) -> Result<T, String>,
147) -> Result<Vec<T>, ApiError> {
148 let statuses = accepted.statuses.as_slice();
149 if let [lone @ Status::Error(_)] = statuses
150 && sent > 1
151 {
152 return (0..sent).map(|_| read(lone).map_err(unreadable)).collect();
153 }
154 if statuses.len() != sent {
155 return Err(unreadable(format!("{sent} were sent and {} statuses came back", statuses.len())));
156 }
157 statuses.iter().map(|status| read(status).map_err(unreadable)).collect()
158}
159
160fn lone(accepted: &Accepted) -> Result<&Status, ApiError> {
162 match accepted.statuses.as_slice() {
163 [status] => Ok(status),
164 many => Err(unreadable(format!("one status was expected and {} came back", many.len()))),
165 }
166}
167
168pub fn orders(accepted: &Accepted, sent: usize) -> Result<Vec<OrderAnswer>, ApiError> {
170 per_item(accepted, sent, OrderAnswer::read)
171}
172
173pub fn cancels(accepted: &Accepted, sent: usize) -> Result<Vec<CancelAnswer>, ApiError> {
175 per_item(accepted, sent, |status| Ok(CancelAnswer::read(status)))
176}
177
178pub fn twap(accepted: &Accepted) -> Result<TwapAnswer, ApiError> {
180 match lone(accepted)? {
181 Status::Error(reason) => Ok(TwapAnswer::Refused(reason.clone())),
182 Status::Ok(value) => value
183 .get("running")
184 .and_then(|running| running.get("twapId"))
185 .and_then(Value::as_u64)
186 .map(|twap_id| TwapAnswer::Running { twap_id })
187 .ok_or_else(|| unreadable(format!("a TWAP status this build cannot read: {value}"))),
188 }
189}
190
191pub fn twap_cancel(accepted: &Accepted) -> Result<CancelAnswer, ApiError> {
193 lone(accepted).map(CancelAnswer::read)
194}
195
196pub fn taken(accepted: &Accepted) -> Result<(), ApiError> {
199 match accepted.errors().next() {
200 Some(reason) => Err(ApiError::Refused(reason.to_owned())),
201 None => Ok(()),
202 }
203}
204
205pub fn read(text: &str) -> Result<Accepted, ApiError> {
207 match ExchangeResponse::parse(text)? {
208 ExchangeResponse::Ok(accepted) => Ok(accepted),
209 ExchangeResponse::Err(reason) => Err(ApiError::Refused(reason)),
210 }
211}
212
213pub async fn send_orders(transport: &impl Transport, body: String, sent: usize) -> Result<Vec<OrderAnswer>, ApiError> {
215 orders(&exchange(transport, body).await?, sent)
216}
217
218pub async fn send_cancels(
220 transport: &impl Transport,
221 body: String,
222 sent: usize,
223) -> Result<Vec<CancelAnswer>, ApiError> {
224 cancels(&exchange(transport, body).await?, sent)
225}
226
227pub async fn send_twap(transport: &impl Transport, body: String) -> Result<TwapAnswer, ApiError> {
229 twap(&exchange(transport, body).await?)
230}
231
232pub async fn send_twap_cancel(transport: &impl Transport, body: String) -> Result<CancelAnswer, ApiError> {
234 twap_cancel(&exchange(transport, body).await?)
235}
236
237pub async fn send_taken(transport: &impl Transport, body: String) -> Result<(), ApiError> {
239 taken(&exchange(transport, body).await?)
240}
241
242#[cfg(test)]
243#[path = "tests/answer.rs"]
244mod tests;