Skip to main content

strata_public_contract/
platform.rs

1//! Public SDK 2.0 request and response primitives.
2
3use 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/// Exact asset amount. Public money never crosses the contract as a float.
141#[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/// Sequence metadata shared by all recoverable state streams.
149#[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/// One operation currently callable through the live v2 gateway.
189#[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/// Operations currently available to the client.
200#[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/// Asset identity used by ordinary SDK operations.
217#[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/// Stable market metadata for public SDK operations.
249#[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    /// Quote atoms per whole base unit, encoded as an unsigned decimal string.
274    pub price_atoms: String,
275    /// Available base quantity in base atoms, encoded as an unsigned decimal string.
276    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    /// Zero removes the price level.
292    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/// Externally authorized resting-order operation. The public contract exposes
384/// product intent only; private construction details never cross the SDK
385/// boundary.
386#[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/// Request canonical bytes for one externally signed order-control operation.
395/// Variant-specific fields are sealed so an authorization cannot be widened
396/// between challenge and transaction preparation.
397#[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    /// Exact opaque order set bound by the authorization. Place contains the
430    /// deterministic new order ID; cancel-all contains at most six IDs.
431    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    /// Base58 Ed25519 signature over `authorization_payload_base64`.
442    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    /// Backend-partially-signed Solana v0 transaction. The external session
455    /// signer verifies and fills only its signature slot.
456    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}