1use serde::{Deserialize, Serialize};
4
5use crate::{CapabilityRisk, McpExposure};
6
7pub const PLATFORM_SCHEMA_VERSION: u16 = 2;
8pub const PLATFORM_CONTRACT_VERSION: &str = "2.0";
9#[cfg(any(test, feature = "fixtures"))]
10#[doc(hidden)]
11pub const PLATFORM_CAPABILITIES_FIXTURE: &str =
12 include_str!("../fixtures/v2/platform-capabilities.json");
13#[cfg(any(test, feature = "fixtures"))]
14#[doc(hidden)]
15pub const PLATFORM_ASSETS_FIXTURE: &str = include_str!("../fixtures/v2/assets.json");
16#[cfg(any(test, feature = "fixtures"))]
17#[doc(hidden)]
18pub const PLATFORM_MARKETS_FIXTURE: &str = include_str!("../fixtures/v2/markets.json");
19#[cfg(any(test, feature = "fixtures"))]
20#[doc(hidden)]
21pub const PLATFORM_BOOK_FIXTURE: &str = include_str!("../fixtures/v2/book.json");
22#[cfg(any(test, feature = "fixtures"))]
23#[doc(hidden)]
24pub const PLATFORM_BBO_FIXTURE: &str = include_str!("../fixtures/v2/bbo.json");
25#[cfg(any(test, feature = "fixtures"))]
26#[doc(hidden)]
27pub const PLATFORM_FEES_FIXTURE: &str = include_str!("../fixtures/v2/fees.json");
28#[cfg(any(test, feature = "fixtures"))]
29#[doc(hidden)]
30pub const PLATFORM_STATUS_FIXTURE: &str = include_str!("../fixtures/v2/status.json");
31#[cfg(any(test, feature = "fixtures"))]
32#[doc(hidden)]
33pub const PLATFORM_TRADES_FIXTURE: &str = include_str!("../fixtures/v2/trades.json");
34#[cfg(any(test, feature = "fixtures"))]
35#[doc(hidden)]
36pub const PLATFORM_ACCOUNT_FIXTURE: &str = include_str!("../fixtures/v2/account.json");
37#[cfg(any(test, feature = "fixtures"))]
38#[doc(hidden)]
39pub const PLATFORM_ORDER_CHALLENGE_FIXTURE: &str =
40 include_str!("../fixtures/v2/order-challenge.json");
41#[cfg(any(test, feature = "fixtures"))]
42#[doc(hidden)]
43pub const PLATFORM_ORDER_PREPARE_FIXTURE: &str = include_str!("../fixtures/v2/order-prepare.json");
44#[cfg(any(test, feature = "fixtures"))]
45#[doc(hidden)]
46pub const PLATFORM_ORDER_SUBMIT_FIXTURE: &str = include_str!("../fixtures/v2/order-submit.json");
47#[cfg(any(test, feature = "fixtures"))]
48#[doc(hidden)]
49pub const PLATFORM_ORDER_STATUS_FIXTURE: &str = include_str!("../fixtures/v2/order-status.json");
50
51#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
52#[serde(rename_all = "snake_case")]
53pub enum PermissionSource {
54 ExternalAgentOwner,
55}
56
57#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
58#[serde(rename_all = "snake_case")]
59pub enum SigningLocation {
60 External,
61}
62
63#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
64#[serde(deny_unknown_fields)]
65pub struct PlatformAuthority {
66 pub permission_source: PermissionSource,
67 pub signing_location: SigningLocation,
68 pub accepts_private_keys: bool,
69}
70
71#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
72#[serde(rename_all = "snake_case")]
73pub enum PlatformTransport {
74 Http,
75 Websocket,
76 Mcp,
77}
78
79#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
80#[serde(rename_all = "snake_case")]
81pub enum PlatformMarketState {
82 Active,
83 ReadOnly,
84 QuoteOnly,
85 CancelOnly,
86 Paused,
87 Warming,
88 Degraded,
89 Unavailable,
90}
91
92#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
93#[serde(rename_all = "snake_case")]
94pub enum PlatformOrderState {
95 Created,
96 Accepted,
97 Open,
98 PartiallyFilled,
99 Filled,
100 CancelPending,
101 Cancelled,
102 Expired,
103 Rejected,
104}
105
106#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
107#[serde(rename_all = "snake_case")]
108pub enum PlatformSettlementState {
109 NotApplicable,
110 Pending,
111 Confirmed,
112 Failed,
113}
114
115#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
116#[serde(rename_all = "snake_case")]
117pub enum PlatformPublicErrorCode {
118 InvalidRequest,
119 UnsupportedCapability,
120 MarketUnavailable,
121 MarketWarming,
122 QuoteUnavailable,
123 QuoteExpired,
124 PriceBoundFailed,
125 InsufficientBalance,
126 PolicyRejected,
127 SessionExpired,
128 SequenceConflict,
129 DuplicateClientId,
130 OrderRejected,
131 OrderNotFound,
132 CancelTooLate,
133 RateLimited,
134 TemporarilyUnavailable,
135 SubmissionAmbiguous,
136 SettlementPending,
137 SettlementFailed,
138}
139
140#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
142#[serde(deny_unknown_fields)]
143pub struct ExactAmount {
144 pub asset_id: String,
145 pub atoms: String,
146}
147
148#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
150#[serde(deny_unknown_fields)]
151pub struct SequenceEnvelope {
152 pub stream_id: String,
153 pub sequence: String,
154 pub previous_sequence: Option<String>,
155 pub server_time_ms: u64,
156 #[serde(skip_serializing_if = "Option::is_none")]
157 pub snapshot_id: Option<String>,
158}
159
160#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
161#[serde(deny_unknown_fields)]
162pub struct PageRequest {
163 #[serde(skip_serializing_if = "Option::is_none")]
164 pub cursor: Option<String>,
165 #[serde(skip_serializing_if = "Option::is_none")]
166 pub limit: Option<u32>,
167}
168
169#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
170#[serde(deny_unknown_fields)]
171pub struct PageInfo {
172 pub next_cursor: Option<String>,
173 pub has_more: bool,
174}
175
176#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
177#[serde(deny_unknown_fields)]
178pub struct PublicOperationError {
179 pub code: PlatformPublicErrorCode,
180 pub message: String,
181 pub retryable: bool,
182 #[serde(skip_serializing_if = "Option::is_none")]
183 pub retry_after_ms: Option<u64>,
184 #[serde(skip_serializing_if = "Option::is_none")]
185 pub operation_id: Option<String>,
186}
187
188#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
190#[serde(deny_unknown_fields)]
191pub struct LivePlatformCapability {
192 pub id: String,
193 pub risk: CapabilityRisk,
194 pub required_scope: String,
195 pub transports: Vec<PlatformTransport>,
196 pub mcp_exposure: McpExposure,
197}
198
199#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
201#[serde(deny_unknown_fields)]
202pub struct PlatformDiscoveryResponse {
203 pub schema_version: u16,
204 pub contract_version: String,
205 pub server_time_ms: u64,
206 pub authority: PlatformAuthority,
207 pub capabilities: Vec<LivePlatformCapability>,
208}
209
210#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
211#[serde(rename_all = "snake_case")]
212pub enum PlatformNetwork {
213 Solana,
214}
215
216#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
218#[serde(deny_unknown_fields)]
219pub struct PlatformAsset {
220 pub asset_id: String,
221 pub symbol: String,
222 pub name: String,
223 pub decimals: u8,
224 #[serde(skip_serializing_if = "Option::is_none")]
225 pub logo_url: Option<String>,
226 pub network: PlatformNetwork,
227}
228
229#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
230#[serde(deny_unknown_fields)]
231pub struct PlatformAssetsResponse {
232 pub schema_version: u16,
233 pub contract_version: String,
234 pub server_time_ms: u64,
235 pub assets: Vec<PlatformAsset>,
236 pub page: PageInfo,
237}
238
239#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
240#[serde(rename_all = "snake_case")]
241pub enum PlatformMarketAction {
242 Quote,
243 ExecuteImmediate,
244 PlaceOrder,
245 ScheduleTwap,
246}
247
248#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
250#[serde(deny_unknown_fields)]
251pub struct PlatformMarket {
252 pub market_id: String,
253 pub label: String,
254 pub base_asset_id: String,
255 pub quote_asset_id: String,
256 pub status: PlatformMarketState,
257 pub available_actions: Vec<PlatformMarketAction>,
258}
259
260#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
261#[serde(deny_unknown_fields)]
262pub struct PlatformMarketsResponse {
263 pub schema_version: u16,
264 pub contract_version: String,
265 pub server_time_ms: u64,
266 pub markets: Vec<PlatformMarket>,
267 pub page: PageInfo,
268}
269
270#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
271#[serde(deny_unknown_fields)]
272pub struct PlatformBookLevel {
273 pub price_atoms: String,
275 pub size_atoms: String,
277}
278
279#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
280#[serde(rename_all = "snake_case")]
281pub enum PlatformBookSide {
282 Bid,
283 Ask,
284}
285
286#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
287#[serde(deny_unknown_fields)]
288pub struct PlatformBookChange {
289 pub side: PlatformBookSide,
290 pub price_atoms: String,
291 pub size_atoms: String,
293}
294
295#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
296#[serde(deny_unknown_fields)]
297pub struct PlatformBookSnapshotResponse {
298 pub schema_version: u16,
299 pub contract_version: String,
300 pub market_id: String,
301 pub stream_id: String,
302 pub sequence: String,
303 pub server_time_ms: u64,
304 pub snapshot_id: String,
305 pub bids: Vec<PlatformBookLevel>,
306 pub asks: Vec<PlatformBookLevel>,
307}
308
309#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
310#[serde(deny_unknown_fields)]
311pub struct PlatformBestBidAskResponse {
312 pub schema_version: u16,
313 pub contract_version: String,
314 pub market_id: String,
315 pub stream_id: String,
316 pub sequence: String,
317 pub server_time_ms: u64,
318 pub best_bid: Option<PlatformBookLevel>,
319 pub best_ask: Option<PlatformBookLevel>,
320}
321
322#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
323#[serde(deny_unknown_fields)]
324pub struct PlatformFeeScheduleResponse {
325 pub schema_version: u16,
326 pub contract_version: String,
327 pub market_id: String,
328 pub server_time_ms: u64,
329 pub passive_maker_fee_bps: u16,
330 pub maximum_immediate_execution_fee_bps: u16,
331 pub book_prices_include_trading_fees: bool,
332 pub exact_fee_returned_by_quote: bool,
333}
334
335#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
336#[serde(deny_unknown_fields)]
337pub struct PlatformMarketStatusResponse {
338 pub schema_version: u16,
339 pub contract_version: String,
340 pub market_id: String,
341 pub server_time_ms: u64,
342 pub status: PlatformMarketState,
343 pub tick_size_atoms: String,
344 pub minimum_order_size_atoms: String,
345}
346
347#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
348#[serde(rename_all = "snake_case")]
349pub enum PlatformTradeSide {
350 Buy,
351 Sell,
352}
353
354#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
355#[serde(deny_unknown_fields)]
356pub struct PlatformTrade {
357 pub trade_id: String,
358 pub side: PlatformTradeSide,
359 pub price_atoms: String,
360 pub size_atoms: String,
361 pub executed_at_ms: u64,
362}
363
364#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365#[serde(deny_unknown_fields)]
366pub struct PlatformTradesResponse {
367 pub schema_version: u16,
368 pub contract_version: String,
369 pub market_id: String,
370 pub server_time_ms: u64,
371 pub trades: Vec<PlatformTrade>,
372}
373
374#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
375#[serde(rename_all = "snake_case")]
376pub enum PlatformOrderType {
377 GoodUntilCancelled,
378 ImmediateOrCancel,
379 FillOrKill,
380 PostOnly,
381}
382
383#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
387#[serde(rename_all = "snake_case")]
388pub enum PlatformOrderAction {
389 Place,
390 Cancel,
391 CancelAll,
392 Replace,
395 Batch,
398}
399
400#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
403#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
404pub enum PlatformOrderBatchOperation {
405 Place {
406 account_sequence: String,
407 client_order_id: String,
408 side: PlatformTradeSide,
409 order_type: PlatformOrderType,
410 limit_price_atoms: String,
411 size_atoms: String,
412 },
413 Cancel {
414 order_id: String,
415 },
416 Replace {
417 order_id: String,
418 account_sequence: String,
419 client_order_id: String,
420 side: PlatformTradeSide,
421 order_type: PlatformOrderType,
422 limit_price_atoms: String,
423 size_atoms: String,
424 },
425}
426
427#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
431#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
432pub enum PlatformOrderChallengeRequest {
433 Place {
434 owner_wallet: String,
435 session_public_key: String,
436 account_sequence: String,
437 client_order_id: String,
438 side: PlatformTradeSide,
439 order_type: PlatformOrderType,
440 limit_price_atoms: String,
441 size_atoms: String,
442 },
443 Cancel {
444 owner_wallet: String,
445 session_public_key: String,
446 order_id: String,
447 },
448 CancelAll {
449 owner_wallet: String,
450 session_public_key: String,
451 },
452 Replace {
453 owner_wallet: String,
454 session_public_key: String,
455 order_id: String,
456 account_sequence: String,
457 client_order_id: String,
458 side: PlatformTradeSide,
459 order_type: PlatformOrderType,
460 limit_price_atoms: String,
461 size_atoms: String,
462 },
463 Batch {
464 owner_wallet: String,
465 session_public_key: String,
466 operations: Vec<PlatformOrderBatchOperation>,
467 },
468}
469
470#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
471#[serde(deny_unknown_fields)]
472pub struct PlatformOrderChallengeResponse {
473 pub schema_version: u16,
474 pub contract_version: String,
475 pub challenge_id: String,
476 pub market_id: String,
477 pub action: PlatformOrderAction,
478 pub order_ids: Vec<String>,
482 pub authorization_payload_base64: String,
483 pub server_time_ms: u64,
484 pub expires_at_ms: u64,
485}
486
487#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
488#[serde(deny_unknown_fields)]
489pub struct PlatformOrderPrepareRequest {
490 pub challenge_id: String,
491 pub authorization_signature: String,
493}
494
495#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
496#[serde(deny_unknown_fields)]
497pub struct PlatformOrderPrepareResponse {
498 pub schema_version: u16,
499 pub contract_version: String,
500 pub order_control_id: String,
501 pub market_id: String,
502 pub action: PlatformOrderAction,
503 pub order_ids: Vec<String>,
504 pub transaction_base64: String,
507 pub recent_blockhash: String,
508 pub last_valid_block_height: u64,
509 pub expires_at_ms: u64,
510}
511
512#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
513#[serde(deny_unknown_fields)]
514pub struct PlatformOrderSubmitRequest {
515 pub order_control_id: String,
516 pub signed_transaction_base64: String,
517 pub idempotency_key: String,
518}
519
520#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
521#[serde(rename_all = "snake_case")]
522pub enum PlatformOrderSubmissionStatus {
523 Submitted,
524}
525
526#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
527#[serde(deny_unknown_fields)]
528pub struct PlatformOrderSubmitResponse {
529 pub schema_version: u16,
530 pub contract_version: String,
531 pub order_control_id: String,
532 pub market_id: String,
533 pub action: PlatformOrderAction,
534 pub order_ids: Vec<String>,
535 pub signature: String,
536 pub status: PlatformOrderSubmissionStatus,
537}
538
539#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
540#[serde(deny_unknown_fields)]
541pub struct PlatformOrderStatusRequest {
542 pub order_control_id: String,
543 pub idempotency_key: String,
544}
545
546#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
547#[serde(rename_all = "snake_case")]
548pub enum PlatformOrderControlStatus {
549 Submitting,
550 Submitted,
551 Failed,
552}
553
554#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
555#[serde(deny_unknown_fields)]
556pub struct PlatformOrderStatusResponse {
557 pub schema_version: u16,
558 pub contract_version: String,
559 pub order_control_id: String,
560 pub market_id: String,
561 pub action: PlatformOrderAction,
562 pub order_ids: Vec<String>,
563 pub signature: String,
564 pub status: PlatformOrderControlStatus,
565 pub failure_code: Option<String>,
566 pub updated_at_ms: u64,
567}
568
569#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
570#[serde(deny_unknown_fields)]
571pub struct PlatformAccountOrder {
572 pub order_id: String,
573 pub side: PlatformTradeSide,
574 pub order_type: PlatformOrderType,
575 pub state: PlatformOrderState,
576 pub limit_price_atoms: String,
577 pub original_size_atoms: String,
578 pub remaining_size_atoms: String,
579}
580
581#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
582#[serde(deny_unknown_fields)]
583pub struct PlatformAccountFill {
584 pub fill_id: String,
585 pub side: PlatformTradeSide,
586 pub price_atoms: String,
587 pub size_atoms: String,
588 pub fee_quote_atoms: String,
589 pub fee_is_final: bool,
590 pub settlement: PlatformSettlementState,
591 pub executed_at_ms: u64,
592 pub confirmed_at_ms: Option<u64>,
593 pub transaction_id: Option<String>,
594 pub realized_pnl_quote_atoms: String,
595}
596
597#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
598#[serde(deny_unknown_fields)]
599pub struct PlatformAccountSnapshotResponse {
600 pub schema_version: u16,
601 pub contract_version: String,
602 pub market_id: String,
603 pub wallet_address: String,
604 pub server_time_ms: u64,
605 pub orders: Vec<PlatformAccountOrder>,
606 pub fills: Vec<PlatformAccountFill>,
607}
608
609#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
610#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
611pub enum PlatformAccountEvent {
612 AuthChallenge {
613 schema_version: u16,
614 contract_version: String,
615 market_id: String,
616 wallet_address: String,
617 challenge: String,
618 server_time_ms: u64,
619 expires_at_ms: u64,
620 },
621 AccountSnapshot {
622 schema_version: u16,
623 contract_version: String,
624 market_id: String,
625 wallet_address: String,
626 stream_id: String,
627 sequence: String,
628 server_time_ms: u64,
629 orders: Vec<PlatformAccountOrder>,
630 fills: Vec<PlatformAccountFill>,
631 },
632 OrdersSnapshot {
633 schema_version: u16,
634 contract_version: String,
635 market_id: String,
636 wallet_address: String,
637 stream_id: String,
638 sequence: String,
639 previous_sequence: String,
640 server_time_ms: u64,
641 orders: Vec<PlatformAccountOrder>,
642 },
643 Fill {
644 schema_version: u16,
645 contract_version: String,
646 market_id: String,
647 wallet_address: String,
648 stream_id: String,
649 sequence: String,
650 previous_sequence: String,
651 server_time_ms: u64,
652 fill: PlatformAccountFill,
653 },
654 Heartbeat {
655 schema_version: u16,
656 contract_version: String,
657 market_id: String,
658 wallet_address: String,
659 stream_id: String,
660 sequence: String,
661 previous_sequence: String,
662 server_time_ms: u64,
663 },
664}
665
666#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
667#[serde(tag = "type", rename_all = "snake_case")]
668pub enum PlatformMarketDataEvent {
669 BookSnapshot {
670 schema_version: u16,
671 contract_version: String,
672 market_id: String,
673 stream_id: String,
674 sequence: String,
675 server_time_ms: u64,
676 snapshot_id: String,
677 bids: Vec<PlatformBookLevel>,
678 asks: Vec<PlatformBookLevel>,
679 },
680 BookDelta {
681 schema_version: u16,
682 contract_version: String,
683 market_id: String,
684 stream_id: String,
685 sequence: String,
686 previous_sequence: String,
687 server_time_ms: u64,
688 changes: Vec<PlatformBookChange>,
689 },
690 BestBidAsk {
691 schema_version: u16,
692 contract_version: String,
693 market_id: String,
694 stream_id: String,
695 sequence: String,
696 server_time_ms: u64,
697 best_bid: Option<PlatformBookLevel>,
698 best_ask: Option<PlatformBookLevel>,
699 },
700 Trade {
701 schema_version: u16,
702 contract_version: String,
703 market_id: String,
704 server_time_ms: u64,
705 trade: PlatformTrade,
706 },
707 MarketStatus {
708 schema_version: u16,
709 contract_version: String,
710 market_id: String,
711 server_time_ms: u64,
712 status: PlatformMarketState,
713 },
714 Heartbeat {
715 schema_version: u16,
716 contract_version: String,
717 market_id: String,
718 server_time_ms: u64,
719 },
720}
721
722#[cfg(test)]
723mod tests {
724 use super::*;
725
726 #[test]
727 fn public_platform_fixtures_decode_strictly() {
728 let discovery: PlatformDiscoveryResponse =
729 serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
730 let assets: PlatformAssetsResponse = serde_json::from_str(PLATFORM_ASSETS_FIXTURE).unwrap();
731 let markets: PlatformMarketsResponse =
732 serde_json::from_str(PLATFORM_MARKETS_FIXTURE).unwrap();
733 let book: PlatformBookSnapshotResponse =
734 serde_json::from_str(PLATFORM_BOOK_FIXTURE).unwrap();
735 let bbo: PlatformBestBidAskResponse = serde_json::from_str(PLATFORM_BBO_FIXTURE).unwrap();
736 let fees: PlatformFeeScheduleResponse =
737 serde_json::from_str(PLATFORM_FEES_FIXTURE).unwrap();
738 let status: PlatformMarketStatusResponse =
739 serde_json::from_str(PLATFORM_STATUS_FIXTURE).unwrap();
740 let trades: PlatformTradesResponse = serde_json::from_str(PLATFORM_TRADES_FIXTURE).unwrap();
741 let account: PlatformAccountSnapshotResponse =
742 serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
743 let order_challenge: PlatformOrderChallengeResponse =
744 serde_json::from_str(PLATFORM_ORDER_CHALLENGE_FIXTURE).unwrap();
745 let order_prepare: PlatformOrderPrepareResponse =
746 serde_json::from_str(PLATFORM_ORDER_PREPARE_FIXTURE).unwrap();
747 let order_submit: PlatformOrderSubmitResponse =
748 serde_json::from_str(PLATFORM_ORDER_SUBMIT_FIXTURE).unwrap();
749 let order_status: PlatformOrderStatusResponse =
750 serde_json::from_str(PLATFORM_ORDER_STATUS_FIXTURE).unwrap();
751
752 assert_eq!(discovery.schema_version, PLATFORM_SCHEMA_VERSION);
753 assert_eq!(discovery.capabilities.len(), 5);
754 assert!(!discovery.authority.accepts_private_keys);
755 assert_eq!(assets.assets.len(), 2);
756 assert_eq!(markets.markets.len(), 1);
757 assert_eq!(markets.markets[0].base_asset_id, assets.assets[0].asset_id);
758 assert_eq!(markets.markets[0].quote_asset_id, assets.assets[1].asset_id);
759 assert_eq!(book.sequence, "42");
760 assert_eq!(bbo.best_bid.unwrap().price_atoms, "149990000");
761 assert_eq!(fees.maximum_immediate_execution_fee_bps, 10);
762 assert_eq!(status.status, PlatformMarketState::Active);
763 assert_eq!(trades.trades.len(), 1);
764 assert_eq!(account.orders.len(), 1);
765 assert_eq!(account.fills.len(), 1);
766 assert_eq!(order_challenge.action, PlatformOrderAction::Place);
767 assert_eq!(order_prepare.order_ids, order_challenge.order_ids);
768 assert_eq!(order_submit.order_ids, order_challenge.order_ids);
769 assert_eq!(order_status.order_control_id, order_submit.order_control_id);
770 assert_eq!(order_status.status, PlatformOrderControlStatus::Submitting);
771 }
772
773 #[test]
774 fn public_platform_response_rejects_unreviewed_fields() {
775 let mut value: serde_json::Value =
776 serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
777 value
778 .as_object_mut()
779 .unwrap()
780 .insert("unexpected_field".to_owned(), serde_json::Value::Bool(true));
781 assert!(serde_json::from_value::<PlatformDiscoveryResponse>(value).is_err());
782
783 let mut account_event: serde_json::Value =
784 serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
785 let event = account_event.as_object_mut().unwrap();
786 event.insert("type".to_owned(), serde_json::json!("account_snapshot"));
787 event.insert(
788 "stream_id".to_owned(),
789 serde_json::json!("account_stream_66666666666666666666666666666666"),
790 );
791 event.insert("sequence".to_owned(), serde_json::json!("1"));
792 event.insert("unexpected_field".to_owned(), serde_json::json!(true));
793 assert!(serde_json::from_value::<PlatformAccountEvent>(account_event).is_err());
794 }
795
796 #[test]
797 fn atomic_order_batch_request_is_strict_and_typed() {
798 let request: PlatformOrderChallengeRequest = serde_json::from_value(serde_json::json!({
799 "action": "batch",
800 "owner_wallet": "11111111111111111111111111111111",
801 "session_public_key": "22222222222222222222222222222222",
802 "operations": [
803 {
804 "action": "cancel",
805 "order_id": "order_11111111111111111111111111111111"
806 },
807 {
808 "action": "replace",
809 "order_id": "order_22222222222222222222222222222222",
810 "account_sequence": "8",
811 "client_order_id": "replacement-8",
812 "side": "sell",
813 "order_type": "post_only",
814 "limit_price_atoms": "151000000",
815 "size_atoms": "2000000"
816 }
817 ]
818 }))
819 .unwrap();
820 let PlatformOrderChallengeRequest::Batch { operations, .. } = request else {
821 panic!("expected batch request");
822 };
823 assert_eq!(operations.len(), 2);
824 assert!(matches!(
825 operations[1],
826 PlatformOrderBatchOperation::Replace { .. }
827 ));
828
829 assert!(
830 serde_json::from_value::<PlatformOrderChallengeRequest>(serde_json::json!({
831 "action": "batch",
832 "owner_wallet": "11111111111111111111111111111111",
833 "session_public_key": "22222222222222222222222222222222",
834 "operations": [{
835 "action": "cancel",
836 "order_id": "order_11111111111111111111111111111111",
837 "implementation": "hidden"
838 }]
839 }))
840 .is_err()
841 );
842 }
843}