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/// One maker-owned Strand mutation. Amounts that may exceed JavaScript's safe
2401/// integer range remain canonical unsigned decimal strings on the wire.
2402#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2403#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
2404pub enum PlatformMakerStrandPrepareRequest {
2405    Upsert {
2406        maker_wallet: String,
2407        enabled: bool,
2408        async_only: bool,
2409        sync_spread_ticks: u16,
2410        mid_price_atoms: String,
2411        max_exposure_base_lots: String,
2412        bid_offsets_ticks: Vec<u16>,
2413        ask_offsets_ticks: Vec<u16>,
2414        bid_sizes_base_lots: Vec<String>,
2415        ask_sizes_base_lots: Vec<String>,
2416        valid_until_slot: String,
2417    },
2418    Recenter {
2419        maker_wallet: String,
2420        new_mid_price_atoms: String,
2421        valid_until_slot: String,
2422    },
2423    SetEnabled {
2424        maker_wallet: String,
2425        enabled: bool,
2426    },
2427    Cancel {
2428        maker_wallet: String,
2429    },
2430}
2431
2432/// One maker-owned Current mutation. Current is parameterized around the
2433/// market's configured on-chain reference and therefore has no recenter action.
2434#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2435#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
2436pub enum PlatformMakerCurrentPrepareRequest {
2437    Upsert {
2438        maker_wallet: String,
2439        enabled: bool,
2440        async_only: bool,
2441        half_spread_bps: u16,
2442        band_step_bps: u16,
2443        max_conf_bps: u16,
2444        max_oracle_dev_bps: u16,
2445        max_oracle_age_secs: u32,
2446        sync_spread_bps: u16,
2447        max_exposure_base_atoms: String,
2448        bid_depth_base_atoms: Vec<String>,
2449        ask_depth_base_atoms: Vec<String>,
2450        valid_until_slot: String,
2451    },
2452    Cancel {
2453        maker_wallet: String,
2454    },
2455}
2456
2457#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2458#[serde(rename_all = "snake_case")]
2459pub enum PlatformMakerControlProduct {
2460    Strand,
2461    Current,
2462}
2463
2464#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2465#[serde(rename_all = "snake_case")]
2466pub enum PlatformMakerControlAction {
2467    StrandUpsert,
2468    StrandRecenter,
2469    StrandSetEnabled,
2470    StrandCancel,
2471    CurrentUpsert,
2472    CurrentCancel,
2473}
2474
2475#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2476#[serde(deny_unknown_fields)]
2477pub struct PlatformMakerControlPrepareResponse {
2478    pub schema_version: u16,
2479    pub contract_version: String,
2480    pub maker_control_id: String,
2481    pub market_id: String,
2482    pub maker_wallet: String,
2483    pub product: PlatformMakerControlProduct,
2484    pub action: PlatformMakerControlAction,
2485    /// Unsigned legacy Solana transaction. The maker verifies the exact
2486    /// instruction and fills its only signature slot externally.
2487    pub transaction_base64: String,
2488    pub recent_blockhash: String,
2489    pub last_valid_block_height: u64,
2490    pub expires_at_ms: u64,
2491}
2492
2493#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2494#[serde(deny_unknown_fields)]
2495pub struct PlatformMakerControlSubmitRequest {
2496    pub maker_control_id: String,
2497    pub signed_transaction_base64: String,
2498    pub idempotency_key: String,
2499}
2500
2501#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2502#[serde(rename_all = "snake_case")]
2503pub enum PlatformMakerControlSubmissionStatus {
2504    Submitted,
2505}
2506
2507#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2508#[serde(deny_unknown_fields)]
2509pub struct PlatformMakerControlSubmitResponse {
2510    pub schema_version: u16,
2511    pub contract_version: String,
2512    pub maker_control_id: String,
2513    pub market_id: String,
2514    pub maker_wallet: String,
2515    pub product: PlatformMakerControlProduct,
2516    pub action: PlatformMakerControlAction,
2517    pub signature: String,
2518    pub status: PlatformMakerControlSubmissionStatus,
2519}
2520
2521/// Which Strata maker product produced a maker-side fill.
2522#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
2523#[serde(rename_all = "snake_case")]
2524pub enum PlatformMakerProduct {
2525    FirmOrder,
2526    Intent,
2527    Strand,
2528    Current,
2529}
2530
2531/// One maker-side fill: the same sanitized settlement view as an account fill
2532/// plus the maker product that produced it. No counterparty or venue.
2533#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2534#[serde(deny_unknown_fields)]
2535pub struct PlatformMakerFill {
2536    pub fill_id: String,
2537    pub product: PlatformMakerProduct,
2538    pub side: PlatformTradeSide,
2539    pub price_atoms: String,
2540    pub size_atoms: String,
2541    pub fee_quote_atoms: String,
2542    pub fee_is_final: bool,
2543    pub settlement: PlatformSettlementState,
2544    pub executed_at_ms: u64,
2545    pub confirmed_at_ms: Option<u64>,
2546    pub transaction_id: Option<String>,
2547    pub realized_pnl_quote_atoms: String,
2548}
2549
2550/// Authenticated, sequenced owner-only maker stream (`mm.fills.stream`).
2551/// After the signed challenge the server sends one `maker_snapshot`, then
2552/// sequenced `maker_fill`, `maker_status` (exposure/product change), and
2553/// `heartbeat` events; a recovery snapshot advances the sequence on the same
2554/// stream identity.
2555#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2556#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2557pub enum PlatformMakerEvent {
2558    AuthChallenge {
2559        schema_version: u16,
2560        contract_version: String,
2561        market_id: String,
2562        wallet_address: String,
2563        challenge: String,
2564        server_time_ms: u64,
2565        expires_at_ms: u64,
2566    },
2567    MakerSnapshot {
2568        schema_version: u16,
2569        contract_version: String,
2570        market_id: String,
2571        wallet_address: String,
2572        stream_id: String,
2573        sequence: String,
2574        server_time_ms: u64,
2575        status: PlatformMakerStatusResponse,
2576        fills: Vec<PlatformMakerFill>,
2577    },
2578    MakerFill {
2579        schema_version: u16,
2580        contract_version: String,
2581        market_id: String,
2582        wallet_address: String,
2583        stream_id: String,
2584        sequence: String,
2585        previous_sequence: String,
2586        server_time_ms: u64,
2587        fill: PlatformMakerFill,
2588    },
2589    MakerStatus {
2590        schema_version: u16,
2591        contract_version: String,
2592        market_id: String,
2593        wallet_address: String,
2594        stream_id: String,
2595        sequence: String,
2596        previous_sequence: String,
2597        server_time_ms: u64,
2598        status: PlatformMakerStatusResponse,
2599    },
2600    Heartbeat {
2601        schema_version: u16,
2602        contract_version: String,
2603        market_id: String,
2604        wallet_address: String,
2605        stream_id: String,
2606        sequence: String,
2607        previous_sequence: String,
2608        server_time_ms: u64,
2609    },
2610}
2611
2612#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2613#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2614pub enum PlatformAccountEvent {
2615    AuthChallenge {
2616        schema_version: u16,
2617        contract_version: String,
2618        market_id: String,
2619        wallet_address: String,
2620        challenge: String,
2621        server_time_ms: u64,
2622        expires_at_ms: u64,
2623    },
2624    AccountSnapshot {
2625        schema_version: u16,
2626        contract_version: String,
2627        market_id: String,
2628        wallet_address: String,
2629        stream_id: String,
2630        sequence: String,
2631        server_time_ms: u64,
2632        orders: Vec<PlatformAccountOrder>,
2633        fills: Vec<PlatformAccountFill>,
2634    },
2635    OrdersSnapshot {
2636        schema_version: u16,
2637        contract_version: String,
2638        market_id: String,
2639        wallet_address: String,
2640        stream_id: String,
2641        sequence: String,
2642        previous_sequence: String,
2643        server_time_ms: u64,
2644        orders: Vec<PlatformAccountOrder>,
2645    },
2646    Fill {
2647        schema_version: u16,
2648        contract_version: String,
2649        market_id: String,
2650        wallet_address: String,
2651        stream_id: String,
2652        sequence: String,
2653        previous_sequence: String,
2654        server_time_ms: u64,
2655        fill: PlatformAccountFill,
2656    },
2657    Heartbeat {
2658        schema_version: u16,
2659        contract_version: String,
2660        market_id: String,
2661        wallet_address: String,
2662        stream_id: String,
2663        sequence: String,
2664        previous_sequence: String,
2665        server_time_ms: u64,
2666    },
2667}
2668
2669#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
2670#[serde(tag = "type", rename_all = "snake_case", deny_unknown_fields)]
2671pub enum PlatformMarketDataEvent {
2672    BookSnapshot {
2673        schema_version: u16,
2674        contract_version: String,
2675        market_id: String,
2676        stream_id: String,
2677        sequence: String,
2678        server_time_ms: u64,
2679        snapshot_id: String,
2680        bids: Vec<PlatformBookLevel>,
2681        asks: Vec<PlatformBookLevel>,
2682    },
2683    BookDelta {
2684        schema_version: u16,
2685        contract_version: String,
2686        market_id: String,
2687        stream_id: String,
2688        sequence: String,
2689        previous_sequence: String,
2690        server_time_ms: u64,
2691        changes: Vec<PlatformBookChange>,
2692    },
2693    BestBidAsk {
2694        schema_version: u16,
2695        contract_version: String,
2696        market_id: String,
2697        stream_id: String,
2698        sequence: String,
2699        server_time_ms: u64,
2700        best_bid: Option<PlatformBookLevel>,
2701        best_ask: Option<PlatformBookLevel>,
2702    },
2703    Trade {
2704        schema_version: u16,
2705        contract_version: String,
2706        market_id: String,
2707        server_time_ms: u64,
2708        trade: PlatformTrade,
2709    },
2710    MarketStatus {
2711        schema_version: u16,
2712        contract_version: String,
2713        market_id: String,
2714        server_time_ms: u64,
2715        status: PlatformMarketState,
2716    },
2717    Heartbeat {
2718        schema_version: u16,
2719        contract_version: String,
2720        market_id: String,
2721        server_time_ms: u64,
2722    },
2723}
2724
2725#[cfg(test)]
2726mod tests {
2727    use super::*;
2728
2729    #[test]
2730    fn public_platform_fixtures_decode_strictly() {
2731        let discovery: PlatformDiscoveryResponse =
2732            serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
2733        let service_status: PlatformServiceStatusResponse =
2734            serde_json::from_str(PLATFORM_SERVICE_STATUS_FIXTURE).unwrap();
2735        let graph = PlatformActionGraphResponse::foundation();
2736        let assets: PlatformAssetsResponse = serde_json::from_str(PLATFORM_ASSETS_FIXTURE).unwrap();
2737        let swap_quote: PlatformSwapQuoteResponse =
2738            serde_json::from_str(PLATFORM_SWAP_QUOTE_FIXTURE).unwrap();
2739        let markets: PlatformMarketsResponse =
2740            serde_json::from_str(PLATFORM_MARKETS_FIXTURE).unwrap();
2741        let book: PlatformBookSnapshotResponse =
2742            serde_json::from_str(PLATFORM_BOOK_FIXTURE).unwrap();
2743        let bbo: PlatformBestBidAskResponse = serde_json::from_str(PLATFORM_BBO_FIXTURE).unwrap();
2744        let fees: PlatformFeeScheduleResponse =
2745            serde_json::from_str(PLATFORM_FEES_FIXTURE).unwrap();
2746        let status: PlatformMarketStatusResponse =
2747            serde_json::from_str(PLATFORM_STATUS_FIXTURE).unwrap();
2748        let candles: PlatformCandlesResponse =
2749            serde_json::from_str(PLATFORM_CANDLES_FIXTURE).unwrap();
2750        let mark: PlatformMarkResponse = serde_json::from_str(PLATFORM_MARK_FIXTURE).unwrap();
2751        let execution_status: PlatformExecutionStatusResponse =
2752            serde_json::from_str(PLATFORM_EXECUTION_STATUS_FIXTURE).unwrap();
2753        let twaps: PlatformTwapsResponse = serde_json::from_str(PLATFORM_TWAPS_FIXTURE).unwrap();
2754        let twap_challenge: PlatformTwapChallengeResponse =
2755            serde_json::from_str(PLATFORM_TWAP_CHALLENGE_FIXTURE).unwrap();
2756        let twap_prepare: PlatformTwapPrepareResponse =
2757            serde_json::from_str(PLATFORM_TWAP_PREPARE_FIXTURE).unwrap();
2758        let twap_submit: PlatformTwapSubmitResponse =
2759            serde_json::from_str(PLATFORM_TWAP_SUBMIT_FIXTURE).unwrap();
2760        let portfolio_history: PlatformPortfolioHistoryResponse =
2761            serde_json::from_str(PLATFORM_PORTFOLIO_HISTORY_FIXTURE).unwrap();
2762        let portfolio: PlatformPortfolioResponse =
2763            serde_json::from_str(PLATFORM_PORTFOLIO_FIXTURE).unwrap();
2764        let rewards: PlatformRewardsResponse =
2765            serde_json::from_str(PLATFORM_REWARDS_FIXTURE).unwrap();
2766        let referrals: PlatformReferralsResponse =
2767            serde_json::from_str(PLATFORM_REFERRALS_FIXTURE).unwrap();
2768        let referral_link: PlatformReferralLinkResponse =
2769            serde_json::from_str(PLATFORM_REFERRAL_LINK_FIXTURE).unwrap();
2770        let referral_claim: PlatformReferralClaimResponse =
2771            serde_json::from_str(PLATFORM_REFERRAL_CLAIM_FIXTURE).unwrap();
2772        let vault_status: PlatformVaultStatusResponse =
2773            serde_json::from_str(PLATFORM_VAULT_STATUS_FIXTURE).unwrap();
2774        let vault_pause: PlatformVaultPausePrepareResponse =
2775            serde_json::from_str(PLATFORM_VAULT_PAUSE_PREPARE_FIXTURE).unwrap();
2776        let vault_setup: PlatformVaultSetupPrepareResponse =
2777            serde_json::from_str(PLATFORM_VAULT_SETUP_PREPARE_FIXTURE).unwrap();
2778        let vault_delegate: PlatformVaultDelegatePrepareResponse =
2779            serde_json::from_str(PLATFORM_VAULT_DELEGATE_PREPARE_FIXTURE).unwrap();
2780        let vault_policy: PlatformVaultPolicyPrepareResponse =
2781            serde_json::from_str(PLATFORM_VAULT_POLICY_PREPARE_FIXTURE).unwrap();
2782        let vault_deposit: PlatformVaultDepositPrepareResponse =
2783            serde_json::from_str(PLATFORM_VAULT_DEPOSIT_PREPARE_FIXTURE).unwrap();
2784        let vault_withdraw: PlatformVaultWithdrawPrepareResponse =
2785            serde_json::from_str(PLATFORM_VAULT_WITHDRAW_PREPARE_FIXTURE).unwrap();
2786        let vault_submit: PlatformVaultSubmitResponse =
2787            serde_json::from_str(PLATFORM_VAULT_SUBMIT_FIXTURE).unwrap();
2788        let bugs: PlatformBugsResponse = serde_json::from_str(PLATFORM_BUGS_FIXTURE).unwrap();
2789        let bug_submit: PlatformBugSubmitResponse =
2790            serde_json::from_str(PLATFORM_BUG_SUBMIT_FIXTURE).unwrap();
2791        let trades: PlatformTradesResponse = serde_json::from_str(PLATFORM_TRADES_FIXTURE).unwrap();
2792        let account: PlatformAccountSnapshotResponse =
2793            serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
2794        let maker_reputation: PlatformMakerReputationResponse =
2795            serde_json::from_str(PLATFORM_MAKER_REPUTATION_FIXTURE).unwrap();
2796        let maker_status: PlatformMakerStatusResponse =
2797            serde_json::from_str(PLATFORM_MAKER_STATUS_FIXTURE).unwrap();
2798        let maker_stream: PlatformMakerEvent =
2799            serde_json::from_str(PLATFORM_MAKER_STREAM_FIXTURE).unwrap();
2800        let twap_stream: PlatformTwapEvent =
2801            serde_json::from_str(PLATFORM_TWAP_STREAM_FIXTURE).unwrap();
2802        let execution_stream: PlatformExecutionEvent =
2803            serde_json::from_str(PLATFORM_EXECUTION_STREAM_FIXTURE).unwrap();
2804        let order_challenge: PlatformOrderChallengeResponse =
2805            serde_json::from_str(PLATFORM_ORDER_CHALLENGE_FIXTURE).unwrap();
2806        let order_prepare: PlatformOrderPrepareResponse =
2807            serde_json::from_str(PLATFORM_ORDER_PREPARE_FIXTURE).unwrap();
2808        let order_submit: PlatformOrderSubmitResponse =
2809            serde_json::from_str(PLATFORM_ORDER_SUBMIT_FIXTURE).unwrap();
2810        let order_status: PlatformOrderStatusResponse =
2811            serde_json::from_str(PLATFORM_ORDER_STATUS_FIXTURE).unwrap();
2812
2813        assert_eq!(discovery.schema_version, PLATFORM_SCHEMA_VERSION);
2814        assert_eq!(service_status.status, PlatformServiceState::Operational);
2815        assert_eq!(service_status.available_operations, 59);
2816        assert_eq!(graph.entry_operation_id, "platform.capabilities.read");
2817        assert_eq!(graph.operations.len(), 69);
2818        assert_eq!(maker_reputation.tier, PlatformMakerReputationTier::Gold);
2819        assert_eq!(maker_status.active_products, 3);
2820        match &maker_stream {
2821            PlatformMakerEvent::MakerSnapshot { status, fills, .. } => {
2822                assert_eq!(status.active_products, maker_status.active_products);
2823                assert_eq!(fills.len(), 1);
2824                assert_eq!(fills[0].product, PlatformMakerProduct::Strand);
2825            }
2826            other => panic!("maker stream fixture must be a snapshot, got {other:?}"),
2827        }
2828        match &twap_stream {
2829            PlatformTwapEvent::TwapsSnapshot {
2830                twaps: streamed, ..
2831            } => {
2832                assert_eq!(streamed, &twaps.twaps);
2833            }
2834            other => panic!("twap stream fixture must be a snapshot, got {other:?}"),
2835        }
2836        match &execution_stream {
2837            PlatformExecutionEvent::ExecutionsSnapshot {
2838                executions,
2839                unknown_execution_ids,
2840                ..
2841            } => {
2842                assert_eq!(executions.len(), 2);
2843                assert_eq!(executions[0].execution_id, execution_status.execution_id);
2844                assert_eq!(unknown_execution_ids.len(), 1);
2845            }
2846            other => panic!("execution stream fixture must be a snapshot, got {other:?}"),
2847        }
2848        assert_eq!(maker_status.strands.len(), 1);
2849        assert_eq!(maker_status.currents.len(), 1);
2850        assert!(maker_status
2851            .intent
2852            .as_ref()
2853            .is_some_and(|intent| intent.active));
2854        assert_eq!(portfolio.balances.len(), 2);
2855        assert_eq!(portfolio.positions.len(), 1);
2856        assert!(portfolio.valuation_complete);
2857        assert_eq!(portfolio.equity_usd_micros.as_deref(), Some("439989500"));
2858        assert!(graph
2859            .operations
2860            .iter()
2861            .any(|operation| operation.id == "twap.place.submit"));
2862        assert!(graph
2863            .operations
2864            .iter()
2865            .any(|operation| operation.id == "twap.cancel.submit"));
2866        assert_eq!(discovery.capabilities.len(), 5);
2867        assert!(!discovery.authority.accepts_private_keys);
2868        assert_eq!(assets.assets.len(), 2);
2869        assert_eq!(swap_quote.input_asset_id, assets.assets[0].asset_id);
2870        assert_eq!(swap_quote.output_asset_id, assets.assets[1].asset_id);
2871        assert_eq!(markets.markets.len(), 1);
2872        assert_eq!(markets.markets[0].base_asset_id, assets.assets[0].asset_id);
2873        assert_eq!(markets.markets[0].quote_asset_id, assets.assets[1].asset_id);
2874        assert_eq!(book.sequence, "42");
2875        assert_eq!(bbo.best_bid.unwrap().price_atoms, "149990000");
2876        assert_eq!(fees.maximum_immediate_execution_fee_bps, 10);
2877        assert_eq!(status.status, PlatformMarketState::Active);
2878        assert_eq!(candles.candles.len(), 2);
2879        assert_eq!(mark.price_atoms_per_base_unit.as_deref(), Some("149995000"));
2880        assert_eq!(execution_status.status, PlatformExecutionState::Confirmed);
2881        assert_eq!(
2882            execution_status.settlement,
2883            PlatformSettlementState::Confirmed
2884        );
2885        assert_eq!(twaps.twaps[0].fills.len(), 1);
2886        assert_eq!(twaps.twaps[0].slices_executed, 2);
2887        assert_eq!(twap_challenge.action, PlatformTwapControlAction::Place);
2888        assert_eq!(twap_prepare.twap_id, twap_challenge.twap_id);
2889        assert_eq!(twap_submit.twap_control_id, twap_prepare.twap_control_id);
2890        assert_eq!(portfolio_history.points.len(), 2);
2891        assert_eq!(rewards.standings.len(), 2);
2892        assert!(referrals.enabled);
2893        assert_eq!(referral_link.status, "pending_first_fill");
2894        assert_eq!(referral_claim.status, "requested");
2895        assert_eq!(vault_status.state, PlatformVaultState::Active);
2896        assert_eq!(
2897            vault_status.session.as_ref().unwrap().state,
2898            PlatformVaultSessionState::Active
2899        );
2900        assert!(vault_pause.paused);
2901        assert!(vault_pause.owner_signature_required);
2902        assert_eq!(vault_setup.mode, PlatformVaultSetupMode::Create);
2903        assert!(vault_setup.permanent);
2904        assert_eq!(vault_delegate.action, PlatformVaultDelegateAction::Revoke);
2905        assert!(vault_delegate.owner_signature_required);
2906        assert_eq!(
2907            vault_policy.withdrawal_access.mode,
2908            PlatformVaultWithdrawalMode::Restricted
2909        );
2910        assert!(vault_policy.owner_signature_required);
2911        assert_eq!(vault_deposit.amount_atoms, "10000000");
2912        assert!(vault_deposit.owner_signature_required);
2913        assert_eq!(vault_withdraw.amount_atoms, "5000000");
2914        assert!(vault_withdraw.owner_signature_required);
2915        assert!(vault_withdraw.sponsored);
2916        assert!(vault_withdraw.preparation_id.starts_with("vp_"));
2917        assert_eq!(vault_submit.action, PlatformVaultAction::Deposit);
2918        assert_eq!(
2919            vault_submit.status,
2920            PlatformVaultSubmissionStatus::Submitted
2921        );
2922        assert!(vault_submit.sponsored);
2923        assert_eq!(vault_submit.failure_code, None);
2924        assert_eq!(bugs.reports[0].status, PlatformBugStatus::Confirmed);
2925        assert_eq!(bug_submit.status, PlatformBugStatus::Pending);
2926        assert_eq!(trades.trades.len(), 1);
2927        assert_eq!(account.orders.len(), 1);
2928        assert_eq!(account.fills.len(), 1);
2929        assert_eq!(order_challenge.action, PlatformOrderAction::Place);
2930        assert_eq!(order_prepare.order_ids, order_challenge.order_ids);
2931        assert_eq!(order_submit.order_ids, order_challenge.order_ids);
2932        assert_eq!(order_status.order_control_id, order_submit.order_control_id);
2933        assert_eq!(order_status.status, PlatformOrderControlStatus::Submitting);
2934    }
2935
2936    #[test]
2937    fn public_platform_response_rejects_unreviewed_fields() {
2938        let mut value: serde_json::Value =
2939            serde_json::from_str(PLATFORM_CAPABILITIES_FIXTURE).unwrap();
2940        value
2941            .as_object_mut()
2942            .unwrap()
2943            .insert("unexpected_field".to_owned(), serde_json::Value::Bool(true));
2944        assert!(serde_json::from_value::<PlatformDiscoveryResponse>(value).is_err());
2945
2946        let mut account_event: serde_json::Value =
2947            serde_json::from_str(PLATFORM_ACCOUNT_FIXTURE).unwrap();
2948        let event = account_event.as_object_mut().unwrap();
2949        event.insert("type".to_owned(), serde_json::json!("account_snapshot"));
2950        event.insert(
2951            "stream_id".to_owned(),
2952            serde_json::json!("account_stream_66666666666666666666666666666666"),
2953        );
2954        event.insert("sequence".to_owned(), serde_json::json!("1"));
2955        event.insert("unexpected_field".to_owned(), serde_json::json!(true));
2956        assert!(serde_json::from_value::<PlatformAccountEvent>(account_event).is_err());
2957    }
2958
2959    #[test]
2960    fn platform_graph_availability_is_projected_from_live_capabilities() {
2961        let mut graph = PlatformActionGraphResponse::foundation();
2962        let live = std::collections::BTreeSet::from([
2963            "platform.discover".to_owned(),
2964            "graphs.read".to_owned(),
2965            "orders.replace".to_owned(),
2966        ]);
2967
2968        graph.project_availability(&live);
2969
2970        for operation in &graph.operations {
2971            assert_eq!(
2972                operation.available,
2973                live.contains(&operation.capability_id),
2974                "operation {} did not follow capability {}",
2975                operation.id,
2976                operation.capability_id,
2977            );
2978        }
2979        assert!(graph
2980            .workflows
2981            .iter()
2982            .flat_map(|workflow| &workflow.nodes)
2983            .filter(|node| node.kind != PlatformActionKind::ExternalSignature)
2984            .all(|node| {
2985                node.available
2986                    == node
2987                        .capability_id
2988                        .as_ref()
2989                        .is_some_and(|capability_id| live.contains(capability_id))
2990            }));
2991        assert!(graph
2992            .workflows
2993            .iter()
2994            .flat_map(|workflow| &workflow.nodes)
2995            .filter(|node| node.kind == PlatformActionKind::ExternalSignature)
2996            .all(|node| node.available));
2997    }
2998
2999    #[test]
3000    fn atomic_order_batch_request_is_strict_and_typed() {
3001        let request: PlatformOrderChallengeRequest = serde_json::from_value(serde_json::json!({
3002            "action": "batch",
3003            "owner_wallet": "11111111111111111111111111111111",
3004            "session_public_key": "22222222222222222222222222222222",
3005            "operations": [
3006                {
3007                    "action": "cancel",
3008                    "order_id": "order_11111111111111111111111111111111"
3009                },
3010                {
3011                    "action": "replace",
3012                    "order_id": "order_22222222222222222222222222222222",
3013                    "account_sequence": "8",
3014                    "client_order_id": "replacement-8",
3015                    "side": "sell",
3016                    "order_type": "post_only",
3017                    "limit_price_atoms": "151000000",
3018                    "size_atoms": "2000000"
3019                }
3020            ]
3021        }))
3022        .unwrap();
3023        let PlatformOrderChallengeRequest::Batch { operations, .. } = request else {
3024            panic!("expected batch request");
3025        };
3026        assert_eq!(operations.len(), 2);
3027        assert!(matches!(
3028            &operations[1],
3029            PlatformOrderBatchOperation::Replace { account_sequence: Some(sequence), .. }
3030                if sequence == "8"
3031        ));
3032
3033        // The account sequence is optional: Strata resolves it from the Vault's
3034        // confirmed market account when omitted, and omitted stays omitted on
3035        // the wire so older servers reject rather than misread it.
3036        let place: PlatformOrderChallengeRequest = serde_json::from_value(serde_json::json!({
3037            "action": "place",
3038            "owner_wallet": "11111111111111111111111111111111",
3039            "session_public_key": "22222222222222222222222222222222",
3040            "client_order_id": "first-order",
3041            "side": "buy",
3042            "order_type": "post_only",
3043            "limit_price_atoms": "150000000",
3044            "size_atoms": "1000000"
3045        }))
3046        .unwrap();
3047        assert!(matches!(
3048            place,
3049            PlatformOrderChallengeRequest::Place {
3050                account_sequence: None,
3051                ..
3052            }
3053        ));
3054        assert!(!serde_json::to_string(&place)
3055            .unwrap()
3056            .contains("account_sequence"));
3057
3058        assert!(
3059            serde_json::from_value::<PlatformOrderChallengeRequest>(serde_json::json!({
3060                "action": "batch",
3061                "owner_wallet": "11111111111111111111111111111111",
3062                "session_public_key": "22222222222222222222222222222222",
3063                "operations": [{
3064                    "action": "cancel",
3065                    "order_id": "order_11111111111111111111111111111111",
3066                    "implementation": "hidden"
3067                }]
3068            }))
3069            .is_err()
3070        );
3071    }
3072
3073    #[test]
3074    fn persistent_order_commands_are_strict_and_explicit_about_self_trade_policy() {
3075        let frame: PlatformOrderCommandClientFrame = serde_json::from_value(serde_json::json!({
3076            "type": "command",
3077            "request_id": "agent-1",
3078            "sequence": "1",
3079            "command": {
3080                "type": "challenge",
3081                "self_trade_prevention": "cancel_maker",
3082                "request": {
3083                    "action": "cancel_all",
3084                    "owner_wallet": "11111111111111111111111111111111",
3085                    "session_public_key": "22222222222222222222222222222222"
3086                }
3087            }
3088        }))
3089        .unwrap();
3090        assert!(matches!(
3091            frame,
3092            PlatformOrderCommandClientFrame::Command {
3093                command: PlatformOrderCommand::Challenge {
3094                    self_trade_prevention: PlatformSelfTradePrevention::CancelMaker,
3095                    ..
3096                },
3097                ..
3098            }
3099        ));
3100        assert!(
3101            serde_json::from_value::<PlatformOrderCommandClientFrame>(serde_json::json!({
3102                "type": "command",
3103                "request_id": "agent-1",
3104                "sequence": "1",
3105                "command": {
3106                    "type": "challenge",
3107                    "request": {
3108                        "action": "cancel_all",
3109                        "owner_wallet": "11111111111111111111111111111111",
3110                        "session_public_key": "22222222222222222222222222222222"
3111                    }
3112                }
3113            }))
3114            .is_err()
3115        );
3116    }
3117
3118    #[test]
3119    fn prepare_requests_accept_a_signed_challenge_or_the_operation_itself() {
3120        let signed: PlatformOrderPrepareRequest = serde_json::from_value(serde_json::json!({
3121            "challenge_id": "oc_0123456789abcdef0123456789abcdef",
3122            "authorization_signature": "1111",
3123        }))
3124        .unwrap();
3125        assert!(matches!(signed, PlatformOrderPrepareRequest::Authorized(_)));
3126        let direct: PlatformOrderPrepareRequest = serde_json::from_value(serde_json::json!({
3127            "action": "cancel_all",
3128            "owner_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3129            "session_public_key": "9Uu7cLBgfMk233BAjMvTS8XJy6KbZK7oQ7NXuCTi3Fg2",
3130        }))
3131        .unwrap();
3132        assert!(matches!(
3133            direct,
3134            PlatformOrderPrepareRequest::Direct(PlatformOrderChallengeRequest::CancelAll { .. })
3135        ));
3136        // Neither shape tolerates a stray field.
3137        assert!(
3138            serde_json::from_value::<PlatformOrderPrepareRequest>(serde_json::json!({
3139                "challenge_id": "oc_0123456789abcdef0123456789abcdef",
3140                "authorization_signature": "1111",
3141                "extra": true,
3142            }))
3143            .is_err()
3144        );
3145        let twap: PlatformTwapPrepareRequest = serde_json::from_value(serde_json::json!({
3146            "action": "cancel",
3147            "owner_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3148            "session_public_key": "9Uu7cLBgfMk233BAjMvTS8XJy6KbZK7oQ7NXuCTi3Fg2",
3149            "twap_id": "twap_0123456789abcdef0123456789abcdef",
3150        }))
3151        .unwrap();
3152        assert!(matches!(twap, PlatformTwapPrepareRequest::Direct(_)));
3153        let execution: crate::ExecutionPrepareRequest = serde_json::from_value(serde_json::json!({
3154            "quote_id": "quote_0123456789abcdef0123456789abcdef",
3155            "owner_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3156            "session_public_key": "9Uu7cLBgfMk233BAjMvTS8XJy6KbZK7oQ7NXuCTi3Fg2",
3157        }))
3158        .unwrap();
3159        assert!(matches!(
3160            execution,
3161            crate::ExecutionPrepareRequest::Direct(_)
3162        ));
3163    }
3164
3165    #[test]
3166    fn maker_control_requests_are_tagged_exact_and_amount_safe() {
3167        let strand: PlatformMakerStrandPrepareRequest = serde_json::from_value(serde_json::json!({
3168            "action": "recenter",
3169            "maker_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3170            "new_mid_price_atoms": "123000000",
3171            "valid_until_slot": "0"
3172        }))
3173        .unwrap();
3174        assert!(matches!(
3175            strand,
3176            PlatformMakerStrandPrepareRequest::Recenter { .. }
3177        ));
3178
3179        let current: PlatformMakerCurrentPrepareRequest =
3180            serde_json::from_value(serde_json::json!({
3181                "action": "cancel",
3182                "maker_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL"
3183            }))
3184            .unwrap();
3185        assert!(matches!(
3186            current,
3187            PlatformMakerCurrentPrepareRequest::Cancel { .. }
3188        ));
3189        assert!(
3190            serde_json::from_value::<PlatformMakerCurrentPrepareRequest>(serde_json::json!({
3191                "action": "cancel",
3192                "maker_wallet": "5Ji61Fbeb22Yntgv1hhHeSSLgdEdZchHeM1Tv1MjGhSL",
3193                "oracle_price": 123.45
3194            }))
3195            .is_err()
3196        );
3197    }
3198}