1use crate::common::tif::TimeInForce;
2use crate::transaction::ActionMeta;
3use serde::ser::{SerializeStruct, SerializeTuple};
4use serde::{Deserialize, Serialize};
5use sha2::Digest;
6use solana_hash::Hash;
7use solana_pubkey::Pubkey;
8use std::sync::Arc;
9
10struct FixedF64(f64);
11
12impl Serialize for FixedF64 {
13 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
14 crate::msgs::fixed_point::serialize(&self.0, serializer)
15 }
16}
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct BuilderCode {
21 #[serde(with = "crate::msgs::serde_pubkey")]
22 pub to: Pubkey,
23 pub fee: u8,
24}
25
26fn deserialize_builder_code<'de, D>(deserializer: D) -> Result<Option<BuilderCode>, D::Error>
27where
28 D: serde::Deserializer<'de>,
29{
30 if !deserializer.is_human_readable() {
31 return <Option<BuilderCode> as Deserialize>::deserialize(deserializer);
32 }
33
34 struct BuilderCodeVisitor;
35
36 impl<'de> serde::de::Visitor<'de> for BuilderCodeVisitor {
37 type Value = Option<BuilderCode>;
38
39 fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 formatter.write_str("an omitted builderCode field or a builderCode object")
41 }
42
43 fn visit_none<E>(self) -> Result<Self::Value, E>
44 where
45 E: serde::de::Error,
46 {
47 Err(E::custom("builderCode must be omitted or an object"))
48 }
49
50 fn visit_unit<E>(self) -> Result<Self::Value, E>
51 where
52 E: serde::de::Error,
53 {
54 Err(E::custom("builderCode must be omitted or an object"))
55 }
56
57 fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
58 where
59 D: serde::Deserializer<'de>,
60 {
61 <BuilderCode as Deserialize>::deserialize(deserializer).map(Some)
62 }
63 }
64
65 deserializer.deserialize_option(BuilderCodeVisitor)
66}
67
68struct OrderHashSafeF64(f64);
69
70impl Serialize for OrderHashSafeF64 {
71 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72 crate::msgs::fixed_point::serialize(&self.0, serializer)
73 }
74}
75
76struct OrderHashMarketOrder<'a>(&'a MarketOrder);
77
78impl Serialize for OrderHashMarketOrder<'_> {
79 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
80 let mut tuple = serializer.serialize_tuple(5)?;
81 tuple.serialize_element(&self.0.symbol)?;
82 tuple.serialize_element(&self.0.is_buy)?;
83 tuple.serialize_element(&OrderHashSafeF64(self.0.size))?;
84 tuple.serialize_element(&self.0.reduce_only)?;
85 tuple.serialize_element(&self.0.iso)?;
86 tuple.end()
87 }
88}
89
90struct OrderHashLimitOrder<'a>(&'a LimitOrder);
91
92impl Serialize for OrderHashLimitOrder<'_> {
93 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
94 let mut tuple = serializer.serialize_tuple(7)?;
95 tuple.serialize_element(&self.0.symbol)?;
96 tuple.serialize_element(&self.0.is_buy)?;
97 tuple.serialize_element(&OrderHashSafeF64(self.0.price))?;
98 tuple.serialize_element(&OrderHashSafeF64(self.0.size))?;
99 tuple.serialize_element(&self.0.tif)?;
100 tuple.serialize_element(&self.0.reduce_only)?;
101 tuple.serialize_element(&self.0.iso)?;
102 tuple.end()
103 }
104}
105
106struct OrderHashMarketAction<'a>(&'a MarketOrder);
107
108impl Serialize for OrderHashMarketAction<'_> {
109 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
110 serializer.serialize_newtype_variant(
111 "Action",
112 0,
113 "MarketOrder",
114 &OrderHashMarketOrder(self.0),
115 )
116 }
117}
118
119struct OrderHashLimitAction<'a>(&'a LimitOrder);
120
121impl Serialize for OrderHashLimitAction<'_> {
122 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
123 serializer.serialize_newtype_variant(
124 "Action",
125 1,
126 "LimitOrder",
127 &OrderHashLimitOrder(self.0),
128 )
129 }
130}
131
132#[derive(Clone, Debug, Deserialize)]
137#[serde(deny_unknown_fields)]
138pub struct MarketOrder {
139 #[serde(rename = "c")]
140 pub symbol: Arc<str>,
141
142 #[serde(rename = "b")]
143 pub is_buy: bool,
144
145 #[serde(rename = "sz", with = "crate::msgs::fixed_point")]
146 pub size: f64,
147
148 #[serde(rename = "r")]
149 pub reduce_only: bool,
150
151 #[serde(rename = "i", default)]
152 pub iso: bool,
153
154 #[serde(
155 rename = "builderCode",
156 default,
157 deserialize_with = "deserialize_builder_code"
158 )]
159 pub builder_code: Option<BuilderCode>,
160
161 #[serde(skip)]
162 pub meta: ActionMeta,
163}
164
165impl Serialize for MarketOrder {
166 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
167 if serializer.is_human_readable() {
168 let mut state = serializer
169 .serialize_struct("MarketOrder", 5 + usize::from(self.builder_code.is_some()))?;
170 state.serialize_field("c", &self.symbol)?;
171 state.serialize_field("b", &self.is_buy)?;
172 state.serialize_field("sz", &FixedF64(self.size))?;
173 state.serialize_field("r", &self.reduce_only)?;
174 state.serialize_field("i", &self.iso)?;
175 if let Some(commission) = &self.builder_code {
176 state.serialize_field("builderCode", commission)?;
177 }
178 state.end()
179 } else {
180 let mut tuple = serializer.serialize_tuple(6)?;
181 tuple.serialize_element(&self.symbol)?;
182 tuple.serialize_element(&self.is_buy)?;
183 tuple.serialize_element(&FixedF64(self.size))?;
184 tuple.serialize_element(&self.reduce_only)?;
185 tuple.serialize_element(&self.iso)?;
186 tuple.serialize_element(&self.builder_code)?;
187 tuple.end()
188 }
189 }
190}
191
192impl MarketOrder {
193 pub fn order_id(&self, account: Pubkey, nonce: u64, seqno: u32) -> Hash {
200 let mut bin = Vec::<u8>::new();
201 bin.extend(seqno.to_le_bytes());
202 bin.extend(bincode::serialize(&OrderHashMarketAction(self)).unwrap());
203 bin.extend_from_slice(account.as_ref());
204 bin.extend_from_slice(&nonce.to_le_bytes());
205
206 let mut hasher = sha2::Sha256::new();
207 hasher.update(&bin);
208 let hash: [u8; 32] = hasher.finalize().into();
209 Hash::from(hash)
210 }
211}
212
213#[derive(Clone, Debug, Deserialize)]
218#[serde(deny_unknown_fields)]
219pub struct LimitOrder {
220 #[serde(rename = "c")]
221 pub symbol: Arc<str>,
222
223 #[serde(rename = "b")]
224 pub is_buy: bool,
225
226 #[serde(rename = "px", with = "crate::msgs::fixed_point")]
227 pub price: f64,
228
229 #[serde(rename = "sz", with = "crate::msgs::fixed_point")]
230 pub size: f64,
231
232 #[serde(rename = "tif")]
233 pub tif: TimeInForce,
234
235 #[serde(rename = "r")]
236 pub reduce_only: bool,
237
238 #[serde(rename = "i", default)]
239 pub iso: bool,
240
241 #[serde(
242 rename = "builderCode",
243 default,
244 deserialize_with = "deserialize_builder_code"
245 )]
246 pub builder_code: Option<BuilderCode>,
247
248 #[serde(skip)]
249 pub meta: ActionMeta,
250}
251
252impl Serialize for LimitOrder {
253 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
254 if serializer.is_human_readable() {
255 let mut state = serializer
256 .serialize_struct("LimitOrder", 7 + usize::from(self.builder_code.is_some()))?;
257 state.serialize_field("c", &self.symbol)?;
258 state.serialize_field("b", &self.is_buy)?;
259 state.serialize_field("px", &FixedF64(self.price))?;
260 state.serialize_field("sz", &FixedF64(self.size))?;
261 state.serialize_field("tif", &self.tif)?;
262 state.serialize_field("r", &self.reduce_only)?;
263 state.serialize_field("i", &self.iso)?;
264 if let Some(commission) = &self.builder_code {
265 state.serialize_field("builderCode", commission)?;
266 }
267 state.end()
268 } else {
269 let mut tuple = serializer.serialize_tuple(8)?;
270 tuple.serialize_element(&self.symbol)?;
271 tuple.serialize_element(&self.is_buy)?;
272 tuple.serialize_element(&FixedF64(self.price))?;
273 tuple.serialize_element(&FixedF64(self.size))?;
274 tuple.serialize_element(&self.tif)?;
275 tuple.serialize_element(&self.reduce_only)?;
276 tuple.serialize_element(&self.iso)?;
277 tuple.serialize_element(&self.builder_code)?;
278 tuple.end()
279 }
280 }
281}
282
283impl LimitOrder {
284 pub fn order_id(&self, account: Pubkey, nonce: u64, seqno: u32) -> Hash {
291 let mut bin = Vec::<u8>::new();
292 bin.extend(seqno.to_le_bytes());
293 bin.extend(bincode::serialize(&OrderHashLimitAction(self)).unwrap());
294 bin.extend_from_slice(account.as_ref());
295 bin.extend_from_slice(&nonce.to_le_bytes());
296
297 let mut hasher = sha2::Sha256::new();
298 hasher.update(&bin);
299 let hash: [u8; 32] = hasher.finalize().into();
300 Hash::from(hash)
301 }
302}
303
304#[derive(Clone, Debug, Serialize, Deserialize)]
310pub struct ModifyOrder {
311 #[serde(with = "crate::msgs::serde_hash", rename = "oid")]
312 pub order_id: Hash,
313 #[serde(rename = "c")]
314 pub symbol: String,
315 #[serde(rename = "sz")]
316 pub amount: f64,
317
318 #[serde(skip)]
319 pub meta: ActionMeta,
320}
321
322#[derive(Clone, Debug, Serialize, Deserialize)]
327pub struct CancelOrder {
328 #[serde(rename = "c")]
329 pub symbol: String,
330 #[serde(with = "crate::msgs::serde_hash", rename = "oid")]
331 pub oid: Hash,
332
333 #[serde(skip)]
334 pub meta: ActionMeta,
335}
336
337#[derive(Clone, Debug, Serialize, Deserialize)]
342pub struct CancelAll {
343 #[serde(rename = "c")]
344 pub symbols: Vec<String>,
345
346 #[serde(skip)]
347 pub meta: ActionMeta,
348}
349
350#[cfg(test)]
351mod tests {
352 use super::*;
353
354 #[test]
355 fn limit_order_without_builder_code_omits_json_field() {
356 assert!(!serde_json::to_value(LimitOrder {
357 symbol: Arc::from("BTC-USD"),
358 is_buy: true,
359 price: 100.0,
360 size: 1.0,
361 tif: TimeInForce::GTC,
362 reduce_only: false,
363 iso: false,
364 builder_code: None,
365 meta: ActionMeta::default(),
366 })
367 .expect("limit order should serialize")
368 .as_object()
369 .expect("limit order json should be an object")
370 .contains_key("builderCode"));
371 }
372
373 #[test]
374 fn limit_order_with_builder_code_includes_json_field() {
375 assert_eq!(
376 serde_json::to_value(LimitOrder {
377 symbol: Arc::from("BTC-USD"),
378 is_buy: true,
379 price: 100.0,
380 size: 1.0,
381 tif: TimeInForce::GTC,
382 reduce_only: false,
383 iso: false,
384 builder_code: Some(BuilderCode {
385 to: Pubkey::new_unique(),
386 fee: 5,
387 }),
388 meta: ActionMeta::default(),
389 })
390 .expect("limit order should serialize")["builderCode"]["fee"],
391 5
392 );
393 }
394
395 #[test]
396 fn market_order_without_builder_code_omits_json_field() {
397 assert!(!serde_json::to_value(MarketOrder {
398 symbol: Arc::from("BTC-USD"),
399 is_buy: false,
400 size: 1.0,
401 reduce_only: false,
402 iso: false,
403 builder_code: None,
404 meta: ActionMeta::default(),
405 })
406 .expect("market order should serialize")
407 .as_object()
408 .expect("market order json should be an object")
409 .contains_key("builderCode"));
410 }
411
412 #[test]
413 fn order_json_rejects_null_builder_code() {
414 assert!(serde_json::from_value::<LimitOrder>(serde_json::json!({
415 "c": "BTC-USD",
416 "b": true,
417 "px": 100.0,
418 "sz": 1.0,
419 "tif": "GTC",
420 "r": false,
421 "i": false,
422 "builderCode": null
423 }))
424 .is_err());
425
426 assert!(serde_json::from_value::<MarketOrder>(serde_json::json!({
427 "c": "BTC-USD",
428 "b": true,
429 "sz": 1.0,
430 "r": false,
431 "i": false,
432 "builderCode": null
433 }))
434 .is_err());
435 }
436
437 #[test]
438 fn order_id_ignores_commission() {
439 let account = Pubkey::new_unique();
440 let to = Pubkey::new_unique();
441
442 let without = LimitOrder {
443 symbol: Arc::from("BTC-USD"),
444 is_buy: true,
445 price: 100.0,
446 size: 1.0,
447 tif: TimeInForce::GTC,
448 reduce_only: false,
449 iso: false,
450 builder_code: None,
451 meta: ActionMeta::default(),
452 };
453 let with = LimitOrder {
454 builder_code: Some(BuilderCode { to, fee: 5 }),
455 ..without.clone()
456 };
457
458 assert_eq!(
459 without.order_id(account, 7, 3),
460 with.order_id(account, 7, 3)
461 );
462
463 let market_without = MarketOrder {
464 symbol: Arc::from("BTC-USD"),
465 is_buy: false,
466 size: 2.0,
467 reduce_only: false,
468 iso: true,
469 builder_code: None,
470 meta: ActionMeta::default(),
471 };
472 let market_with = MarketOrder {
473 builder_code: Some(BuilderCode { to, fee: 15 }),
474 ..market_without.clone()
475 };
476
477 assert_eq!(
478 market_without.order_id(account, 7, 3),
479 market_with.order_id(account, 7, 3)
480 );
481 }
482}