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";
9pub const PLATFORM_ACTION_GRAPH: &str = include_str!("../fixtures/v2/platform-action-graph.json");
10#[cfg(any(test, feature = "fixtures"))]
11#[doc(hidden)]
12pub const PLATFORM_SERVICE_STATUS_FIXTURE: &str =
13    include_str!("../fixtures/v2/platform-status.json");
14#[cfg(any(test, feature = "fixtures"))]
15#[doc(hidden)]
16pub const PLATFORM_CAPABILITIES_FIXTURE: &str =
17    include_str!("../fixtures/v2/platform-capabilities.json");
18#[cfg(any(test, feature = "fixtures"))]
19#[doc(hidden)]
20pub const PLATFORM_ASSETS_FIXTURE: &str = include_str!("../fixtures/v2/assets.json");
21#[cfg(any(test, feature = "fixtures"))]
22#[doc(hidden)]
23pub const PLATFORM_SWAP_QUOTE_FIXTURE: &str = include_str!("../fixtures/v2/swap-quote.json");
24#[cfg(any(test, feature = "fixtures"))]
25#[doc(hidden)]
26pub const PLATFORM_MARKETS_FIXTURE: &str = include_str!("../fixtures/v2/markets.json");
27#[cfg(any(test, feature = "fixtures"))]
28#[doc(hidden)]
29pub const PLATFORM_BOOK_FIXTURE: &str = include_str!("../fixtures/v2/book.json");
30#[cfg(any(test, feature = "fixtures"))]
31#[doc(hidden)]
32pub const PLATFORM_BBO_FIXTURE: &str = include_str!("../fixtures/v2/bbo.json");
33#[cfg(any(test, feature = "fixtures"))]
34#[doc(hidden)]
35pub const PLATFORM_FEES_FIXTURE: &str = include_str!("../fixtures/v2/fees.json");
36#[cfg(any(test, feature = "fixtures"))]
37#[doc(hidden)]
38pub const PLATFORM_STATUS_FIXTURE: &str = include_str!("../fixtures/v2/status.json");
39#[cfg(any(test, feature = "fixtures"))]
40#[doc(hidden)]
41pub const PLATFORM_CANDLES_FIXTURE: &str = include_str!("../fixtures/v2/candles.json");
42#[cfg(any(test, feature = "fixtures"))]
43#[doc(hidden)]
44pub const PLATFORM_MARK_FIXTURE: &str = include_str!("../fixtures/v2/mark.json");
45#[cfg(any(test, feature = "fixtures"))]
46#[doc(hidden)]
47pub const PLATFORM_EXECUTION_STATUS_FIXTURE: &str =
48    include_str!("../fixtures/v2/execution-status.json");
49#[cfg(any(test, feature = "fixtures"))]
50#[doc(hidden)]
51pub const PLATFORM_TWAPS_FIXTURE: &str = include_str!("../fixtures/v2/twaps.json");
52#[cfg(any(test, feature = "fixtures"))]
53#[doc(hidden)]
54pub const PLATFORM_TWAP_CHALLENGE_FIXTURE: &str =
55    include_str!("../fixtures/v2/twap-challenge.json");
56#[cfg(any(test, feature = "fixtures"))]
57#[doc(hidden)]
58pub const PLATFORM_TWAP_PREPARE_FIXTURE: &str = include_str!("../fixtures/v2/twap-prepare.json");
59#[cfg(any(test, feature = "fixtures"))]
60#[doc(hidden)]
61pub const PLATFORM_TWAP_SUBMIT_FIXTURE: &str = include_str!("../fixtures/v2/twap-submit.json");
62#[cfg(any(test, feature = "fixtures"))]
63#[doc(hidden)]
64pub const PLATFORM_PORTFOLIO_HISTORY_FIXTURE: &str =
65    include_str!("../fixtures/v2/portfolio-history.json");
66#[cfg(any(test, feature = "fixtures"))]
67#[doc(hidden)]
68pub const PLATFORM_PORTFOLIO_FIXTURE: &str = include_str!("../fixtures/v2/portfolio.json");
69#[cfg(any(test, feature = "fixtures"))]
70#[doc(hidden)]
71pub const PLATFORM_REWARDS_FIXTURE: &str = include_str!("../fixtures/v2/rewards.json");
72#[cfg(any(test, feature = "fixtures"))]
73#[doc(hidden)]
74pub const PLATFORM_REFERRALS_FIXTURE: &str = include_str!("../fixtures/v2/referrals.json");
75#[cfg(any(test, feature = "fixtures"))]
76#[doc(hidden)]
77pub const PLATFORM_REFERRAL_LINK_FIXTURE: &str = include_str!("../fixtures/v2/referral-link.json");
78#[cfg(any(test, feature = "fixtures"))]
79#[doc(hidden)]
80pub const PLATFORM_REFERRAL_CLAIM_FIXTURE: &str =
81    include_str!("../fixtures/v2/referral-claim.json");
82#[cfg(any(test, feature = "fixtures"))]
83#[doc(hidden)]
84pub const PLATFORM_VAULT_STATUS_FIXTURE: &str = include_str!("../fixtures/v2/vault-status.json");
85#[cfg(any(test, feature = "fixtures"))]
86#[doc(hidden)]
87pub const PLATFORM_VAULT_PAUSE_PREPARE_FIXTURE: &str =
88    include_str!("../fixtures/v2/vault-pause-prepare.json");
89#[cfg(any(test, feature = "fixtures"))]
90#[doc(hidden)]
91pub const PLATFORM_VAULT_SETUP_PREPARE_FIXTURE: &str =
92    include_str!("../fixtures/v2/vault-setup-prepare.json");
93pub const PLATFORM_VAULT_DELEGATE_PREPARE_FIXTURE: &str =
94    include_str!("../fixtures/v2/vault-delegate-prepare.json");
95pub const PLATFORM_VAULT_POLICY_PREPARE_FIXTURE: &str =
96    include_str!("../fixtures/v2/vault-policy-prepare.json");
97pub const PLATFORM_VAULT_DEPOSIT_PREPARE_FIXTURE: &str =
98    include_str!("../fixtures/v2/vault-deposit-prepare.json");
99pub const PLATFORM_VAULT_WITHDRAW_PREPARE_FIXTURE: &str =
100    include_str!("../fixtures/v2/vault-withdraw-prepare.json");
101pub const PLATFORM_VAULT_SUBMIT_FIXTURE: &str = include_str!("../fixtures/v2/vault-submit.json");
102#[cfg(any(test, feature = "fixtures"))]
103#[doc(hidden)]
104pub const PLATFORM_BUGS_FIXTURE: &str = include_str!("../fixtures/v2/bugs.json");
105#[cfg(any(test, feature = "fixtures"))]
106#[doc(hidden)]
107pub const PLATFORM_BUG_SUBMIT_FIXTURE: &str = include_str!("../fixtures/v2/bug-submit.json");
108#[cfg(any(test, feature = "fixtures"))]
109#[doc(hidden)]
110pub const PLATFORM_TRADES_FIXTURE: &str = include_str!("../fixtures/v2/trades.json");
111#[cfg(any(test, feature = "fixtures"))]
112#[doc(hidden)]
113pub const PLATFORM_ACCOUNT_FIXTURE: &str = include_str!("../fixtures/v2/account.json");
114#[cfg(any(test, feature = "fixtures"))]
115#[doc(hidden)]
116pub const PLATFORM_MAKER_REPUTATION_FIXTURE: &str =
117    include_str!("../fixtures/v2/maker-reputation.json");
118#[cfg(any(test, feature = "fixtures"))]
119#[doc(hidden)]
120pub const PLATFORM_MAKER_STATUS_FIXTURE: &str = include_str!("../fixtures/v2/maker-status.json");
121#[cfg(any(test, feature = "fixtures"))]
122#[doc(hidden)]
123pub const PLATFORM_MAKER_STREAM_FIXTURE: &str = include_str!("../fixtures/v2/maker-stream.json");
124#[cfg(any(test, feature = "fixtures"))]
125#[doc(hidden)]
126pub const PLATFORM_TWAP_STREAM_FIXTURE: &str = include_str!("../fixtures/v2/twap-stream.json");
127#[cfg(any(test, feature = "fixtures"))]
128#[doc(hidden)]
129pub const PLATFORM_EXECUTION_STREAM_FIXTURE: &str =
130    include_str!("../fixtures/v2/execution-stream.json");
131#[cfg(any(test, feature = "fixtures"))]
132#[doc(hidden)]
133pub const PLATFORM_ORDER_CHALLENGE_FIXTURE: &str =
134    include_str!("../fixtures/v2/order-challenge.json");
135#[cfg(any(test, feature = "fixtures"))]
136#[doc(hidden)]
137pub const PLATFORM_ORDER_PREPARE_FIXTURE: &str = include_str!("../fixtures/v2/order-prepare.json");
138#[cfg(any(test, feature = "fixtures"))]
139#[doc(hidden)]
140pub const PLATFORM_ORDER_SUBMIT_FIXTURE: &str = include_str!("../fixtures/v2/order-submit.json");
141#[cfg(any(test, feature = "fixtures"))]
142#[doc(hidden)]
143pub const PLATFORM_ORDER_STATUS_FIXTURE: &str = include_str!("../fixtures/v2/order-status.json");
144
145#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
146#[serde(rename_all = "snake_case")]
147pub enum PermissionSource {
148    ExternalAgentOwner,
149}
150
151#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152#[serde(rename_all = "snake_case")]
153pub enum SigningLocation {
154    External,
155}
156
157#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
158#[serde(deny_unknown_fields)]
159pub struct PlatformAuthority {
160    pub permission_source: PermissionSource,
161    pub signing_location: SigningLocation,
162    pub accepts_private_keys: bool,
163}
164
165#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
166#[serde(rename_all = "snake_case")]
167pub enum PlatformTransport {
168    Http,
169    Websocket,
170    Mcp,
171}
172
173#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
174#[serde(rename_all = "snake_case")]
175pub enum PlatformMarketState {
176    Active,
177    ReadOnly,
178    QuoteOnly,
179    CancelOnly,
180    Paused,
181    Warming,
182    Degraded,
183    Unavailable,
184}
185
186#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
187#[serde(rename_all = "snake_case")]
188pub enum PlatformOrderState {
189    Created,
190    Accepted,
191    Open,
192    PartiallyFilled,
193    Filled,
194    CancelPending,
195    Cancelled,
196    Expired,
197    Rejected,
198}
199
200#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
201#[serde(rename_all = "snake_case")]
202pub enum PlatformSettlementState {
203    NotApplicable,
204    Pending,
205    Confirmed,
206    Failed,
207}
208
209#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
210#[serde(rename_all = "snake_case")]
211pub enum PlatformPublicErrorCode {
212    InvalidRequest,
213    UnsupportedCapability,
214    MarketUnavailable,
215    MarketWarming,
216    QuoteUnavailable,
217    QuoteExpired,
218    PriceBoundFailed,
219    InsufficientBalance,
220    PolicyRejected,
221    SessionExpired,
222    SequenceConflict,
223    DuplicateClientId,
224    OrderRejected,
225    OrderNotFound,
226    CancelTooLate,
227    SelfTradePrevented,
228    DeadManExpired,
229    RateLimited,
230    TemporarilyUnavailable,
231    SubmissionAmbiguous,
232    SettlementPending,
233    SettlementFailed,
234}
235
236/// Exact asset amount. Public money never crosses the contract as a float.
237#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
238#[serde(deny_unknown_fields)]
239pub struct ExactAmount {
240    pub asset_id: String,
241    pub atoms: String,
242}
243
244/// Sequence metadata shared by all recoverable state streams.
245#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
246#[serde(deny_unknown_fields)]
247pub struct SequenceEnvelope {
248    pub stream_id: String,
249    pub sequence: String,
250    pub previous_sequence: Option<String>,
251    pub server_time_ms: u64,
252    #[serde(skip_serializing_if = "Option::is_none")]
253    pub snapshot_id: Option<String>,
254}
255
256#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
257#[serde(deny_unknown_fields)]
258pub struct PageRequest {
259    #[serde(skip_serializing_if = "Option::is_none")]
260    pub cursor: Option<String>,
261    #[serde(skip_serializing_if = "Option::is_none")]
262    pub limit: Option<u32>,
263}
264
265#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
266#[serde(deny_unknown_fields)]
267pub struct PageInfo {
268    pub next_cursor: Option<String>,
269    pub has_more: bool,
270}
271
272#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
273#[serde(deny_unknown_fields)]
274pub struct PublicOperationError {
275    pub code: PlatformPublicErrorCode,
276    pub message: String,
277    pub retryable: bool,
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub retry_after_ms: Option<u64>,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub operation_id: Option<String>,
282}
283
284/// One operation currently callable through the live v2 gateway.
285#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
286#[serde(deny_unknown_fields)]
287pub struct LivePlatformCapability {
288    pub id: String,
289    pub risk: CapabilityRisk,
290    pub required_scope: String,
291    pub transports: Vec<PlatformTransport>,
292    pub mcp_exposure: McpExposure,
293}
294
295/// Operations currently available to the client.
296#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
297#[serde(deny_unknown_fields)]
298pub struct PlatformDiscoveryResponse {
299    pub schema_version: u16,
300    pub contract_version: String,
301    pub server_time_ms: u64,
302    pub authority: PlatformAuthority,
303    pub capabilities: Vec<LivePlatformCapability>,
304}
305
306#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
307#[serde(rename_all = "snake_case")]
308pub enum PlatformServiceState {
309    Operational,
310    Degraded,
311}
312
313/// Product-level readiness without leaking private implementation details.
314#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
315#[serde(deny_unknown_fields)]
316pub struct PlatformServiceStatusResponse {
317    pub schema_version: u16,
318    pub contract_version: String,
319    pub server_time_ms: u64,
320    pub status: PlatformServiceState,
321    pub available_operations: u32,
322}
323
324#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
325#[serde(rename_all = "snake_case")]
326pub enum PlatformActionKind {
327    Discovery,
328    Read,
329    Prepare,
330    ExternalSignature,
331    Submit,
332    Receipt,
333    Stream,
334}
335
336#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
337#[serde(deny_unknown_fields)]
338pub struct PlatformGraphRelation {
339    pub from: String,
340    pub to: String,
341    pub kind: String,
342}
343
344#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
345#[serde(deny_unknown_fields)]
346pub struct PlatformGraphModule {
347    pub id: String,
348    pub client_property: String,
349    pub capability_ids: Vec<String>,
350}
351
352#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
353#[serde(deny_unknown_fields)]
354pub struct PlatformOperationTransport {
355    pub transport: PlatformTransport,
356    #[serde(skip_serializing_if = "Option::is_none")]
357    pub method: Option<String>,
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub path: Option<String>,
360    #[serde(skip_serializing_if = "Option::is_none")]
361    pub tool: Option<String>,
362}
363
364#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
365#[serde(deny_unknown_fields)]
366pub struct PlatformOperation {
367    pub id: String,
368    pub capability_id: String,
369    pub kind: PlatformActionKind,
370    pub summary: String,
371    pub transports: Vec<PlatformOperationTransport>,
372    pub available: bool,
373}
374
375#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
376#[serde(deny_unknown_fields)]
377pub struct PlatformWorkflowNode {
378    pub id: String,
379    pub kind: PlatformActionKind,
380    pub capability_id: Option<String>,
381    pub operation_ids: Vec<String>,
382    pub available: bool,
383}
384
385#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
386#[serde(deny_unknown_fields)]
387pub struct PlatformWorkflowEdge {
388    pub from: String,
389    pub to: String,
390    pub condition: String,
391}
392
393#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
394#[serde(deny_unknown_fields)]
395pub struct PlatformWorkflow {
396    pub id: String,
397    pub entry_node: String,
398    pub nodes: Vec<PlatformWorkflowNode>,
399    pub edges: Vec<PlatformWorkflowEdge>,
400}
401
402/// Complete customer-safe product graph. Static package support is projected
403/// against live capability discovery before this response is served.
404#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
405#[serde(deny_unknown_fields)]
406pub struct PlatformActionGraphResponse {
407    pub schema_version: u16,
408    pub contract_version: String,
409    pub graph_version: String,
410    pub entry_operation_id: String,
411    pub authority: PlatformAuthority,
412    pub entities: Vec<String>,
413    pub relations: Vec<PlatformGraphRelation>,
414    pub modules: Vec<PlatformGraphModule>,
415    pub operations: Vec<PlatformOperation>,
416    pub workflows: Vec<PlatformWorkflow>,
417}
418
419impl PlatformActionGraphResponse {
420    pub fn foundation() -> Self {
421        serde_json::from_str(PLATFORM_ACTION_GRAPH)
422            .expect("embedded platform action graph must be valid")
423    }
424
425    /// Live discovery is the authority. Package support alone never makes a
426    /// callable operation or workflow node available.
427    pub fn project_availability(
428        &mut self,
429        live_capability_ids: &std::collections::BTreeSet<String>,
430    ) {
431        for operation in &mut self.operations {
432            operation.available = live_capability_ids.contains(&operation.capability_id);
433        }
434        for workflow in &mut self.workflows {
435            for node in &mut workflow.nodes {
436                node.available = node
437                    .capability_id
438                    .as_ref()
439                    .is_none_or(|capability_id| live_capability_ids.contains(capability_id));
440            }
441        }
442    }
443}
444
445#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
446#[serde(rename_all = "snake_case")]
447pub enum PlatformNetwork {
448    Solana,
449}
450
451/// Asset identity used by ordinary SDK operations.
452#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
453#[serde(deny_unknown_fields)]
454pub struct PlatformAsset {
455    pub asset_id: String,
456    pub symbol: String,
457    pub name: String,
458    pub decimals: u8,
459    #[serde(skip_serializing_if = "Option::is_none")]
460    pub logo_url: Option<String>,
461    pub network: PlatformNetwork,
462}
463
464#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
465#[serde(deny_unknown_fields)]
466pub struct PlatformAssetsResponse {
467    pub schema_version: u16,
468    pub contract_version: String,
469    pub server_time_ms: u64,
470    pub assets: Vec<PlatformAsset>,
471    pub page: PageInfo,
472}
473
474/// Exact-input asset swap request. Asset identifiers come from
475/// [`PlatformAssetsResponse`]; implementation-specific identifiers are not
476/// part of this contract.
477#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
478#[serde(deny_unknown_fields)]
479pub struct PlatformSwapQuoteRequest {
480    pub input_asset_id: String,
481    pub output_asset_id: String,
482    pub amount_in_atoms: String,
483    #[serde(default)]
484    pub maximum_tolerance_bps: u16,
485}
486
487/// Short-lived customer economics for an exact-input asset swap.
488#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
489#[serde(deny_unknown_fields)]
490pub struct PlatformSwapQuoteResponse {
491    pub schema_version: u16,
492    pub contract_version: String,
493    pub quote_id: String,
494    pub server_time_ms: u64,
495    pub expires_at_ms: u64,
496    pub input_asset_id: String,
497    pub output_asset_id: String,
498    pub amount_in_atoms: String,
499    pub amount_in_consumed_atoms: String,
500    pub amount_out_atoms: String,
501    pub minimum_output_atoms: String,
502    pub input_fee_atoms: String,
503    pub output_fee_atoms: String,
504    pub maximum_tolerance_bps: u16,
505    pub reference_price: String,
506    pub price_impact_pct: String,
507    pub provider: String,
508}
509
510#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
511#[serde(rename_all = "snake_case")]
512pub enum PlatformMarketAction {
513    Quote,
514    ExecuteImmediate,
515    PlaceOrder,
516    ScheduleTwap,
517}
518
519/// Stable market metadata for public SDK operations.
520#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
521#[serde(deny_unknown_fields)]
522pub struct PlatformMarket {
523    pub market_id: String,
524    pub label: String,
525    pub base_asset_id: String,
526    pub quote_asset_id: String,
527    pub status: PlatformMarketState,
528    pub available_actions: Vec<PlatformMarketAction>,
529}
530
531#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
532#[serde(deny_unknown_fields)]
533pub struct PlatformMarketsResponse {
534    pub schema_version: u16,
535    pub contract_version: String,
536    pub server_time_ms: u64,
537    pub markets: Vec<PlatformMarket>,
538    pub page: PageInfo,
539}
540
541#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
542#[serde(deny_unknown_fields)]
543pub struct PlatformBookLevel {
544    /// Quote atoms per whole base unit, encoded as an unsigned decimal string.
545    pub price_atoms: String,
546    /// Available base quantity in base atoms, encoded as an unsigned decimal string.
547    pub size_atoms: String,
548}
549
550#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
551#[serde(rename_all = "snake_case")]
552pub enum PlatformBookSide {
553    Bid,
554    Ask,
555}
556
557#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
558#[serde(deny_unknown_fields)]
559pub struct PlatformBookChange {
560    pub side: PlatformBookSide,
561    pub price_atoms: String,
562    /// Zero removes the price level.
563    pub size_atoms: String,
564}
565
566#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
567#[serde(deny_unknown_fields)]
568pub struct PlatformBookSnapshotResponse {
569    pub schema_version: u16,
570    pub contract_version: String,
571    pub market_id: String,
572    pub stream_id: String,
573    pub sequence: String,
574    pub server_time_ms: u64,
575    pub snapshot_id: String,
576    pub bids: Vec<PlatformBookLevel>,
577    pub asks: Vec<PlatformBookLevel>,
578}
579
580#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
581#[serde(deny_unknown_fields)]
582pub struct PlatformBestBidAskResponse {
583    pub schema_version: u16,
584    pub contract_version: String,
585    pub market_id: String,
586    pub stream_id: String,
587    pub sequence: String,
588    pub server_time_ms: u64,
589    pub best_bid: Option<PlatformBookLevel>,
590    pub best_ask: Option<PlatformBookLevel>,
591}
592
593#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
594#[serde(deny_unknown_fields)]
595pub struct PlatformFeeScheduleResponse {
596    pub schema_version: u16,
597    pub contract_version: String,
598    pub market_id: String,
599    pub server_time_ms: u64,
600    pub passive_maker_fee_bps: u16,
601    pub maximum_immediate_execution_fee_bps: u16,
602    pub book_prices_include_trading_fees: bool,
603    pub exact_fee_returned_by_quote: bool,
604}
605
606#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
607#[serde(deny_unknown_fields)]
608pub struct PlatformMarketStatusResponse {
609    pub schema_version: u16,
610    pub contract_version: String,
611    pub market_id: String,
612    pub server_time_ms: u64,
613    pub status: PlatformMarketState,
614    pub tick_size_atoms: String,
615    pub minimum_order_size_atoms: String,
616}
617
618/// Decimal prices are strings so no SDK boundary silently rounds money.
619#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
620#[serde(deny_unknown_fields)]
621pub struct PlatformCandle {
622    pub started_at_ms: u64,
623    pub open_price: String,
624    pub high_price: String,
625    pub low_price: String,
626    pub close_price: String,
627}
628
629#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
630#[serde(deny_unknown_fields)]
631pub struct PlatformCandlesResponse {
632    pub schema_version: u16,
633    pub contract_version: String,
634    pub market_id: String,
635    pub server_time_ms: u64,
636    pub resolution_seconds: u32,
637    pub candles: Vec<PlatformCandle>,
638}
639
640#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
641#[serde(deny_unknown_fields)]
642pub struct PlatformMarkResponse {
643    pub schema_version: u16,
644    pub contract_version: String,
645    pub market_id: String,
646    pub server_time_ms: u64,
647    pub price_atoms_per_base_unit: Option<String>,
648    pub quote_decimals: u8,
649    pub stale: bool,
650    pub age_ms: Option<u64>,
651}
652
653#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
654#[serde(rename_all = "snake_case")]
655pub enum PlatformExecutionState {
656    Prepared,
657    Confirmed,
658}
659
660/// Recoverable immediate-execution receipt. Confirmed rows are journalled and
661/// survive a market-service restart.
662#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
663#[serde(deny_unknown_fields)]
664pub struct PlatformExecutionStatusResponse {
665    pub schema_version: u16,
666    pub contract_version: String,
667    pub execution_id: String,
668    pub market_id: String,
669    pub status: PlatformExecutionState,
670    pub signature: Option<String>,
671    pub settlement: PlatformSettlementState,
672    pub updated_at_ms: u64,
673}
674
675/// One watched immediate execution as the stream sees it: the same fields as
676/// the recoverable HTTP receipt without the envelope.
677#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
678#[serde(deny_unknown_fields)]
679pub struct PlatformExecutionRow {
680    pub execution_id: String,
681    pub market_id: String,
682    pub status: PlatformExecutionState,
683    pub signature: Option<String>,
684    pub settlement: PlatformSettlementState,
685    pub updated_at_ms: u64,
686}
687
688/// Client frame for the execution stream: watch one or more opaque execution
689/// handles issued by `execution.prepare` in this market.
690#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
691#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
692pub enum PlatformExecutionCommand {
693    Watch { execution_ids: Vec<String> },
694}
695
696/// Sequenced execution stream (`execution.stream`) for one market. The client
697/// opens the socket and sends a `watch` frame; the server answers with one
698/// `executions_snapshot` for the watched handles, then `execution_update`
699/// whenever a watched execution is prepared, confirmed on chain, or expires
700/// unconfirmed, `execution_unknown` for handles this market never issued or
701/// no longer remembers, and heartbeats. Later `watch` frames add handles and
702/// produce update/unknown events for them.
703#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
704#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
705pub enum PlatformExecutionEvent {
706    ExecutionsSnapshot {
707        schema_version: u16,
708        contract_version: String,
709        market_id: String,
710        stream_id: String,
711        sequence: String,
712        server_time_ms: u64,
713        executions: Vec<PlatformExecutionRow>,
714        unknown_execution_ids: Vec<String>,
715    },
716    ExecutionUpdate {
717        schema_version: u16,
718        contract_version: String,
719        market_id: String,
720        stream_id: String,
721        sequence: String,
722        previous_sequence: String,
723        server_time_ms: u64,
724        execution: PlatformExecutionRow,
725    },
726    ExecutionExpired {
727        schema_version: u16,
728        contract_version: String,
729        market_id: String,
730        stream_id: String,
731        sequence: String,
732        previous_sequence: String,
733        server_time_ms: u64,
734        execution_id: String,
735    },
736    ExecutionUnknown {
737        schema_version: u16,
738        contract_version: String,
739        market_id: String,
740        stream_id: String,
741        sequence: String,
742        previous_sequence: String,
743        server_time_ms: u64,
744        execution_id: String,
745    },
746    Heartbeat {
747        schema_version: u16,
748        contract_version: String,
749        market_id: String,
750        stream_id: String,
751        sequence: String,
752        previous_sequence: String,
753        server_time_ms: u64,
754    },
755}
756
757#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
758#[serde(rename_all = "snake_case")]
759pub enum PlatformTwapState {
760    Active,
761    Completed,
762    Cancelled,
763}
764
765#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
766#[serde(deny_unknown_fields)]
767pub struct PlatformTwapFill {
768    pub fill_id: String,
769    pub size_atoms: String,
770    pub price_atoms: String,
771    pub gross_quote_atoms: String,
772    pub base_fee_atoms: String,
773    pub quote_fee_atoms: String,
774    pub signature: Option<String>,
775    pub observed_at_ms: u64,
776}
777
778#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
779#[serde(deny_unknown_fields)]
780pub struct PlatformTwap {
781    pub twap_id: String,
782    pub side: PlatformTradeSide,
783    pub status: PlatformTwapState,
784    pub slices_total: u16,
785    pub slices_executed: u16,
786    pub interval_slots: u32,
787    pub maximum_tolerance_bps: u16,
788    pub limit_price_atoms: String,
789    pub total_size_atoms: String,
790    pub executed_size_atoms: String,
791    pub gross_quote_executed_atoms: String,
792    pub complete_execution_value: bool,
793    pub created_at_ms: u64,
794    pub completed_at_ms: Option<u64>,
795    pub placed_signature: Option<String>,
796    pub terminal_signature: Option<String>,
797    pub fills: Vec<PlatformTwapFill>,
798}
799
800#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
801#[serde(deny_unknown_fields)]
802pub struct PlatformTwapsResponse {
803    pub schema_version: u16,
804    pub contract_version: String,
805    pub market_id: String,
806    pub wallet_address: String,
807    pub server_time_ms: u64,
808    pub twaps: Vec<PlatformTwap>,
809}
810
811#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
812#[serde(rename_all = "snake_case")]
813pub enum PlatformTwapControlAction {
814    Place,
815    Cancel,
816}
817
818/// Request exact authorization bytes for one Vault-owned TWAP action. The
819/// external owner chooses the session signer; Strata never receives its key.
820#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
821#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
822pub enum PlatformTwapChallengeRequest {
823    Place {
824        owner_wallet: String,
825        session_public_key: String,
826        side: PlatformTradeSide,
827        total_size_atoms: String,
828        slices_total: u16,
829        maximum_tolerance_bps: u16,
830        /// Slots between slices. Slot time is a cluster parameter (400 ms
831        /// today, stepping down to 200 ms under SIMD-0525), so a schedule
832        /// expressed in slots runs faster in wall time as slots shorten.
833        interval_slots: u32,
834        limit_price_atoms: String,
835    },
836    Cancel {
837        owner_wallet: String,
838        session_public_key: String,
839        twap_id: String,
840    },
841}
842
843#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
844#[serde(deny_unknown_fields)]
845pub struct PlatformTwapChallengeResponse {
846    pub schema_version: u16,
847    pub contract_version: String,
848    pub challenge_id: String,
849    pub market_id: String,
850    pub action: PlatformTwapControlAction,
851    pub twap_id: String,
852    pub authorization_payload_base64: String,
853    pub server_time_ms: u64,
854    pub expires_at_ms: u64,
855}
856
857/// A prepared TWAP challenge, signed: the two-step path.
858#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
859#[serde(deny_unknown_fields)]
860pub struct PlatformTwapPrepareAuthorization {
861    pub challenge_id: String,
862    /// Base58 Ed25519 signature over `authorization_payload_base64`.
863    pub authorization_signature: String,
864}
865
866/// Prepare a TWAP-control transaction: a signed challenge (`Authorized`) or
867/// the action itself (`Direct`, one signature — the transaction signature is
868/// the authorization). The response is identical.
869#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
870#[serde(untagged)]
871pub enum PlatformTwapPrepareRequest {
872    Authorized(PlatformTwapPrepareAuthorization),
873    Direct(PlatformTwapChallengeRequest),
874}
875
876#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
877#[serde(deny_unknown_fields)]
878pub struct PlatformTwapPrepareResponse {
879    pub schema_version: u16,
880    pub contract_version: String,
881    pub twap_control_id: String,
882    pub market_id: String,
883    pub action: PlatformTwapControlAction,
884    pub twap_id: String,
885    /// Backend-partially-signed transaction. The external session signer
886    /// verifies and fills only its signature slot.
887    pub transaction_base64: String,
888    pub recent_blockhash: String,
889    pub last_valid_block_height: u64,
890    pub expires_at_ms: u64,
891}
892
893#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
894#[serde(deny_unknown_fields)]
895pub struct PlatformTwapSubmitRequest {
896    pub twap_control_id: String,
897    pub signed_transaction_base64: String,
898    pub idempotency_key: String,
899}
900
901#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
902#[serde(deny_unknown_fields)]
903pub struct PlatformTwapSubmitResponse {
904    pub schema_version: u16,
905    pub contract_version: String,
906    pub twap_control_id: String,
907    pub market_id: String,
908    pub action: PlatformTwapControlAction,
909    pub twap_id: String,
910    pub signature: String,
911    pub status: PlatformOrderSubmissionStatus,
912}
913
914/// Sequenced wallet-scoped TWAP progress stream (`algos.twap.stream`) for one
915/// market. It starts with a `twaps_snapshot`, then sends one `twap_update`
916/// carrying the complete sanitized TWAP row whenever a schedule is created,
917/// executes a slice, or reaches a terminal state, plus heartbeats. Every event
918/// carries the stream identity and previous sequence; a recovery snapshot
919/// advances the sequence on the same identity.
920#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
921#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
922#[allow(clippy::large_enum_variant)]
923pub enum PlatformTwapEvent {
924    TwapsSnapshot {
925        schema_version: u16,
926        contract_version: String,
927        market_id: String,
928        wallet_address: String,
929        stream_id: String,
930        sequence: String,
931        server_time_ms: u64,
932        twaps: Vec<PlatformTwap>,
933    },
934    TwapUpdate {
935        schema_version: u16,
936        contract_version: String,
937        market_id: String,
938        wallet_address: String,
939        stream_id: String,
940        sequence: String,
941        previous_sequence: String,
942        server_time_ms: u64,
943        twap: PlatformTwap,
944    },
945    Heartbeat {
946        schema_version: u16,
947        contract_version: String,
948        market_id: String,
949        wallet_address: String,
950        stream_id: String,
951        sequence: String,
952        previous_sequence: String,
953        server_time_ms: u64,
954    },
955}
956
957#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
958pub enum PlatformPortfolioHistoryRange {
959    #[serde(rename = "24h")]
960    Day,
961    #[serde(rename = "7d")]
962    Week,
963    #[serde(rename = "30d")]
964    Month,
965}
966
967#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
968#[serde(deny_unknown_fields)]
969pub struct PlatformPortfolioHistoryPoint {
970    pub recorded_at_ms: u64,
971    pub equity_usd_micros: String,
972    pub available_usd_micros: String,
973    pub locked_usd_micros: String,
974    pub market_count: u32,
975}
976
977/// Stored account-equity history. It never fabricates data before collection
978/// began and keeps all currency values in exact USD micros.
979#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
980#[serde(deny_unknown_fields)]
981pub struct PlatformPortfolioHistoryResponse {
982    pub schema_version: u16,
983    pub contract_version: String,
984    pub wallet_address: String,
985    pub server_time_ms: u64,
986    pub range: PlatformPortfolioHistoryRange,
987    pub points: Vec<PlatformPortfolioHistoryPoint>,
988    pub collecting: bool,
989    pub first_sample_ms: Option<u64>,
990    pub last_sample_ms: Option<u64>,
991}
992
993/// One asset the owner holds on Strata, across every live market. Assets
994/// with no holdings are omitted. A balance is a balance: `total` is what the
995/// owner has, `available` is what is free to trade or withdraw, `locked` is
996/// what resting orders reserve.
997#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
998#[serde(deny_unknown_fields)]
999pub struct PlatformPortfolioBalance {
1000    pub asset_id: String,
1001    /// Holdings not reserved by resting orders.
1002    pub available_atoms: String,
1003    /// Holdings reserved by resting orders.
1004    pub locked_atoms: String,
1005    /// `available_atoms + locked_atoms`.
1006    pub total_atoms: String,
1007    /// Exact USD micros for `total_atoms` when a fresh public mark exists.
1008    pub value_usd_micros: Option<String>,
1009}
1010
1011/// The owner's Vault position in one live market. Only markets where the
1012/// Vault holds a market account are listed.
1013#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1014#[serde(deny_unknown_fields)]
1015pub struct PlatformPortfolioPosition {
1016    pub market_id: String,
1017    pub base_asset_id: String,
1018    pub quote_asset_id: String,
1019    pub base_available_atoms: String,
1020    pub base_locked_atoms: String,
1021    pub quote_available_atoms: String,
1022    pub quote_locked_atoms: String,
1023}
1024
1025/// One open order, tagged with the market it rests in.
1026#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1027#[serde(deny_unknown_fields)]
1028pub struct PlatformPortfolioOrder {
1029    pub market_id: String,
1030    pub order_id: String,
1031    pub side: PlatformTradeSide,
1032    pub order_type: PlatformOrderType,
1033    pub state: PlatformOrderState,
1034    pub limit_price_atoms: String,
1035    pub original_size_atoms: String,
1036    pub remaining_size_atoms: String,
1037}
1038
1039/// One recent fill, tagged with the market it happened in.
1040#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1041#[serde(deny_unknown_fields)]
1042pub struct PlatformPortfolioFill {
1043    pub market_id: String,
1044    pub fill_id: String,
1045    pub side: PlatformTradeSide,
1046    pub price_atoms: String,
1047    pub size_atoms: String,
1048    pub fee_quote_atoms: String,
1049    pub fee_is_final: bool,
1050    pub settlement: PlatformSettlementState,
1051    pub executed_at_ms: u64,
1052    pub confirmed_at_ms: Option<u64>,
1053    pub transaction_id: Option<String>,
1054    pub realized_pnl_quote_atoms: String,
1055}
1056
1057/// The owner's whole account in one public read, by wallet address: balances,
1058/// per-market positions, open orders, and recent fills across every live
1059/// market, plus USD totals. No signature and no market selection is needed.
1060/// Amounts are exact atomic strings; USD totals are null whenever any held
1061/// asset lacks a fresh public mark, so a partial valuation is never presented
1062/// as complete.
1063#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1064#[serde(deny_unknown_fields)]
1065pub struct PlatformPortfolioResponse {
1066    pub schema_version: u16,
1067    pub contract_version: String,
1068    pub wallet_address: String,
1069    pub server_time_ms: u64,
1070    /// When the on-chain state behind this snapshot was observed.
1071    pub observed_at_ms: u64,
1072    /// Chain slot the snapshot was observed at.
1073    pub observed_slot: String,
1074    /// Live markets included in the snapshot.
1075    pub market_count: u32,
1076    pub balances: Vec<PlatformPortfolioBalance>,
1077    pub positions: Vec<PlatformPortfolioPosition>,
1078    /// Every open order across every live market.
1079    pub open_orders: Vec<PlatformPortfolioOrder>,
1080    /// Recent fills across every live market, newest first (bounded).
1081    pub recent_fills: Vec<PlatformPortfolioFill>,
1082    /// Markets whose orders and fills could not be read for this snapshot;
1083    /// balances and positions are still complete.
1084    pub unavailable_market_ids: Vec<String>,
1085    /// Sum of every balance's `value_usd_micros`; null unless the valuation is complete.
1086    pub equity_usd_micros: Option<String>,
1087    /// Exact USD value of every available balance; null unless the valuation is complete.
1088    pub available_usd_micros: Option<String>,
1089    /// `equity_usd_micros - available_usd_micros`; null unless the valuation is complete.
1090    pub locked_usd_micros: Option<String>,
1091    pub valuation_complete: bool,
1092    pub unpriced_asset_ids: Vec<String>,
1093}
1094
1095#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1096#[serde(rename_all = "snake_case")]
1097pub enum PlatformVaultState {
1098    Absent,
1099    Active,
1100    Paused,
1101}
1102
1103#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1104#[serde(rename_all = "snake_case")]
1105pub enum PlatformVaultSessionState {
1106    Absent,
1107    Active,
1108    Expired,
1109}
1110
1111#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1112#[serde(rename_all = "snake_case")]
1113pub enum PlatformVaultWithdrawalMode {
1114    Unrestricted,
1115    Blocked,
1116    Restricted,
1117}
1118
1119/// One asset-specific execution limit. A null maximum means that the session
1120/// is permitted to use the asset without a per-execution amount ceiling.
1121#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1122#[serde(deny_unknown_fields)]
1123pub struct PlatformVaultSpendingLimit {
1124    pub asset_id: String,
1125    pub maximum_per_execution_atoms: Option<String>,
1126}
1127
1128/// Sanitized state for the requested external session key. It intentionally
1129/// omits all construction accounts and price-source identities.
1130#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1131#[serde(deny_unknown_fields)]
1132pub struct PlatformVaultSessionStatus {
1133    pub session_public_key: String,
1134    pub state: PlatformVaultSessionState,
1135    pub expires_at_ms: Option<u64>,
1136    pub permanent: bool,
1137    pub minimum_interval_seconds: u32,
1138    pub maximum_tolerance_bps: u16,
1139    pub last_execution_at_ms: Option<u64>,
1140    pub market_execution_ready: bool,
1141    pub price_protection_active: bool,
1142    pub spending_limits: Vec<PlatformVaultSpendingLimit>,
1143}
1144
1145#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1146#[serde(deny_unknown_fields)]
1147pub struct PlatformVaultWithdrawalAccess {
1148    pub mode: PlatformVaultWithdrawalMode,
1149    pub allowed_wallet_addresses: Vec<String>,
1150}
1151
1152/// Product-level Vault state for an owner and, when requested, one external
1153/// session key. Chain construction identities never cross this boundary.
1154#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1155#[serde(deny_unknown_fields)]
1156pub struct PlatformVaultStatusResponse {
1157    pub schema_version: u16,
1158    pub contract_version: String,
1159    pub server_time_ms: u64,
1160    pub wallet_address: String,
1161    pub state: PlatformVaultState,
1162    pub session: Option<PlatformVaultSessionStatus>,
1163    pub withdrawal_access: PlatformVaultWithdrawalAccess,
1164}
1165
1166#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1167#[serde(deny_unknown_fields)]
1168pub struct PlatformVaultPausePrepareRequest {
1169    pub wallet_address: String,
1170    pub paused: bool,
1171}
1172
1173/// An unsigned owner transaction. The external owner must verify its wallet
1174/// and requested state before signing and broadcasting it.
1175#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1176#[serde(deny_unknown_fields)]
1177pub struct PlatformVaultPausePrepareResponse {
1178    pub schema_version: u16,
1179    pub contract_version: String,
1180    pub server_time_ms: u64,
1181    pub wallet_address: String,
1182    pub paused: bool,
1183    pub transaction_base64: String,
1184    pub recent_blockhash: String,
1185    pub owner_signature_required: bool,
1186    /// Opaque handle for this prepared transaction. Hand it back with the
1187    /// owner-signed transaction to `vault.relay` and Strata submits it — no
1188    /// RPC or SOL needed on the owner side.
1189    pub preparation_id: String,
1190    /// `true` when Strata is the transaction fee payer and covers any rent the
1191    /// action creates, so the owner needs no SOL at all. `false` means the
1192    /// owner wallet is the fee payer (Strata still submits it on request).
1193    pub sponsored: bool,
1194    /// The prepared transaction must be submitted before this server time.
1195    pub submit_by_ms: u64,
1196}
1197
1198#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1199#[serde(rename_all = "snake_case")]
1200pub enum PlatformVaultSetupMode {
1201    Create,
1202    ReplaceSession,
1203}
1204
1205/// Session policy applied when onboarding does not state one: at most one
1206/// execution per second per session, and a 1% maximum tolerance.
1207pub const PLATFORM_SESSION_DEFAULT_MINIMUM_INTERVAL_SECONDS: u32 = 1;
1208pub const PLATFORM_SESSION_DEFAULT_MAXIMUM_TOLERANCE_BPS: u16 = 100;
1209/// A session carries at most this many spending limits.
1210pub const PLATFORM_SESSION_MAX_SPENDING_LIMITS: usize = 4;
1211
1212/// One-signature onboarding: only the wallet and the external session key are
1213/// required. One session then trades every market. Everything else is an
1214/// optional policy on top; absent values take the product defaults.
1215#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1216#[serde(deny_unknown_fields)]
1217pub struct PlatformVaultSetupPrepareRequest {
1218    pub wallet_address: String,
1219    pub session_public_key: String,
1220    /// Optional. Names the market whose price protection the session pins
1221    /// when the product has one; the session trades every market either way.
1222    #[serde(default)]
1223    pub market_id: Option<String>,
1224    /// Null or absent requests the permanent-session expiry supported by the
1225    /// product.
1226    #[serde(default)]
1227    pub expires_at_ms: Option<u64>,
1228    /// Absent takes `PLATFORM_SESSION_DEFAULT_MINIMUM_INTERVAL_SECONDS`.
1229    #[serde(default)]
1230    pub minimum_interval_seconds: Option<u32>,
1231    /// Absent takes `PLATFORM_SESSION_DEFAULT_MAXIMUM_TOLERANCE_BPS`.
1232    #[serde(default)]
1233    pub maximum_tolerance_bps: Option<u16>,
1234    /// Optional per-asset ceilings, at most `PLATFORM_SESSION_MAX_SPENDING_LIMITS`.
1235    /// Assets without a limit are unlimited.
1236    #[serde(default)]
1237    pub spending_limits: Vec<PlatformVaultSpendingLimit>,
1238}
1239
1240/// Owner-bound onboarding or session-replacement transaction. Product inputs
1241/// are echoed exactly so an external verifier can reject changed intent.
1242#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1243#[serde(deny_unknown_fields)]
1244pub struct PlatformVaultSetupPrepareResponse {
1245    pub schema_version: u16,
1246    pub contract_version: String,
1247    pub server_time_ms: u64,
1248    pub wallet_address: String,
1249    pub session_public_key: String,
1250    /// The market named in the request, if any.
1251    pub market_id: Option<String>,
1252    pub mode: PlatformVaultSetupMode,
1253    pub expires_at_ms: Option<u64>,
1254    pub permanent: bool,
1255    /// The applied policy, defaults resolved.
1256    pub minimum_interval_seconds: u32,
1257    pub maximum_tolerance_bps: u16,
1258    pub spending_limits: Vec<PlatformVaultSpendingLimit>,
1259    pub transaction_base64: String,
1260    pub recent_blockhash: String,
1261    pub owner_signature_required: bool,
1262    /// Opaque handle for this prepared transaction. Hand it back with the
1263    /// owner-signed transaction to `vault.relay` and Strata submits it — no
1264    /// RPC or SOL needed on the owner side.
1265    pub preparation_id: String,
1266    /// `true` when Strata is the transaction fee payer and covers any rent the
1267    /// action creates, so the owner needs no SOL at all. `false` means the
1268    /// owner wallet is the fee payer (Strata still submits it on request).
1269    pub sponsored: bool,
1270    /// The prepared transaction must be submitted before this server time.
1271    pub submit_by_ms: u64,
1272}
1273
1274#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1275#[serde(rename_all = "snake_case")]
1276pub enum PlatformVaultDelegateAction {
1277    Revoke,
1278}
1279
1280#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1281#[serde(deny_unknown_fields)]
1282pub struct PlatformVaultDelegatePrepareRequest {
1283    pub wallet_address: String,
1284    pub session_public_key: String,
1285    pub action: PlatformVaultDelegateAction,
1286}
1287
1288/// Unsigned session-lifecycle control. The owner verifies both identities and
1289/// the destructive action before signing and broadcasting it externally.
1290#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1291#[serde(deny_unknown_fields)]
1292pub struct PlatformVaultDelegatePrepareResponse {
1293    pub schema_version: u16,
1294    pub contract_version: String,
1295    pub server_time_ms: u64,
1296    pub wallet_address: String,
1297    pub session_public_key: String,
1298    pub action: PlatformVaultDelegateAction,
1299    pub transaction_base64: String,
1300    pub recent_blockhash: String,
1301    pub owner_signature_required: bool,
1302    /// Opaque handle for this prepared transaction. Hand it back with the
1303    /// owner-signed transaction to `vault.relay` and Strata submits it — no
1304    /// RPC or SOL needed on the owner side.
1305    pub preparation_id: String,
1306    /// `true` when Strata is the transaction fee payer and covers any rent the
1307    /// action creates, so the owner needs no SOL at all. `false` means the
1308    /// owner wallet is the fee payer (Strata still submits it on request).
1309    pub sponsored: bool,
1310    /// The prepared transaction must be submitted before this server time.
1311    pub submit_by_ms: u64,
1312}
1313
1314#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1315#[serde(deny_unknown_fields)]
1316pub struct PlatformVaultPolicyPrepareRequest {
1317    pub wallet_address: String,
1318    pub withdrawal_access: PlatformVaultWithdrawalAccess,
1319}
1320
1321/// An owner-bound withdrawal-access change. Unrestricted access is a status
1322/// state rather than a preparable action in this contract.
1323#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1324#[serde(deny_unknown_fields)]
1325pub struct PlatformVaultPolicyPrepareResponse {
1326    pub schema_version: u16,
1327    pub contract_version: String,
1328    pub server_time_ms: u64,
1329    pub wallet_address: String,
1330    pub withdrawal_access: PlatformVaultWithdrawalAccess,
1331    pub transaction_base64: String,
1332    pub recent_blockhash: String,
1333    pub owner_signature_required: bool,
1334    /// Opaque handle for this prepared transaction. Hand it back with the
1335    /// owner-signed transaction to `vault.relay` and Strata submits it — no
1336    /// RPC or SOL needed on the owner side.
1337    pub preparation_id: String,
1338    /// `true` when Strata is the transaction fee payer and covers any rent the
1339    /// action creates, so the owner needs no SOL at all. `false` means the
1340    /// owner wallet is the fee payer (Strata still submits it on request).
1341    pub sponsored: bool,
1342    /// The prepared transaction must be submitted before this server time.
1343    pub submit_by_ms: u64,
1344}
1345
1346#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1347#[serde(deny_unknown_fields)]
1348pub struct PlatformVaultDepositPrepareRequest {
1349    pub wallet_address: String,
1350    pub market_id: String,
1351    pub asset_id: String,
1352    pub amount_atoms: String,
1353    /// Optional external session key. When it is not yet registered for this
1354    /// wallet, the same deposit transaction registers it with the default
1355    /// session policy — a first deposit is the whole onboarding, one owner
1356    /// signature. An already-registered key changes nothing.
1357    #[serde(default)]
1358    pub session_public_key: Option<String>,
1359}
1360
1361/// Exact owner-funded deposit transaction. Asset construction and custody
1362/// identities remain internal; the public intent is echoed for verification.
1363#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1364#[serde(deny_unknown_fields)]
1365pub struct PlatformVaultDepositPrepareResponse {
1366    pub schema_version: u16,
1367    pub contract_version: String,
1368    pub server_time_ms: u64,
1369    pub wallet_address: String,
1370    pub market_id: String,
1371    pub asset_id: String,
1372    pub amount_atoms: String,
1373    /// SOL Strata already spent on this owner's sponsored actions, recovered
1374    /// in the deposit asset inside this same transaction (a second transfer
1375    /// from the owner's account to Strata). "0" when nothing is owed. It is
1376    /// only ever charged when the owner had no SOL and Strata paid instead,
1377    /// and never exceeds 1% of the deposit.
1378    pub network_cost_atoms: String,
1379    /// The session key named in the request, if any.
1380    pub session_public_key: Option<String>,
1381    /// `true` when this transaction also registers `session_public_key` with
1382    /// the default session policy (the deposit doubles as onboarding);
1383    /// `false` when the key was already registered or none was named.
1384    pub registers_session: bool,
1385    pub transaction_base64: String,
1386    pub recent_blockhash: String,
1387    pub owner_signature_required: bool,
1388    /// Opaque handle for this prepared transaction. Hand it back with the
1389    /// owner-signed transaction to `vault.relay` and Strata submits it — no
1390    /// RPC or SOL needed on the owner side.
1391    pub preparation_id: String,
1392    /// `true` when Strata is the transaction fee payer and covers any rent the
1393    /// action creates, so the owner needs no SOL at all. `false` means the
1394    /// owner wallet is the fee payer (Strata still submits it on request).
1395    pub sponsored: bool,
1396    /// The prepared transaction must be submitted before this server time.
1397    pub submit_by_ms: u64,
1398}
1399
1400#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1401#[serde(deny_unknown_fields)]
1402pub struct PlatformVaultWithdrawPrepareRequest {
1403    pub wallet_address: String,
1404    pub market_id: String,
1405    pub asset_id: String,
1406    pub destination_wallet_address: String,
1407    pub amount_atoms: String,
1408}
1409
1410/// Exact owner-authorized withdrawal transaction. The destination is a wallet
1411/// identity; account construction and private balance routing remain internal.
1412#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1413#[serde(deny_unknown_fields)]
1414pub struct PlatformVaultWithdrawPrepareResponse {
1415    pub schema_version: u16,
1416    pub contract_version: String,
1417    pub server_time_ms: u64,
1418    pub wallet_address: String,
1419    pub market_id: String,
1420    pub asset_id: String,
1421    pub destination_wallet_address: String,
1422    pub amount_atoms: String,
1423    pub transaction_base64: String,
1424    pub recent_blockhash: String,
1425    pub owner_signature_required: bool,
1426    /// Opaque handle for this prepared transaction. Hand it back with the
1427    /// owner-signed transaction to `vault.relay` and Strata submits it — no
1428    /// RPC or SOL needed on the owner side.
1429    pub preparation_id: String,
1430    /// `true` when Strata is the transaction fee payer and covers any rent the
1431    /// action creates, so the owner needs no SOL at all. `false` means the
1432    /// owner wallet is the fee payer (Strata still submits it on request).
1433    pub sponsored: bool,
1434    /// The prepared transaction must be submitted before this server time.
1435    pub submit_by_ms: u64,
1436}
1437
1438/// Which prepared Vault action a submission carries.
1439#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1440#[serde(rename_all = "snake_case")]
1441pub enum PlatformVaultAction {
1442    Setup,
1443    Deposit,
1444    Withdraw,
1445    Delegate,
1446    Policy,
1447    Pause,
1448}
1449
1450#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1451#[serde(rename_all = "snake_case")]
1452pub enum PlatformVaultSubmissionStatus {
1453    /// Accepted by the cluster; confirmation pending.
1454    Submitted,
1455    Confirmed,
1456    Failed,
1457}
1458
1459/// Submit an owner-signed prepared Vault transaction. Strata verifies it is
1460/// exactly the prepared transaction, adds its own fee-payer signature when
1461/// the preparation was sponsored, and broadcasts it. Idempotent per
1462/// `idempotency_key`.
1463#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1464#[serde(deny_unknown_fields)]
1465pub struct PlatformVaultSubmitRequest {
1466    pub preparation_id: String,
1467    pub signed_transaction_base64: String,
1468    pub idempotency_key: String,
1469}
1470
1471/// Durable outcome of a Vault submission, also returned by the status read.
1472#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1473#[serde(deny_unknown_fields)]
1474pub struct PlatformVaultSubmitResponse {
1475    pub schema_version: u16,
1476    pub contract_version: String,
1477    pub preparation_id: String,
1478    pub action: PlatformVaultAction,
1479    pub wallet_address: String,
1480    pub sponsored: bool,
1481    pub signature: String,
1482    pub status: PlatformVaultSubmissionStatus,
1483    /// Present only when `status` is `failed`.
1484    pub failure_code: Option<String>,
1485    pub updated_at_ms: u64,
1486}
1487
1488#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1489#[serde(deny_unknown_fields)]
1490pub struct PlatformRewardStanding {
1491    pub rank: u32,
1492    pub wallet_address: String,
1493    pub points: String,
1494}
1495
1496#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1497#[serde(deny_unknown_fields)]
1498pub struct PlatformOwnerRewards {
1499    pub wallet_address: String,
1500    pub rank: Option<u32>,
1501    pub points: String,
1502    pub trading_points: String,
1503    pub making_points: String,
1504    pub bug_points: String,
1505    pub referral_points: String,
1506}
1507
1508#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1509#[serde(deny_unknown_fields)]
1510pub struct PlatformRewardsResponse {
1511    pub schema_version: u16,
1512    pub contract_version: String,
1513    pub server_time_ms: u64,
1514    pub season: String,
1515    pub total_wallets: u32,
1516    pub owner: Option<PlatformOwnerRewards>,
1517    pub standings: Vec<PlatformRewardStanding>,
1518}
1519
1520#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1521#[serde(deny_unknown_fields)]
1522pub struct PlatformReferralsResponse {
1523    pub schema_version: u16,
1524    pub contract_version: String,
1525    pub server_time_ms: u64,
1526    pub wallet_address: String,
1527    pub enabled: bool,
1528    pub cash_rewards_enabled: bool,
1529    pub referral_code: Option<String>,
1530    pub referred_wallets: u32,
1531    pub referral_points: String,
1532    pub referred_by: Option<String>,
1533    pub referral_locked: bool,
1534    pub cash_accrued_atoms: String,
1535    pub cash_paid_atoms: String,
1536    pub cash_claimable_atoms: String,
1537}
1538
1539#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1540#[serde(deny_unknown_fields)]
1541pub struct PlatformReferralLinkRequest {
1542    pub wallet_address: String,
1543    pub referral_code: String,
1544    pub authorization_signature: String,
1545}
1546
1547#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1548#[serde(deny_unknown_fields)]
1549pub struct PlatformReferralLinkResponse {
1550    pub schema_version: u16,
1551    pub contract_version: String,
1552    pub server_time_ms: u64,
1553    pub wallet_address: String,
1554    pub referral_code: String,
1555    pub status: String,
1556}
1557
1558#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1559#[serde(deny_unknown_fields)]
1560pub struct PlatformReferralClaimRequest {
1561    pub wallet_address: String,
1562    pub payout_wallet_address: Option<String>,
1563    pub authorization_signature: String,
1564}
1565
1566#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1567#[serde(deny_unknown_fields)]
1568pub struct PlatformReferralClaimResponse {
1569    pub schema_version: u16,
1570    pub contract_version: String,
1571    pub server_time_ms: u64,
1572    pub wallet_address: String,
1573    pub payout_wallet_address: String,
1574    pub claimable_atoms: String,
1575    pub status: String,
1576}
1577
1578#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1579#[serde(rename_all = "snake_case")]
1580pub enum PlatformBugStatus {
1581    Pending,
1582    Confirmed,
1583    Rejected,
1584}
1585
1586#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1587#[serde(deny_unknown_fields)]
1588pub struct PlatformBugReport {
1589    pub bug_id: String,
1590    pub status: PlatformBugStatus,
1591    pub severity: u8,
1592    pub points: String,
1593    pub created_at_ms: u64,
1594    pub triaged_at_ms: Option<u64>,
1595    pub completed_at_ms: Option<u64>,
1596}
1597
1598#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1599#[serde(deny_unknown_fields)]
1600pub struct PlatformBugsResponse {
1601    pub schema_version: u16,
1602    pub contract_version: String,
1603    pub server_time_ms: u64,
1604    pub wallet_address: String,
1605    pub points: String,
1606    pub confirmed_reports: u32,
1607    pub reports: Vec<PlatformBugReport>,
1608}
1609
1610#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1611#[serde(deny_unknown_fields)]
1612pub struct PlatformBugSubmitRequest {
1613    pub owner_wallet: String,
1614    pub message: String,
1615    /// Hex Ed25519 signature over `strata-bug-report:v1:` followed by the
1616    /// trimmed report message. Signing always happens outside Strata.
1617    pub authorization_signature: String,
1618}
1619
1620#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1621#[serde(deny_unknown_fields)]
1622pub struct PlatformBugSubmitResponse {
1623    pub schema_version: u16,
1624    pub contract_version: String,
1625    pub server_time_ms: u64,
1626    pub bug_id: String,
1627    pub status: PlatformBugStatus,
1628}
1629
1630#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1631#[serde(rename_all = "snake_case")]
1632pub enum PlatformTradeSide {
1633    Buy,
1634    Sell,
1635}
1636
1637#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1638#[serde(deny_unknown_fields)]
1639pub struct PlatformTrade {
1640    pub trade_id: String,
1641    pub side: PlatformTradeSide,
1642    pub price_atoms: String,
1643    pub size_atoms: String,
1644    pub executed_at_ms: u64,
1645}
1646
1647#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1648#[serde(deny_unknown_fields)]
1649pub struct PlatformTradesResponse {
1650    pub schema_version: u16,
1651    pub contract_version: String,
1652    pub market_id: String,
1653    pub server_time_ms: u64,
1654    pub trades: Vec<PlatformTrade>,
1655}
1656
1657#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1658#[serde(rename_all = "snake_case")]
1659pub enum PlatformOrderType {
1660    GoodUntilCancelled,
1661    ImmediateOrCancel,
1662    FillOrKill,
1663    PostOnly,
1664}
1665
1666/// Externally authorized resting-order operation. The public contract exposes
1667/// product intent only; private construction details never cross the SDK
1668/// boundary.
1669#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1670#[serde(rename_all = "snake_case")]
1671pub enum PlatformOrderAction {
1672    Place,
1673    Cancel,
1674    CancelAll,
1675    /// Atomically cancel one existing order and place its explicitly bound
1676    /// successor in the same transaction.
1677    Replace,
1678    /// Atomically execute a bounded heterogeneous set of place, cancel, and
1679    /// replace operations in one transaction.
1680    Batch,
1681}
1682
1683/// One operation inside an atomic order-control batch. Owner and session
1684/// identity live on the enclosing challenge so no item can widen authority.
1685#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1686#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
1687pub enum PlatformOrderBatchOperation {
1688    Place {
1689        /// Vault market account sequence for this order. Omit it and Strata
1690        /// resolves the next sequence from the Vault's confirmed market
1691        /// account when the challenge is issued (consecutive places in one
1692        /// batch receive consecutive sequences); supply it to pin a sequence
1693        /// tracked locally. A batch must either supply every sequence or none.
1694        #[serde(default, skip_serializing_if = "Option::is_none")]
1695        account_sequence: Option<String>,
1696        client_order_id: String,
1697        side: PlatformTradeSide,
1698        order_type: PlatformOrderType,
1699        limit_price_atoms: String,
1700        size_atoms: String,
1701    },
1702    Cancel {
1703        order_id: String,
1704    },
1705    Replace {
1706        order_id: String,
1707        #[serde(default, skip_serializing_if = "Option::is_none")]
1708        account_sequence: Option<String>,
1709        client_order_id: String,
1710        side: PlatformTradeSide,
1711        order_type: PlatformOrderType,
1712        limit_price_atoms: String,
1713        size_atoms: String,
1714    },
1715}
1716
1717/// Request canonical bytes for one externally signed order-control operation.
1718/// Variant-specific fields are sealed so an authorization cannot be widened
1719/// between challenge and transaction preparation.
1720#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1721#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
1722pub enum PlatformOrderChallengeRequest {
1723    Place {
1724        owner_wallet: String,
1725        session_public_key: String,
1726        /// Vault market account sequence. Omit it and Strata resolves the next
1727        /// sequence from the Vault's confirmed market account when the
1728        /// challenge is issued; supply it to pin a sequence tracked locally.
1729        #[serde(default, skip_serializing_if = "Option::is_none")]
1730        account_sequence: Option<String>,
1731        client_order_id: String,
1732        side: PlatformTradeSide,
1733        order_type: PlatformOrderType,
1734        limit_price_atoms: String,
1735        size_atoms: String,
1736    },
1737    Cancel {
1738        owner_wallet: String,
1739        session_public_key: String,
1740        order_id: String,
1741    },
1742    CancelAll {
1743        owner_wallet: String,
1744        session_public_key: String,
1745    },
1746    Replace {
1747        owner_wallet: String,
1748        session_public_key: String,
1749        order_id: String,
1750        #[serde(default, skip_serializing_if = "Option::is_none")]
1751        account_sequence: Option<String>,
1752        client_order_id: String,
1753        side: PlatformTradeSide,
1754        order_type: PlatformOrderType,
1755        limit_price_atoms: String,
1756        size_atoms: String,
1757    },
1758    Batch {
1759        owner_wallet: String,
1760        session_public_key: String,
1761        operations: Vec<PlatformOrderBatchOperation>,
1762    },
1763}
1764
1765#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1766#[serde(deny_unknown_fields)]
1767pub struct PlatformOrderChallengeResponse {
1768    pub schema_version: u16,
1769    pub contract_version: String,
1770    pub challenge_id: String,
1771    pub market_id: String,
1772    pub action: PlatformOrderAction,
1773    /// Exact opaque order set bound by the authorization. Replace returns the
1774    /// old then new ID. Batch flattens item IDs in request order, with replace
1775    /// contributing old then new. A batch contains at most six operations.
1776    pub order_ids: Vec<String>,
1777    pub authorization_payload_base64: String,
1778    pub server_time_ms: u64,
1779    pub expires_at_ms: u64,
1780}
1781
1782/// A prepared challenge, signed: the two-step path.
1783#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1784#[serde(deny_unknown_fields)]
1785pub struct PlatformOrderPrepareAuthorization {
1786    pub challenge_id: String,
1787    /// Base58 Ed25519 signature over `authorization_payload_base64`. Required
1788    /// over HTTP. Over the session-authenticated order command channel it may
1789    /// be omitted: the socket already proved the session and the challenge is
1790    /// bound to it, so the session signs only the transaction (one signature).
1791    #[serde(default, skip_serializing_if = "Option::is_none")]
1792    pub authorization_signature: Option<String>,
1793}
1794
1795/// Prepare an order-control transaction. Either hand back a signed challenge
1796/// (`Authorized`, two signatures per action) or send the operation itself
1797/// (`Direct`, one signature per action): Strata builds the transaction from
1798/// the operation immediately and the session's signature over that
1799/// transaction is the whole authorization. The response is identical.
1800#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1801#[serde(untagged)]
1802pub enum PlatformOrderPrepareRequest {
1803    Authorized(PlatformOrderPrepareAuthorization),
1804    Direct(PlatformOrderChallengeRequest),
1805}
1806
1807#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1808#[serde(deny_unknown_fields)]
1809pub struct PlatformOrderPrepareResponse {
1810    pub schema_version: u16,
1811    pub contract_version: String,
1812    pub order_control_id: String,
1813    pub market_id: String,
1814    pub action: PlatformOrderAction,
1815    pub order_ids: Vec<String>,
1816    /// Backend-partially-signed Solana v0 transaction. The external session
1817    /// signer verifies and fills only its signature slot.
1818    pub transaction_base64: String,
1819    pub recent_blockhash: String,
1820    pub last_valid_block_height: u64,
1821    pub expires_at_ms: u64,
1822}
1823
1824#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1825#[serde(deny_unknown_fields)]
1826pub struct PlatformOrderSubmitRequest {
1827    pub order_control_id: String,
1828    pub signed_transaction_base64: String,
1829    pub idempotency_key: String,
1830}
1831
1832#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1833#[serde(rename_all = "snake_case")]
1834pub enum PlatformOrderSubmissionStatus {
1835    Submitted,
1836}
1837
1838#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1839#[serde(deny_unknown_fields)]
1840pub struct PlatformOrderSubmitResponse {
1841    pub schema_version: u16,
1842    pub contract_version: String,
1843    pub order_control_id: String,
1844    pub market_id: String,
1845    pub action: PlatformOrderAction,
1846    pub order_ids: Vec<String>,
1847    pub signature: String,
1848    pub status: PlatformOrderSubmissionStatus,
1849}
1850
1851#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1852#[serde(deny_unknown_fields)]
1853pub struct PlatformOrderStatusRequest {
1854    pub order_control_id: String,
1855    pub idempotency_key: String,
1856}
1857
1858#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1859#[serde(rename_all = "snake_case")]
1860pub enum PlatformOrderControlStatus {
1861    Submitting,
1862    Submitted,
1863    Failed,
1864}
1865
1866#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1867#[serde(deny_unknown_fields)]
1868pub struct PlatformOrderStatusResponse {
1869    pub schema_version: u16,
1870    pub contract_version: String,
1871    pub order_control_id: String,
1872    pub market_id: String,
1873    pub action: PlatformOrderAction,
1874    pub order_ids: Vec<String>,
1875    pub signature: String,
1876    pub status: PlatformOrderControlStatus,
1877    pub failure_code: Option<String>,
1878    pub updated_at_ms: u64,
1879}
1880
1881/// Collision policy for an incoming order that would cross the owner's own
1882/// resting liquidity. Every mode still preserves Strata's matcher and on-chain
1883/// self-fill prohibition; this only controls which order is cancelled first.
1884#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1885#[serde(rename_all = "snake_case")]
1886pub enum PlatformSelfTradePrevention {
1887    CancelTaker,
1888    CancelMaker,
1889    CancelBoth,
1890    SkipOwnLiquidity,
1891}
1892
1893/// One command on the persistent order-control connection. Challenge results
1894/// may contain an effective request that differs from the requested one only
1895/// by the explicitly selected self-trade prevention transformation.
1896#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1897#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1898pub enum PlatformOrderCommand {
1899    /// Authenticated non-trading round trip used for latency certification.
1900    Probe {
1901        nonce: String,
1902    },
1903    Challenge {
1904        request: PlatformOrderChallengeRequest,
1905        self_trade_prevention: PlatformSelfTradePrevention,
1906    },
1907    Prepare {
1908        request: PlatformOrderPrepareRequest,
1909    },
1910    Submit {
1911        request: PlatformOrderSubmitRequest,
1912    },
1913    Status {
1914        request: PlatformOrderStatusRequest,
1915    },
1916    DeadManArm {
1917        timeout_ms: u64,
1918        request: PlatformOrderSubmitRequest,
1919    },
1920    DeadManStatus,
1921    DeadManHeartbeat,
1922    DeadManDisarm,
1923}
1924
1925/// Frames sent by an external agent. Authentication proves possession of the
1926/// declared session key; individual order authorizations and transactions keep
1927/// their existing exact external-signing boundaries. Authentication is a
1928/// singleton frame. After authentication, the transport accepts either one
1929/// command or a bounded array of commands; every command retains its own
1930/// request ID and contiguous sequence.
1931#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1932#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1933pub enum PlatformOrderCommandClientFrame {
1934    Authenticate {
1935        owner_wallet: String,
1936        session_public_key: String,
1937        /// Base58 Ed25519 signature over the stream authentication payload.
1938        signature: String,
1939        /// Optional negotiated result framing. Omitted clients retain the
1940        /// complete-event array format.
1941        #[serde(default, skip_serializing_if = "Option::is_none")]
1942        batch_format: Option<PlatformOrderCommandBatchFormat>,
1943    },
1944    Command {
1945        request_id: String,
1946        sequence: String,
1947        command: PlatformOrderCommand,
1948    },
1949}
1950
1951#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
1952#[serde(rename_all = "snake_case")]
1953pub enum PlatformOrderCommandBatchFormat {
1954    CompactV1,
1955}
1956
1957/// One result inside a compact event batch. Shared stream identity, time and
1958/// sequence metadata live on the enclosing frame; request correlation and
1959/// command-specific results remain independent.
1960#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1961#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1962pub enum PlatformOrderCommandBatchEvent {
1963    ProbeResult {
1964        request_id: String,
1965        nonce: String,
1966    },
1967    ChallengeResult {
1968        request_id: String,
1969        self_trade_prevention: PlatformSelfTradePrevention,
1970        prevented_order_ids: Vec<String>,
1971        effective_request: PlatformOrderChallengeRequest,
1972        response: PlatformOrderChallengeResponse,
1973    },
1974    PrepareResult {
1975        request_id: String,
1976        response: PlatformOrderPrepareResponse,
1977    },
1978    SubmitResult {
1979        request_id: String,
1980        response: PlatformOrderSubmitResponse,
1981    },
1982    StatusResult {
1983        request_id: String,
1984        response: PlatformOrderStatusResponse,
1985    },
1986    DeadManResult {
1987        request_id: String,
1988        state: PlatformDeadManState,
1989    },
1990    CommandError {
1991        request_id: String,
1992        error: PublicOperationError,
1993    },
1994    Heartbeat,
1995}
1996
1997#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
1998#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
1999pub enum PlatformOrderCommandServerFrame {
2000    EventBatch {
2001        schema_version: u16,
2002        contract_version: String,
2003        market_id: String,
2004        stream_id: String,
2005        first_sequence: String,
2006        previous_sequence: String,
2007        server_time_ms: u64,
2008        events: Vec<PlatformOrderCommandBatchEvent>,
2009    },
2010}
2011
2012#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2013#[serde(rename_all = "snake_case")]
2014pub enum PlatformDeadManStatus {
2015    Armed,
2016    Triggering,
2017    Triggered,
2018    Disarmed,
2019    Expired,
2020    Failed,
2021}
2022
2023#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2024#[serde(deny_unknown_fields)]
2025pub struct PlatformDeadManState {
2026    pub status: PlatformDeadManStatus,
2027    pub timeout_ms: u64,
2028    pub heartbeat_deadline_ms: u64,
2029    pub order_control_id: Option<String>,
2030    pub signature: Option<String>,
2031    pub failure_code: Option<String>,
2032    pub updated_at_ms: u64,
2033}
2034
2035/// One sequenced event emitted by the persistent order-control connection.
2036/// After authentication, the transport carries bounded arrays of these events
2037/// so concurrent results share frame overhead without weakening per-event
2038/// sequence or request correlation. Terminal chain status may arrive later
2039/// without blocking command submission.
2040#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2041#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2042pub enum PlatformOrderCommandEvent {
2043    AuthChallenge {
2044        schema_version: u16,
2045        contract_version: String,
2046        market_id: String,
2047        challenge: String,
2048        server_time_ms: u64,
2049        expires_at_ms: u64,
2050    },
2051    Ready {
2052        schema_version: u16,
2053        contract_version: String,
2054        market_id: String,
2055        stream_id: String,
2056        sequence: String,
2057        server_time_ms: u64,
2058    },
2059    ProbeResult {
2060        schema_version: u16,
2061        contract_version: String,
2062        market_id: String,
2063        stream_id: String,
2064        sequence: String,
2065        previous_sequence: String,
2066        request_id: String,
2067        nonce: String,
2068        server_time_ms: u64,
2069    },
2070    ChallengeResult {
2071        schema_version: u16,
2072        contract_version: String,
2073        market_id: String,
2074        stream_id: String,
2075        sequence: String,
2076        previous_sequence: String,
2077        request_id: String,
2078        self_trade_prevention: PlatformSelfTradePrevention,
2079        prevented_order_ids: Vec<String>,
2080        effective_request: PlatformOrderChallengeRequest,
2081        response: PlatformOrderChallengeResponse,
2082        server_time_ms: u64,
2083    },
2084    PrepareResult {
2085        schema_version: u16,
2086        contract_version: String,
2087        market_id: String,
2088        stream_id: String,
2089        sequence: String,
2090        previous_sequence: String,
2091        request_id: String,
2092        response: PlatformOrderPrepareResponse,
2093        server_time_ms: u64,
2094    },
2095    SubmitResult {
2096        schema_version: u16,
2097        contract_version: String,
2098        market_id: String,
2099        stream_id: String,
2100        sequence: String,
2101        previous_sequence: String,
2102        request_id: String,
2103        response: PlatformOrderSubmitResponse,
2104        server_time_ms: u64,
2105    },
2106    StatusResult {
2107        schema_version: u16,
2108        contract_version: String,
2109        market_id: String,
2110        stream_id: String,
2111        sequence: String,
2112        previous_sequence: String,
2113        request_id: String,
2114        response: PlatformOrderStatusResponse,
2115        server_time_ms: u64,
2116    },
2117    DeadManResult {
2118        schema_version: u16,
2119        contract_version: String,
2120        market_id: String,
2121        stream_id: String,
2122        sequence: String,
2123        previous_sequence: String,
2124        request_id: String,
2125        state: PlatformDeadManState,
2126        server_time_ms: u64,
2127    },
2128    CommandError {
2129        schema_version: u16,
2130        contract_version: String,
2131        market_id: String,
2132        stream_id: String,
2133        sequence: String,
2134        previous_sequence: String,
2135        request_id: String,
2136        error: PublicOperationError,
2137        server_time_ms: u64,
2138    },
2139    Heartbeat {
2140        schema_version: u16,
2141        contract_version: String,
2142        market_id: String,
2143        stream_id: String,
2144        sequence: String,
2145        previous_sequence: String,
2146        server_time_ms: u64,
2147    },
2148}
2149
2150#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2151#[serde(deny_unknown_fields)]
2152pub struct PlatformAccountOrder {
2153    pub order_id: String,
2154    pub side: PlatformTradeSide,
2155    pub order_type: PlatformOrderType,
2156    pub state: PlatformOrderState,
2157    pub limit_price_atoms: String,
2158    pub original_size_atoms: String,
2159    pub remaining_size_atoms: String,
2160}
2161
2162#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2163#[serde(deny_unknown_fields)]
2164pub struct PlatformAccountFill {
2165    pub fill_id: String,
2166    pub side: PlatformTradeSide,
2167    pub price_atoms: String,
2168    pub size_atoms: String,
2169    pub fee_quote_atoms: String,
2170    pub fee_is_final: bool,
2171    pub settlement: PlatformSettlementState,
2172    pub executed_at_ms: u64,
2173    pub confirmed_at_ms: Option<u64>,
2174    pub transaction_id: Option<String>,
2175    pub realized_pnl_quote_atoms: String,
2176}
2177
2178#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2179#[serde(deny_unknown_fields)]
2180pub struct PlatformAccountSnapshotResponse {
2181    pub schema_version: u16,
2182    pub contract_version: String,
2183    pub market_id: String,
2184    pub wallet_address: String,
2185    pub server_time_ms: u64,
2186    pub orders: Vec<PlatformAccountOrder>,
2187    pub fills: Vec<PlatformAccountFill>,
2188}
2189
2190#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2191#[serde(rename_all = "snake_case")]
2192pub enum PlatformMakerReputationTier {
2193    Probation,
2194    Bronze,
2195    Silver,
2196    Gold,
2197    Platinum,
2198}
2199
2200#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2201#[serde(deny_unknown_fields)]
2202pub struct PlatformMakerTierProgress {
2203    pub next_tier: Option<PlatformMakerReputationTier>,
2204    pub reputation_score_required: Option<u16>,
2205    pub reputation_score_remaining: u16,
2206    pub quote_requests_required: Option<String>,
2207    pub quote_requests_remaining: String,
2208    pub stake_atoms_required: Option<String>,
2209    pub stake_atoms_remaining: String,
2210    pub tenure_slots_required: Option<String>,
2211    pub tenure_slots_remaining: String,
2212}
2213
2214/// Authenticated, privacy-preserving reliability and participation record for the
2215/// requesting maker. All potentially large counters and atomic quantities are
2216/// decimal strings so JavaScript agents never lose integer precision.
2217#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2218#[serde(deny_unknown_fields)]
2219pub struct PlatformMakerReputationResponse {
2220    pub schema_version: u16,
2221    pub contract_version: String,
2222    pub market_id: String,
2223    pub maker_id: String,
2224    pub wallet_address: String,
2225    pub active: bool,
2226    pub tier: PlatformMakerReputationTier,
2227    pub reputation_score: u16,
2228    pub total_quote_requests: String,
2229    pub successful_fills: String,
2230    pub missed_quote_requests: String,
2231    pub fill_rate_bps: u16,
2232    pub consecutive_misses: u16,
2233    pub lifetime_filled_quote_atoms: String,
2234    pub distinct_counterparties: u16,
2235    pub recent_average_latency_ms: u16,
2236    pub configured_minimum_spread_bps: u16,
2237    pub weighted_average_spread_bps: u16,
2238    pub stake_atoms: String,
2239    pub epoch_start_stake_atoms: String,
2240    pub epoch_slashed_atoms: String,
2241    pub epoch_slashed_bps: u16,
2242    pub lifetime_auto_slashed_atoms: String,
2243    pub registered_slot: String,
2244    pub last_active_slot: String,
2245    pub last_settled_slot: String,
2246    pub revoked_at_slot: Option<String>,
2247    pub tenure_slots: String,
2248    pub signed_quote_stream_eligible: bool,
2249    pub minimum_quote_interval_ms: Option<u16>,
2250    pub tier_progress: PlatformMakerTierProgress,
2251    pub server_time_ms: u64,
2252}
2253
2254#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2255#[serde(rename_all = "snake_case")]
2256pub enum PlatformMakerSide {
2257    Buy,
2258    Sell,
2259}
2260
2261#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2262#[serde(rename_all = "snake_case")]
2263pub enum PlatformOracleHealth {
2264    Fresh,
2265    Stale,
2266    Unknown,
2267}
2268
2269/// The maker's resting firm orders in this market, summarised by side.
2270#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2271#[serde(deny_unknown_fields)]
2272pub struct PlatformMakerFirmOrderSummary {
2273    pub resting_orders: u32,
2274    pub bid_orders: u32,
2275    pub ask_orders: u32,
2276    pub bid_size_atoms: String,
2277    pub ask_size_atoms: String,
2278}
2279
2280/// One of the maker's own live signed quotes in the streaming lane.
2281#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2282#[serde(deny_unknown_fields)]
2283pub struct PlatformMakerSignedQuote {
2284    pub side: PlatformMakerSide,
2285    pub price_atoms: String,
2286    pub size_atoms: String,
2287    pub nonce: String,
2288    pub issued_at_ms: u64,
2289    pub expires_at_ms: u64,
2290}
2291
2292/// The maker's own intent product in this market.
2293#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2294#[serde(deny_unknown_fields)]
2295pub struct PlatformMakerIntentStatus {
2296    pub active: bool,
2297    pub side: PlatformMakerSide,
2298    pub minimum_price_atoms: String,
2299    pub maximum_price_atoms: String,
2300    pub maximum_fill_size_atoms: String,
2301    /// Fill budget still available after in-flight reservations.
2302    pub remaining_fill_size_atoms: String,
2303    pub minimum_spread_bps: u16,
2304    pub stake_atoms: String,
2305}
2306
2307/// The maker's signed-quote lane: eligibility and its own live quotes.
2308#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2309#[serde(deny_unknown_fields)]
2310pub struct PlatformMakerSignedQuoteLane {
2311    pub eligible: bool,
2312    pub live_quotes: Vec<PlatformMakerSignedQuote>,
2313}
2314
2315#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2316#[serde(deny_unknown_fields)]
2317pub struct PlatformMakerStrandLevel {
2318    /// Null when the configured offset overflows the price range.
2319    pub price_atoms: Option<String>,
2320    pub size_atoms: String,
2321    pub remaining_size_atoms: String,
2322}
2323
2324/// One of the maker's own Strands in this market.
2325#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2326#[serde(deny_unknown_fields)]
2327pub struct PlatformMakerStrandStatus {
2328    pub enabled: bool,
2329    pub async_only: bool,
2330    /// True once the chain would reject fills because `valid_until_slot` passed.
2331    pub expired: bool,
2332    pub mid_price_atoms: String,
2333    pub tick_size_atoms: String,
2334    /// Null means the Strand never expires.
2335    pub valid_until_slot: Option<String>,
2336    pub bids: Vec<PlatformMakerStrandLevel>,
2337    pub asks: Vec<PlatformMakerStrandLevel>,
2338    pub maximum_exposure_atoms: String,
2339    pub remaining_exposure_atoms: String,
2340}
2341
2342/// One of the maker's own Currents in this market.
2343#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2344#[serde(deny_unknown_fields)]
2345pub struct PlatformMakerCurrentStatus {
2346    pub enabled: bool,
2347    pub async_only: bool,
2348    pub expired: bool,
2349    pub half_spread_bps: u16,
2350    pub band_step_bps: u16,
2351    pub maximum_confidence_bps: u16,
2352    pub maximum_oracle_age_seconds: u32,
2353    pub sync_spread_bps: u16,
2354    /// Null means the Current never expires.
2355    pub valid_until_slot: Option<String>,
2356    pub bid_depth_atoms: Vec<String>,
2357    pub ask_depth_atoms: Vec<String>,
2358    pub maximum_exposure_atoms: String,
2359    pub remaining_exposure_atoms: String,
2360    /// Freshness class of the market oracle the Current settles against.
2361    pub oracle_health: PlatformOracleHealth,
2362}
2363
2364/// One durable dead-man guard the owner armed for a session in this market.
2365#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2366#[serde(deny_unknown_fields)]
2367pub struct PlatformMakerDeadManGuard {
2368    pub session_public_key: String,
2369    pub status: PlatformDeadManStatus,
2370    pub timeout_ms: u64,
2371    pub heartbeat_deadline_ms: u64,
2372    pub updated_at_ms: u64,
2373}
2374
2375/// Authenticated, owner-scoped view of the maker's Strata products in one
2376/// market: firm orders, intent, Strands, Currents, the signed-quote lane, live
2377/// exposure, health, and kill state. Nothing about other makers, takers, or
2378/// liquidity sources crosses this boundary.
2379#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2380#[serde(deny_unknown_fields)]
2381pub struct PlatformMakerStatusResponse {
2382    pub schema_version: u16,
2383    pub contract_version: String,
2384    pub market_id: String,
2385    pub maker_id: String,
2386    pub wallet_address: String,
2387    pub server_time_ms: u64,
2388    pub current_slot: String,
2389    pub firm_orders: PlatformMakerFirmOrderSummary,
2390    pub intent: Option<PlatformMakerIntentStatus>,
2391    pub signed_quotes: PlatformMakerSignedQuoteLane,
2392    pub strands: Vec<PlatformMakerStrandStatus>,
2393    pub currents: Vec<PlatformMakerCurrentStatus>,
2394    pub dead_man_guards: Vec<PlatformMakerDeadManGuard>,
2395    /// Count of maker products currently able to fill: an active intent, each
2396    /// enabled unexpired Strand or Current, and resting firm orders (as one).
2397    pub active_products: u16,
2398}
2399
2400/// Which Strata maker product produced a maker-side fill.
2401#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2402#[serde(rename_all = "snake_case")]
2403pub enum PlatformMakerProduct {
2404    FirmOrder,
2405    Intent,
2406    Strand,
2407    Current,
2408}
2409
2410/// One maker-side fill: the same sanitized settlement view as an account fill
2411/// plus the maker product that produced it. No counterparty or venue.
2412#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2413#[serde(deny_unknown_fields)]
2414pub struct PlatformMakerFill {
2415    pub fill_id: String,
2416    pub product: PlatformMakerProduct,
2417    pub side: PlatformTradeSide,
2418    pub price_atoms: String,
2419    pub size_atoms: String,
2420    pub fee_quote_atoms: String,
2421    pub fee_is_final: bool,
2422    pub settlement: PlatformSettlementState,
2423    pub executed_at_ms: u64,
2424    pub confirmed_at_ms: Option<u64>,
2425    pub transaction_id: Option<String>,
2426    pub realized_pnl_quote_atoms: String,
2427}
2428
2429/// Authenticated, sequenced owner-only maker stream (`mm.fills.stream`).
2430/// After the signed challenge the server sends one `maker_snapshot`, then
2431/// sequenced `maker_fill`, `maker_status` (exposure/product change), and
2432/// `heartbeat` events; a recovery snapshot advances the sequence on the same
2433/// stream identity.
2434#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2435#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2436pub enum PlatformMakerEvent {
2437    AuthChallenge {
2438        schema_version: u16,
2439        contract_version: String,
2440        market_id: String,
2441        wallet_address: String,
2442        challenge: String,
2443        server_time_ms: u64,
2444        expires_at_ms: u64,
2445    },
2446    MakerSnapshot {
2447        schema_version: u16,
2448        contract_version: String,
2449        market_id: String,
2450        wallet_address: String,
2451        stream_id: String,
2452        sequence: String,
2453        server_time_ms: u64,
2454        status: PlatformMakerStatusResponse,
2455        fills: Vec<PlatformMakerFill>,
2456    },
2457    MakerFill {
2458        schema_version: u16,
2459        contract_version: String,
2460        market_id: String,
2461        wallet_address: String,
2462        stream_id: String,
2463        sequence: String,
2464        previous_sequence: String,
2465        server_time_ms: u64,
2466        fill: PlatformMakerFill,
2467    },
2468    MakerStatus {
2469        schema_version: u16,
2470        contract_version: String,
2471        market_id: String,
2472        wallet_address: String,
2473        stream_id: String,
2474        sequence: String,
2475        previous_sequence: String,
2476        server_time_ms: u64,
2477        status: PlatformMakerStatusResponse,
2478    },
2479    Heartbeat {
2480        schema_version: u16,
2481        contract_version: String,
2482        market_id: String,
2483        wallet_address: String,
2484        stream_id: String,
2485        sequence: String,
2486        previous_sequence: String,
2487        server_time_ms: u64,
2488    },
2489}
2490
2491#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2492#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2493pub enum PlatformAccountEvent {
2494    AuthChallenge {
2495        schema_version: u16,
2496        contract_version: String,
2497        market_id: String,
2498        wallet_address: String,
2499        challenge: String,
2500        server_time_ms: u64,
2501        expires_at_ms: u64,
2502    },
2503    AccountSnapshot {
2504        schema_version: u16,
2505        contract_version: String,
2506        market_id: String,
2507        wallet_address: String,
2508        stream_id: String,
2509        sequence: String,
2510        server_time_ms: u64,
2511        orders: Vec<PlatformAccountOrder>,
2512        fills: Vec<PlatformAccountFill>,
2513    },
2514    OrdersSnapshot {
2515        schema_version: u16,
2516        contract_version: String,
2517        market_id: String,
2518        wallet_address: String,
2519        stream_id: String,
2520        sequence: String,
2521        previous_sequence: String,
2522        server_time_ms: u64,
2523        orders: Vec<PlatformAccountOrder>,
2524    },
2525    Fill {
2526        schema_version: u16,
2527        contract_version: String,
2528        market_id: String,
2529        wallet_address: String,
2530        stream_id: String,
2531        sequence: String,
2532        previous_sequence: String,
2533        server_time_ms: u64,
2534        fill: PlatformAccountFill,
2535    },
2536    Heartbeat {
2537        schema_version: u16,
2538        contract_version: String,
2539        market_id: String,
2540        wallet_address: String,
2541        stream_id: String,
2542        sequence: String,
2543        previous_sequence: String,
2544        server_time_ms: u64,
2545    },
2546}
2547
2548#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2549#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2550pub enum PlatformMarketDataEvent {
2551    BookSnapshot {
2552        schema_version: u16,
2553        contract_version: String,
2554        market_id: String,
2555        stream_id: String,
2556        sequence: String,
2557        server_time_ms: u64,
2558        snapshot_id: String,
2559        bids: Vec<PlatformBookLevel>,
2560        asks: Vec<PlatformBookLevel>,
2561    },
2562    BookDelta {
2563        schema_version: u16,
2564        contract_version: String,
2565        market_id: String,
2566        stream_id: String,
2567        sequence: String,
2568        previous_sequence: String,
2569        server_time_ms: u64,
2570        changes: Vec<PlatformBookChange>,
2571    },
2572    BestBidAsk {
2573        schema_version: u16,
2574        contract_version: String,
2575        market_id: String,
2576        stream_id: String,
2577        sequence: String,
2578        server_time_ms: u64,
2579        best_bid: Option<PlatformBookLevel>,
2580        best_ask: Option<PlatformBookLevel>,
2581    },
2582    Trade {
2583        schema_version: u16,
2584        contract_version: String,
2585        market_id: String,
2586        server_time_ms: u64,
2587        trade: PlatformTrade,
2588    },
2589    MarketStatus {
2590        schema_version: u16,
2591        contract_version: String,
2592        market_id: String,
2593        server_time_ms: u64,
2594        status: PlatformMarketState,
2595    },
2596    Heartbeat {
2597        schema_version: u16,
2598        contract_version: String,
2599        market_id: String,
2600        server_time_ms: u64,
2601    },
2602}
2603
2604#[cfg(test)]
2605mod tests {
2606    use super::*;
2607
2608    #[test]
2609    fn public_platform_fixtures_decode_strictly() {
2610        let discovery: PlatformDiscoveryResponse =
2611            serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
2612        let service_status: PlatformServiceStatusResponse =
2613            serde_json::from_str(PLATFORM_SERVICE_STATUS_FIXTURE).unwrap();
2614        let graph = PlatformActionGraphResponse::foundation();
2615        let assets: PlatformAssetsResponse = serde_json::from_str(PLATFORM_ASSETS_FIXTURE).unwrap();
2616        let swap_quote: PlatformSwapQuoteResponse =
2617            serde_json::from_str(PLATFORM_SWAP_QUOTE_FIXTURE).unwrap();
2618        let markets: PlatformMarketsResponse =
2619            serde_json::from_str(PLATFORM_MARKETS_FIXTURE).unwrap();
2620        let book: PlatformBookSnapshotResponse =
2621            serde_json::from_str(PLATFORM_BOOK_FIXTURE).unwrap();
2622        let bbo: PlatformBestBidAskResponse = serde_json::from_str(PLATFORM_BBO_FIXTURE).unwrap();
2623        let fees: PlatformFeeScheduleResponse =
2624            serde_json::from_str(PLATFORM_FEES_FIXTURE).unwrap();
2625        let status: PlatformMarketStatusResponse =
2626            serde_json::from_str(PLATFORM_STATUS_FIXTURE).unwrap();
2627        let candles: PlatformCandlesResponse =
2628            serde_json::from_str(PLATFORM_CANDLES_FIXTURE).unwrap();
2629        let mark: PlatformMarkResponse = serde_json::from_str(PLATFORM_MARK_FIXTURE).unwrap();
2630        let execution_status: PlatformExecutionStatusResponse =
2631            serde_json::from_str(PLATFORM_EXECUTION_STATUS_FIXTURE).unwrap();
2632        let twaps: PlatformTwapsResponse = serde_json::from_str(PLATFORM_TWAPS_FIXTURE).unwrap();
2633        let twap_challenge: PlatformTwapChallengeResponse =
2634            serde_json::from_str(PLATFORM_TWAP_CHALLENGE_FIXTURE).unwrap();
2635        let twap_prepare: PlatformTwapPrepareResponse =
2636            serde_json::from_str(PLATFORM_TWAP_PREPARE_FIXTURE).unwrap();
2637        let twap_submit: PlatformTwapSubmitResponse =
2638            serde_json::from_str(PLATFORM_TWAP_SUBMIT_FIXTURE).unwrap();
2639        let portfolio_history: PlatformPortfolioHistoryResponse =
2640            serde_json::from_str(PLATFORM_PORTFOLIO_HISTORY_FIXTURE).unwrap();
2641        let portfolio: PlatformPortfolioResponse =
2642            serde_json::from_str(PLATFORM_PORTFOLIO_FIXTURE).unwrap();
2643        let rewards: PlatformRewardsResponse =
2644            serde_json::from_str(PLATFORM_REWARDS_FIXTURE).unwrap();
2645        let referrals: PlatformReferralsResponse =
2646            serde_json::from_str(PLATFORM_REFERRALS_FIXTURE).unwrap();
2647        let referral_link: PlatformReferralLinkResponse =
2648            serde_json::from_str(PLATFORM_REFERRAL_LINK_FIXTURE).unwrap();
2649        let referral_claim: PlatformReferralClaimResponse =
2650            serde_json::from_str(PLATFORM_REFERRAL_CLAIM_FIXTURE).unwrap();
2651        let vault_status: PlatformVaultStatusResponse =
2652            serde_json::from_str(PLATFORM_VAULT_STATUS_FIXTURE).unwrap();
2653        let vault_pause: PlatformVaultPausePrepareResponse =
2654            serde_json::from_str(PLATFORM_VAULT_PAUSE_PREPARE_FIXTURE).unwrap();
2655        let vault_setup: PlatformVaultSetupPrepareResponse =
2656            serde_json::from_str(PLATFORM_VAULT_SETUP_PREPARE_FIXTURE).unwrap();
2657        let vault_delegate: PlatformVaultDelegatePrepareResponse =
2658            serde_json::from_str(PLATFORM_VAULT_DELEGATE_PREPARE_FIXTURE).unwrap();
2659        let vault_policy: PlatformVaultPolicyPrepareResponse =
2660            serde_json::from_str(PLATFORM_VAULT_POLICY_PREPARE_FIXTURE).unwrap();
2661        let vault_deposit: PlatformVaultDepositPrepareResponse =
2662            serde_json::from_str(PLATFORM_VAULT_DEPOSIT_PREPARE_FIXTURE).unwrap();
2663        let vault_withdraw: PlatformVaultWithdrawPrepareResponse =
2664            serde_json::from_str(PLATFORM_VAULT_WITHDRAW_PREPARE_FIXTURE).unwrap();
2665        let vault_submit: PlatformVaultSubmitResponse =
2666            serde_json::from_str(PLATFORM_VAULT_SUBMIT_FIXTURE).unwrap();
2667        let bugs: PlatformBugsResponse = serde_json::from_str(PLATFORM_BUGS_FIXTURE).unwrap();
2668        let bug_submit: PlatformBugSubmitResponse =
2669            serde_json::from_str(PLATFORM_BUG_SUBMIT_FIXTURE).unwrap();
2670        let trades: PlatformTradesResponse = serde_json::from_str(PLATFORM_TRADES_FIXTURE).unwrap();
2671        let account: PlatformAccountSnapshotResponse =
2672            serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
2673        let maker_reputation: PlatformMakerReputationResponse =
2674            serde_json::from_str(PLATFORM_MAKER_REPUTATION_FIXTURE).unwrap();
2675        let maker_status: PlatformMakerStatusResponse =
2676            serde_json::from_str(PLATFORM_MAKER_STATUS_FIXTURE).unwrap();
2677        let maker_stream: PlatformMakerEvent =
2678            serde_json::from_str(PLATFORM_MAKER_STREAM_FIXTURE).unwrap();
2679        let twap_stream: PlatformTwapEvent =
2680            serde_json::from_str(PLATFORM_TWAP_STREAM_FIXTURE).unwrap();
2681        let execution_stream: PlatformExecutionEvent =
2682            serde_json::from_str(PLATFORM_EXECUTION_STREAM_FIXTURE).unwrap();
2683        let order_challenge: PlatformOrderChallengeResponse =
2684            serde_json::from_str(PLATFORM_ORDER_CHALLENGE_FIXTURE).unwrap();
2685        let order_prepare: PlatformOrderPrepareResponse =
2686            serde_json::from_str(PLATFORM_ORDER_PREPARE_FIXTURE).unwrap();
2687        let order_submit: PlatformOrderSubmitResponse =
2688            serde_json::from_str(PLATFORM_ORDER_SUBMIT_FIXTURE).unwrap();
2689        let order_status: PlatformOrderStatusResponse =
2690            serde_json::from_str(PLATFORM_ORDER_STATUS_FIXTURE).unwrap();
2691
2692        assert_eq!(discovery.schema_version, PLATFORM_SCHEMA_VERSION);
2693        assert_eq!(service_status.status, PlatformServiceState::Operational);
2694        assert_eq!(service_status.available_operations, 59);
2695        assert_eq!(graph.entry_operation_id, "platform.capabilities.read");
2696        assert_eq!(graph.operations.len(), 67);
2697        assert_eq!(maker_reputation.tier, PlatformMakerReputationTier::Gold);
2698        assert_eq!(maker_status.active_products, 3);
2699        match &maker_stream {
2700            PlatformMakerEvent::MakerSnapshot { status, fills, .. } => {
2701                assert_eq!(status.active_products, maker_status.active_products);
2702                assert_eq!(fills.len(), 1);
2703                assert_eq!(fills[0].product, PlatformMakerProduct::Strand);
2704            }
2705            other => panic!("maker stream fixture must be a snapshot, got {other:?}"),
2706        }
2707        match &twap_stream {
2708            PlatformTwapEvent::TwapsSnapshot {
2709                twaps: streamed, ..
2710            } => {
2711                assert_eq!(streamed, &twaps.twaps);
2712            }
2713            other => panic!("twap stream fixture must be a snapshot, got {other:?}"),
2714        }
2715        match &execution_stream {
2716            PlatformExecutionEvent::ExecutionsSnapshot {
2717                executions,
2718                unknown_execution_ids,
2719                ..
2720            } => {
2721                assert_eq!(executions.len(), 2);
2722                assert_eq!(executions[0].execution_id, execution_status.execution_id);
2723                assert_eq!(unknown_execution_ids.len(), 1);
2724            }
2725            other => panic!("execution stream fixture must be a snapshot, got {other:?}"),
2726        }
2727        assert_eq!(maker_status.strands.len(), 1);
2728        assert_eq!(maker_status.currents.len(), 1);
2729        assert!(maker_status
2730            .intent
2731            .as_ref()
2732            .is_some_and(|intent| intent.active));
2733        assert_eq!(portfolio.balances.len(), 2);
2734        assert_eq!(portfolio.positions.len(), 1);
2735        assert!(portfolio.valuation_complete);
2736        assert_eq!(portfolio.equity_usd_micros.as_deref(), Some("439989500"));
2737        assert!(graph
2738            .operations
2739            .iter()
2740            .any(|operation| operation.id == "twap.place.submit"));
2741        assert!(graph
2742            .operations
2743            .iter()
2744            .any(|operation| operation.id == "twap.cancel.submit"));
2745        assert_eq!(discovery.capabilities.len(), 5);
2746        assert!(!discovery.authority.accepts_private_keys);
2747        assert_eq!(assets.assets.len(), 2);
2748        assert_eq!(swap_quote.input_asset_id, assets.assets[0].asset_id);
2749        assert_eq!(swap_quote.output_asset_id, assets.assets[1].asset_id);
2750        assert_eq!(markets.markets.len(), 1);
2751        assert_eq!(markets.markets[0].base_asset_id, assets.assets[0].asset_id);
2752        assert_eq!(markets.markets[0].quote_asset_id, assets.assets[1].asset_id);
2753        assert_eq!(book.sequence, "42");
2754        assert_eq!(bbo.best_bid.unwrap().price_atoms, "149990000");
2755        assert_eq!(fees.maximum_immediate_execution_fee_bps, 10);
2756        assert_eq!(status.status, PlatformMarketState::Active);
2757        assert_eq!(candles.candles.len(), 2);
2758        assert_eq!(mark.price_atoms_per_base_unit.as_deref(), Some("149995000"));
2759        assert_eq!(execution_status.status, PlatformExecutionState::Confirmed);
2760        assert_eq!(
2761            execution_status.settlement,
2762            PlatformSettlementState::Confirmed
2763        );
2764        assert_eq!(twaps.twaps[0].fills.len(), 1);
2765        assert_eq!(twaps.twaps[0].slices_executed, 2);
2766        assert_eq!(twap_challenge.action, PlatformTwapControlAction::Place);
2767        assert_eq!(twap_prepare.twap_id, twap_challenge.twap_id);
2768        assert_eq!(twap_submit.twap_control_id, twap_prepare.twap_control_id);
2769        assert_eq!(portfolio_history.points.len(), 2);
2770        assert_eq!(rewards.standings.len(), 2);
2771        assert!(referrals.enabled);
2772        assert_eq!(referral_link.status, "pending_first_fill");
2773        assert_eq!(referral_claim.status, "requested");
2774        assert_eq!(vault_status.state, PlatformVaultState::Active);
2775        assert_eq!(
2776            vault_status.session.as_ref().unwrap().state,
2777            PlatformVaultSessionState::Active
2778        );
2779        assert!(vault_pause.paused);
2780        assert!(vault_pause.owner_signature_required);
2781        assert_eq!(vault_setup.mode, PlatformVaultSetupMode::Create);
2782        assert!(vault_setup.permanent);
2783        assert_eq!(vault_delegate.action, PlatformVaultDelegateAction::Revoke);
2784        assert!(vault_delegate.owner_signature_required);
2785        assert_eq!(
2786            vault_policy.withdrawal_access.mode,
2787            PlatformVaultWithdrawalMode::Restricted
2788        );
2789        assert!(vault_policy.owner_signature_required);
2790        assert_eq!(vault_deposit.amount_atoms, "10000000");
2791        assert!(vault_deposit.owner_signature_required);
2792        assert_eq!(vault_withdraw.amount_atoms, "5000000");
2793        assert!(vault_withdraw.owner_signature_required);
2794        assert!(vault_withdraw.sponsored);
2795        assert!(vault_withdraw.preparation_id.starts_with("vp_"));
2796        assert_eq!(vault_submit.action, PlatformVaultAction::Deposit);
2797        assert_eq!(
2798            vault_submit.status,
2799            PlatformVaultSubmissionStatus::Submitted
2800        );
2801        assert!(vault_submit.sponsored);
2802        assert_eq!(vault_submit.failure_code, None);
2803        assert_eq!(bugs.reports[0].status, PlatformBugStatus::Confirmed);
2804        assert_eq!(bug_submit.status, PlatformBugStatus::Pending);
2805        assert_eq!(trades.trades.len(), 1);
2806        assert_eq!(account.orders.len(), 1);
2807        assert_eq!(account.fills.len(), 1);
2808        assert_eq!(order_challenge.action, PlatformOrderAction::Place);
2809        assert_eq!(order_prepare.order_ids, order_challenge.order_ids);
2810        assert_eq!(order_submit.order_ids, order_challenge.order_ids);
2811        assert_eq!(order_status.order_control_id, order_submit.order_control_id);
2812        assert_eq!(order_status.status, PlatformOrderControlStatus::Submitting);
2813    }
2814
2815    #[test]
2816    fn public_platform_response_rejects_unreviewed_fields() {
2817        let mut value: serde_json::Value =
2818            serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
2819        value
2820            .as_object_mut()
2821            .unwrap()
2822            .insert("unexpected_field".to_owned(), serde_json::Value::Bool(true));
2823        assert!(serde_json::from_value::<PlatformDiscoveryResponse>(value).is_err());
2824
2825        let mut account_event: serde_json::Value =
2826            serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
2827        let event = account_event.as_object_mut().unwrap();
2828        event.insert("type".to_owned(), serde_json::json!("account_snapshot"));
2829        event.insert(
2830            "stream_id".to_owned(),
2831            serde_json::json!("account_stream_66666666666666666666666666666666"),
2832        );
2833        event.insert("sequence".to_owned(), serde_json::json!("1"));
2834        event.insert("unexpected_field".to_owned(), serde_json::json!(true));
2835        assert!(serde_json::from_value::<PlatformAccountEvent>(account_event).is_err());
2836    }
2837
2838    #[test]
2839    fn platform_graph_availability_is_projected_from_live_capabilities() {
2840        let mut graph = PlatformActionGraphResponse::foundation();
2841        let live = std::collections::BTreeSet::from([
2842            "platform.discover".to_owned(),
2843            "graphs.read".to_owned(),
2844            "orders.replace".to_owned(),
2845        ]);
2846
2847        graph.project_availability(&live);
2848
2849        for operation in &graph.operations {
2850            assert_eq!(
2851                operation.available,
2852                live.contains(&operation.capability_id),
2853                "operation {} did not follow capability {}",
2854                operation.id,
2855                operation.capability_id,
2856            );
2857        }
2858        assert!(graph
2859            .workflows
2860            .iter()
2861            .flat_map(|workflow| &workflow.nodes)
2862            .filter(|node| node.kind != PlatformActionKind::ExternalSignature)
2863            .all(|node| {
2864                node.available
2865                    == node
2866                        .capability_id
2867                        .as_ref()
2868                        .is_some_and(|capability_id| live.contains(capability_id))
2869            }));
2870        assert!(graph
2871            .workflows
2872            .iter()
2873            .flat_map(|workflow| &workflow.nodes)
2874            .filter(|node| node.kind == PlatformActionKind::ExternalSignature)
2875            .all(|node| node.available));
2876    }
2877
2878    #[test]
2879    fn atomic_order_batch_request_is_strict_and_typed() {
2880        let request: PlatformOrderChallengeRequest = serde_json::from_value(serde_json::json!({
2881            "action": "batch",
2882            "owner_wallet": "11111111111111111111111111111111",
2883            "session_public_key": "22222222222222222222222222222222",
2884            "operations": [
2885                {
2886                    "action": "cancel",
2887                    "order_id": "order_11111111111111111111111111111111"
2888                },
2889                {
2890                    "action": "replace",
2891                    "order_id": "order_22222222222222222222222222222222",
2892                    "account_sequence": "8",
2893                    "client_order_id": "replacement-8",
2894                    "side": "sell",
2895                    "order_type": "post_only",
2896                    "limit_price_atoms": "151000000",
2897                    "size_atoms": "2000000"
2898                }
2899            ]
2900        }))
2901        .unwrap();
2902        let PlatformOrderChallengeRequest::Batch { operations, .. } = request else {
2903            panic!("expected batch request");
2904        };
2905        assert_eq!(operations.len(), 2);
2906        assert!(matches!(
2907            &operations[1],
2908            PlatformOrderBatchOperation::Replace { account_sequence: Some(sequence), .. }
2909                if sequence == "8"
2910        ));
2911
2912        // The account sequence is optional: Strata resolves it from the Vault's
2913        // confirmed market account when omitted, and omitted stays omitted on
2914        // the wire so older servers reject rather than misread it.
2915        let place: PlatformOrderChallengeRequest = serde_json::from_value(serde_json::json!({
2916            "action": "place",
2917            "owner_wallet": "11111111111111111111111111111111",
2918            "session_public_key": "22222222222222222222222222222222",
2919            "client_order_id": "first-order",
2920            "side": "buy",
2921            "order_type": "post_only",
2922            "limit_price_atoms": "150000000",
2923            "size_atoms": "1000000"
2924        }))
2925        .unwrap();
2926        assert!(matches!(
2927            place,
2928            PlatformOrderChallengeRequest::Place {
2929                account_sequence: None,
2930                ..
2931            }
2932        ));
2933        assert!(!serde_json::to_string(&place)
2934            .unwrap()
2935            .contains("account_sequence"));
2936
2937        assert!(
2938            serde_json::from_value::<PlatformOrderChallengeRequest>(serde_json::json!({
2939                "action": "batch",
2940                "owner_wallet": "11111111111111111111111111111111",
2941                "session_public_key": "22222222222222222222222222222222",
2942                "operations": [{
2943                    "action": "cancel",
2944                    "order_id": "order_11111111111111111111111111111111",
2945                    "implementation": "hidden"
2946                }]
2947            }))
2948            .is_err()
2949        );
2950    }
2951
2952    #[test]
2953    fn persistent_order_commands_are_strict_and_explicit_about_self_trade_policy() {
2954        let frame: PlatformOrderCommandClientFrame = serde_json::from_value(serde_json::json!({
2955            "type": "command",
2956            "request_id": "agent-1",
2957            "sequence": "1",
2958            "command": {
2959                "type": "challenge",
2960                "self_trade_prevention": "cancel_maker",
2961                "request": {
2962                    "action": "cancel_all",
2963                    "owner_wallet": "11111111111111111111111111111111",
2964                    "session_public_key": "22222222222222222222222222222222"
2965                }
2966            }
2967        }))
2968        .unwrap();
2969        assert!(matches!(
2970            frame,
2971            PlatformOrderCommandClientFrame::Command {
2972                command: PlatformOrderCommand::Challenge {
2973                    self_trade_prevention: PlatformSelfTradePrevention::CancelMaker,
2974                    ..
2975                },
2976                ..
2977            }
2978        ));
2979        assert!(
2980            serde_json::from_value::<PlatformOrderCommandClientFrame>(serde_json::json!({
2981                "type": "command",
2982                "request_id": "agent-1",
2983                "sequence": "1",
2984                "command": {
2985                    "type": "challenge",
2986                    "request": {
2987                        "action": "cancel_all",
2988                        "owner_wallet": "11111111111111111111111111111111",
2989                        "session_public_key": "22222222222222222222222222222222"
2990                    }
2991                }
2992            }))
2993            .is_err()
2994        );
2995    }
2996
2997    #[test]
2998    fn prepare_requests_accept_a_signed_challenge_or_the_operation_itself() {
2999        let signed: PlatformOrderPrepareRequest = serde_json::from_value(serde_json::json!({
3000            "challenge_id": "oc_0123456789abcdef0123456789abcdef",
3001            "authorization_signature": "1111",
3002        }))
3003        .unwrap();
3004        assert!(matches!(signed, PlatformOrderPrepareRequest::Authorized(_)));
3005        let direct: PlatformOrderPrepareRequest = serde_json::from_value(serde_json::json!({
3006            "action": "cancel_all",
3007            "owner_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3008            "session_public_key": "9Uu7cLBgfMk233BAjMvTS8XJy6KbZK7oQ7NXuCTi3Fg2",
3009        }))
3010        .unwrap();
3011        assert!(matches!(
3012            direct,
3013            PlatformOrderPrepareRequest::Direct(PlatformOrderChallengeRequest::CancelAll { .. })
3014        ));
3015        // Neither shape tolerates a stray field.
3016        assert!(
3017            serde_json::from_value::<PlatformOrderPrepareRequest>(serde_json::json!({
3018                "challenge_id": "oc_0123456789abcdef0123456789abcdef",
3019                "authorization_signature": "1111",
3020                "extra": true,
3021            }))
3022            .is_err()
3023        );
3024        let twap: PlatformTwapPrepareRequest = serde_json::from_value(serde_json::json!({
3025            "action": "cancel",
3026            "owner_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3027            "session_public_key": "9Uu7cLBgfMk233BAjMvTS8XJy6KbZK7oQ7NXuCTi3Fg2",
3028            "twap_id": "twap_0123456789abcdef0123456789abcdef",
3029        }))
3030        .unwrap();
3031        assert!(matches!(twap, PlatformTwapPrepareRequest::Direct(_)));
3032        let execution: crate::ExecutionPrepareRequest = serde_json::from_value(serde_json::json!({
3033            "quote_id": "quote_0123456789abcdef0123456789abcdef",
3034            "owner_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3035            "session_public_key": "9Uu7cLBgfMk233BAjMvTS8XJy6KbZK7oQ7NXuCTi3Fg2",
3036        }))
3037        .unwrap();
3038        assert!(matches!(
3039            execution,
3040            crate::ExecutionPrepareRequest::Direct(_)
3041        ));
3042    }
3043}