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}
393
394#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
398#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
399pub enum PlatformOrderChallengeRequest {
400 Place {
401 owner_wallet: String,
402 session_public_key: String,
403 account_sequence: String,
404 client_order_id: String,
405 side: PlatformTradeSide,
406 order_type: PlatformOrderType,
407 limit_price_atoms: String,
408 size_atoms: String,
409 },
410 Cancel {
411 owner_wallet: String,
412 session_public_key: String,
413 order_id: String,
414 },
415 CancelAll {
416 owner_wallet: String,
417 session_public_key: String,
418 },
419}
420
421#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
422#[serde(deny_unknown_fields)]
423pub struct PlatformOrderChallengeResponse {
424 pub schema_version: u16,
425 pub contract_version: String,
426 pub challenge_id: String,
427 pub market_id: String,
428 pub action: PlatformOrderAction,
429 pub order_ids: Vec<String>,
432 pub authorization_payload_base64: String,
433 pub server_time_ms: u64,
434 pub expires_at_ms: u64,
435}
436
437#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
438#[serde(deny_unknown_fields)]
439pub struct PlatformOrderPrepareRequest {
440 pub challenge_id: String,
441 pub authorization_signature: String,
443}
444
445#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
446#[serde(deny_unknown_fields)]
447pub struct PlatformOrderPrepareResponse {
448 pub schema_version: u16,
449 pub contract_version: String,
450 pub order_control_id: String,
451 pub market_id: String,
452 pub action: PlatformOrderAction,
453 pub order_ids: Vec<String>,
454 pub transaction_base64: String,
457 pub recent_blockhash: String,
458 pub last_valid_block_height: u64,
459 pub expires_at_ms: u64,
460}
461
462#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
463#[serde(deny_unknown_fields)]
464pub struct PlatformOrderSubmitRequest {
465 pub order_control_id: String,
466 pub signed_transaction_base64: String,
467 pub idempotency_key: String,
468}
469
470#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
471#[serde(rename_all = "snake_case")]
472pub enum PlatformOrderSubmissionStatus {
473 Submitted,
474}
475
476#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
477#[serde(deny_unknown_fields)]
478pub struct PlatformOrderSubmitResponse {
479 pub schema_version: u16,
480 pub contract_version: String,
481 pub order_control_id: String,
482 pub market_id: String,
483 pub action: PlatformOrderAction,
484 pub order_ids: Vec<String>,
485 pub signature: String,
486 pub status: PlatformOrderSubmissionStatus,
487}
488
489#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
490#[serde(deny_unknown_fields)]
491pub struct PlatformOrderStatusRequest {
492 pub order_control_id: String,
493 pub idempotency_key: String,
494}
495
496#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
497#[serde(rename_all = "snake_case")]
498pub enum PlatformOrderControlStatus {
499 Submitting,
500 Submitted,
501 Failed,
502}
503
504#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
505#[serde(deny_unknown_fields)]
506pub struct PlatformOrderStatusResponse {
507 pub schema_version: u16,
508 pub contract_version: String,
509 pub order_control_id: String,
510 pub market_id: String,
511 pub action: PlatformOrderAction,
512 pub order_ids: Vec<String>,
513 pub signature: String,
514 pub status: PlatformOrderControlStatus,
515 pub failure_code: Option<String>,
516 pub updated_at_ms: u64,
517}
518
519#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
520#[serde(deny_unknown_fields)]
521pub struct PlatformAccountOrder {
522 pub order_id: String,
523 pub side: PlatformTradeSide,
524 pub order_type: PlatformOrderType,
525 pub state: PlatformOrderState,
526 pub limit_price_atoms: String,
527 pub original_size_atoms: String,
528 pub remaining_size_atoms: String,
529}
530
531#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
532#[serde(deny_unknown_fields)]
533pub struct PlatformAccountFill {
534 pub fill_id: String,
535 pub side: PlatformTradeSide,
536 pub price_atoms: String,
537 pub size_atoms: String,
538 pub fee_quote_atoms: String,
539 pub fee_is_final: bool,
540 pub settlement: PlatformSettlementState,
541 pub executed_at_ms: u64,
542 pub confirmed_at_ms: Option<u64>,
543 pub transaction_id: Option<String>,
544 pub realized_pnl_quote_atoms: String,
545}
546
547#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
548#[serde(deny_unknown_fields)]
549pub struct PlatformAccountSnapshotResponse {
550 pub schema_version: u16,
551 pub contract_version: String,
552 pub market_id: String,
553 pub wallet_address: String,
554 pub server_time_ms: u64,
555 pub orders: Vec<PlatformAccountOrder>,
556 pub fills: Vec<PlatformAccountFill>,
557}
558
559#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
560#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
561pub enum PlatformAccountEvent {
562 AuthChallenge {
563 schema_version: u16,
564 contract_version: String,
565 market_id: String,
566 wallet_address: String,
567 challenge: String,
568 server_time_ms: u64,
569 expires_at_ms: u64,
570 },
571 AccountSnapshot {
572 schema_version: u16,
573 contract_version: String,
574 market_id: String,
575 wallet_address: String,
576 stream_id: String,
577 sequence: String,
578 server_time_ms: u64,
579 orders: Vec<PlatformAccountOrder>,
580 fills: Vec<PlatformAccountFill>,
581 },
582 OrdersSnapshot {
583 schema_version: u16,
584 contract_version: String,
585 market_id: String,
586 wallet_address: String,
587 stream_id: String,
588 sequence: String,
589 previous_sequence: String,
590 server_time_ms: u64,
591 orders: Vec<PlatformAccountOrder>,
592 },
593 Fill {
594 schema_version: u16,
595 contract_version: String,
596 market_id: String,
597 wallet_address: String,
598 stream_id: String,
599 sequence: String,
600 previous_sequence: String,
601 server_time_ms: u64,
602 fill: PlatformAccountFill,
603 },
604 Heartbeat {
605 schema_version: u16,
606 contract_version: String,
607 market_id: String,
608 wallet_address: String,
609 stream_id: String,
610 sequence: String,
611 previous_sequence: String,
612 server_time_ms: u64,
613 },
614}
615
616#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
617#[serde(tag = "type", rename_all = "snake_case")]
618pub enum PlatformMarketDataEvent {
619 BookSnapshot {
620 schema_version: u16,
621 contract_version: String,
622 market_id: String,
623 stream_id: String,
624 sequence: String,
625 server_time_ms: u64,
626 snapshot_id: String,
627 bids: Vec<PlatformBookLevel>,
628 asks: Vec<PlatformBookLevel>,
629 },
630 BookDelta {
631 schema_version: u16,
632 contract_version: String,
633 market_id: String,
634 stream_id: String,
635 sequence: String,
636 previous_sequence: String,
637 server_time_ms: u64,
638 changes: Vec<PlatformBookChange>,
639 },
640 BestBidAsk {
641 schema_version: u16,
642 contract_version: String,
643 market_id: String,
644 stream_id: String,
645 sequence: String,
646 server_time_ms: u64,
647 best_bid: Option<PlatformBookLevel>,
648 best_ask: Option<PlatformBookLevel>,
649 },
650 Trade {
651 schema_version: u16,
652 contract_version: String,
653 market_id: String,
654 server_time_ms: u64,
655 trade: PlatformTrade,
656 },
657 MarketStatus {
658 schema_version: u16,
659 contract_version: String,
660 market_id: String,
661 server_time_ms: u64,
662 status: PlatformMarketState,
663 },
664 Heartbeat {
665 schema_version: u16,
666 contract_version: String,
667 market_id: String,
668 server_time_ms: u64,
669 },
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675
676 #[test]
677 fn public_platform_fixtures_decode_strictly() {
678 let discovery: PlatformDiscoveryResponse =
679 serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
680 let assets: PlatformAssetsResponse = serde_json::from_str(PLATFORM_ASSETS_FIXTURE).unwrap();
681 let markets: PlatformMarketsResponse =
682 serde_json::from_str(PLATFORM_MARKETS_FIXTURE).unwrap();
683 let book: PlatformBookSnapshotResponse =
684 serde_json::from_str(PLATFORM_BOOK_FIXTURE).unwrap();
685 let bbo: PlatformBestBidAskResponse = serde_json::from_str(PLATFORM_BBO_FIXTURE).unwrap();
686 let fees: PlatformFeeScheduleResponse =
687 serde_json::from_str(PLATFORM_FEES_FIXTURE).unwrap();
688 let status: PlatformMarketStatusResponse =
689 serde_json::from_str(PLATFORM_STATUS_FIXTURE).unwrap();
690 let trades: PlatformTradesResponse = serde_json::from_str(PLATFORM_TRADES_FIXTURE).unwrap();
691 let account: PlatformAccountSnapshotResponse =
692 serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
693 let order_challenge: PlatformOrderChallengeResponse =
694 serde_json::from_str(PLATFORM_ORDER_CHALLENGE_FIXTURE).unwrap();
695 let order_prepare: PlatformOrderPrepareResponse =
696 serde_json::from_str(PLATFORM_ORDER_PREPARE_FIXTURE).unwrap();
697 let order_submit: PlatformOrderSubmitResponse =
698 serde_json::from_str(PLATFORM_ORDER_SUBMIT_FIXTURE).unwrap();
699 let order_status: PlatformOrderStatusResponse =
700 serde_json::from_str(PLATFORM_ORDER_STATUS_FIXTURE).unwrap();
701
702 assert_eq!(discovery.schema_version, PLATFORM_SCHEMA_VERSION);
703 assert_eq!(discovery.capabilities.len(), 5);
704 assert!(!discovery.authority.accepts_private_keys);
705 assert_eq!(assets.assets.len(), 2);
706 assert_eq!(markets.markets.len(), 1);
707 assert_eq!(markets.markets[0].base_asset_id, assets.assets[0].asset_id);
708 assert_eq!(markets.markets[0].quote_asset_id, assets.assets[1].asset_id);
709 assert_eq!(book.sequence, "42");
710 assert_eq!(bbo.best_bid.unwrap().price_atoms, "149990000");
711 assert_eq!(fees.maximum_immediate_execution_fee_bps, 10);
712 assert_eq!(status.status, PlatformMarketState::Active);
713 assert_eq!(trades.trades.len(), 1);
714 assert_eq!(account.orders.len(), 1);
715 assert_eq!(account.fills.len(), 1);
716 assert_eq!(order_challenge.action, PlatformOrderAction::Place);
717 assert_eq!(order_prepare.order_ids, order_challenge.order_ids);
718 assert_eq!(order_submit.order_ids, order_challenge.order_ids);
719 assert_eq!(order_status.order_control_id, order_submit.order_control_id);
720 assert_eq!(order_status.status, PlatformOrderControlStatus::Submitting);
721 }
722
723 #[test]
724 fn public_platform_response_rejects_unreviewed_fields() {
725 let mut value: serde_json::Value =
726 serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
727 value
728 .as_object_mut()
729 .unwrap()
730 .insert("unexpected_field".to_owned(), serde_json::Value::Bool(true));
731 assert!(serde_json::from_value::<PlatformDiscoveryResponse>(value).is_err());
732
733 let mut account_event: serde_json::Value =
734 serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
735 let event = account_event.as_object_mut().unwrap();
736 event.insert("type".to_owned(), serde_json::json!("account_snapshot"));
737 event.insert(
738 "stream_id".to_owned(),
739 serde_json::json!("account_stream_66666666666666666666666666666666"),
740 );
741 event.insert("sequence".to_owned(), serde_json::json!("1"));
742 event.insert("unexpected_field".to_owned(), serde_json::json!(true));
743 assert!(serde_json::from_value::<PlatformAccountEvent>(account_event).is_err());
744 }
745}