nautilus_dydx/http/
models.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Data models for dYdX v4 Indexer REST API responses.
17//!
18//! This module contains Rust types that mirror the JSON structures returned
19//! by the dYdX v4 Indexer API endpoints.
20//!
21//! # API Documentation
22//!
23//! - Indexer HTTP API: <https://docs.dydx.exchange/api_integration-indexer/indexer_api>
24//! - Markets: <https://docs.dydx.exchange/api_integration-indexer/indexer_api#markets>
25//! - Accounts: <https://docs.dydx.exchange/api_integration-indexer/indexer_api#accounts>
26
27use std::collections::HashMap;
28
29use chrono::{DateTime, Utc};
30use nautilus_model::enums::OrderSide;
31use rust_decimal::Decimal;
32use serde::{Deserialize, Serialize};
33
34/// Deserializes an empty string as None, otherwise as Some(String).
35fn deserialize_empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
36where
37    D: serde::Deserializer<'de>,
38{
39    let s: Option<String> = Option::deserialize(deserializer)?;
40    Ok(s.filter(|s| !s.is_empty()))
41}
42use serde_with::{DisplayFromStr, serde_as};
43use ustr::Ustr;
44
45use crate::common::enums::{
46    DydxCandleResolution, DydxConditionType, DydxFillType, DydxLiquidity, DydxMarketStatus,
47    DydxOrderExecution, DydxOrderStatus, DydxOrderType, DydxPositionSide, DydxPositionStatus,
48    DydxTickerType, DydxTimeInForce, DydxTradeType, DydxTransferType,
49};
50
51////////////////////////////////////////////////////////////////////////////////
52// Markets
53////////////////////////////////////////////////////////////////////////////////
54
55/// Response wrapper for markets endpoint.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct MarketsResponse {
58    /// Map of market ticker to perpetual market data.
59    pub markets: HashMap<String, PerpetualMarket>,
60}
61
62/// Perpetual market definition.
63#[serde_as]
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct PerpetualMarket {
67    /// Unique identifier for the CLOB pair.
68    #[serde_as(as = "DisplayFromStr")]
69    pub clob_pair_id: u32,
70    /// Market ticker (e.g., "BTC-USD").
71    pub ticker: String,
72    /// Market status (ACTIVE, PAUSED, etc.).
73    pub status: DydxMarketStatus,
74    /// Base asset symbol (optional, not always returned by API).
75    #[serde(default)]
76    pub base_asset: Option<String>,
77    /// Quote asset symbol (optional, not always returned by API).
78    #[serde(default)]
79    pub quote_asset: Option<String>,
80    /// Step size for order quantities (minimum increment).
81    #[serde_as(as = "DisplayFromStr")]
82    pub step_size: Decimal,
83    /// Tick size for order prices (minimum increment).
84    #[serde_as(as = "DisplayFromStr")]
85    pub tick_size: Decimal,
86    /// Index price for the market (optional, not always returned by API).
87    #[serde(default)]
88    #[serde_as(as = "Option<DisplayFromStr>")]
89    pub index_price: Option<Decimal>,
90    /// Oracle price for the market.
91    #[serde_as(as = "DisplayFromStr")]
92    pub oracle_price: Decimal,
93    /// Price change over 24 hours.
94    #[serde(rename = "priceChange24H")]
95    #[serde_as(as = "DisplayFromStr")]
96    pub price_change_24h: Decimal,
97    /// Next funding rate.
98    #[serde_as(as = "DisplayFromStr")]
99    pub next_funding_rate: Decimal,
100    /// Next funding time (ISO8601, optional).
101    #[serde(default)]
102    pub next_funding_at: Option<DateTime<Utc>>,
103    /// Minimum order size in base currency (optional).
104    #[serde(default)]
105    #[serde_as(as = "Option<DisplayFromStr>")]
106    pub min_order_size: Option<Decimal>,
107    /// Market type (always PERPETUAL for dYdX v4, optional).
108    #[serde(rename = "type", default)]
109    pub market_type: Option<DydxTickerType>,
110    /// Initial margin fraction.
111    #[serde_as(as = "DisplayFromStr")]
112    pub initial_margin_fraction: Decimal,
113    /// Maintenance margin fraction.
114    #[serde_as(as = "DisplayFromStr")]
115    pub maintenance_margin_fraction: Decimal,
116    /// Base position notional value (optional).
117    #[serde(default)]
118    #[serde_as(as = "Option<DisplayFromStr>")]
119    pub base_position_notional: Option<Decimal>,
120    /// Incremental position size for margin scaling (optional).
121    #[serde(default)]
122    #[serde_as(as = "Option<DisplayFromStr>")]
123    pub incremental_position_size: Option<Decimal>,
124    /// Incremental initial margin fraction (optional).
125    #[serde(default)]
126    #[serde_as(as = "Option<DisplayFromStr>")]
127    pub incremental_initial_margin_fraction: Option<Decimal>,
128    /// Maximum position size (optional).
129    #[serde(default)]
130    #[serde_as(as = "Option<DisplayFromStr>")]
131    pub max_position_size: Option<Decimal>,
132    /// Open interest in base currency.
133    #[serde_as(as = "DisplayFromStr")]
134    pub open_interest: Decimal,
135    /// Atomic resolution (power of 10 for quantum conversion).
136    pub atomic_resolution: i32,
137    /// Quantum conversion exponent (deprecated, use atomic_resolution).
138    pub quantum_conversion_exponent: i32,
139    /// Subticks per tick.
140    pub subticks_per_tick: u32,
141    /// Step base quantums.
142    pub step_base_quantums: u64,
143    /// Is the market in reduce-only mode.
144    #[serde(default)]
145    pub is_reduce_only: bool,
146}
147
148/// Orderbook snapshot response.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct OrderbookResponse {
151    /// Bids (buy orders).
152    pub bids: Vec<OrderbookLevel>,
153    /// Asks (sell orders).
154    pub asks: Vec<OrderbookLevel>,
155}
156
157/// Single level in the orderbook.
158#[serde_as]
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct OrderbookLevel {
161    /// Price level.
162    #[serde_as(as = "DisplayFromStr")]
163    pub price: Decimal,
164    /// Size at this level.
165    #[serde_as(as = "DisplayFromStr")]
166    pub size: Decimal,
167}
168
169/// Response wrapper for trades endpoint.
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct TradesResponse {
172    /// List of trades.
173    pub trades: Vec<Trade>,
174}
175
176/// Individual trade.
177#[serde_as]
178#[derive(Debug, Clone, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct Trade {
181    /// Unique trade ID.
182    pub id: String,
183    /// Order side that was the taker.
184    pub side: OrderSide,
185    /// Trade size in base currency.
186    #[serde_as(as = "DisplayFromStr")]
187    pub size: Decimal,
188    /// Trade price.
189    #[serde_as(as = "DisplayFromStr")]
190    pub price: Decimal,
191    /// Trade timestamp.
192    pub created_at: DateTime<Utc>,
193    /// Height of block containing this trade.
194    #[serde_as(as = "DisplayFromStr")]
195    pub created_at_height: u64,
196    /// Trade type (LIMIT, MARKET, LIQUIDATED, etc.).
197    #[serde(rename = "type")]
198    pub trade_type: DydxTradeType,
199}
200
201/// Response wrapper for candles endpoint.
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct CandlesResponse {
204    /// List of candles.
205    pub candles: Vec<Candle>,
206}
207
208/// OHLCV candle data.
209#[serde_as]
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct Candle {
213    /// Candle start time.
214    pub started_at: DateTime<Utc>,
215    /// Market ticker.
216    pub ticker: String,
217    /// Candle resolution.
218    pub resolution: DydxCandleResolution,
219    /// Opening price.
220    #[serde_as(as = "DisplayFromStr")]
221    pub open: Decimal,
222    /// Highest price.
223    #[serde_as(as = "DisplayFromStr")]
224    pub high: Decimal,
225    /// Lowest price.
226    #[serde_as(as = "DisplayFromStr")]
227    pub low: Decimal,
228    /// Closing price.
229    #[serde_as(as = "DisplayFromStr")]
230    pub close: Decimal,
231    /// Base asset volume.
232    #[serde_as(as = "DisplayFromStr")]
233    pub base_token_volume: Decimal,
234    /// Quote asset volume (USD).
235    #[serde_as(as = "DisplayFromStr")]
236    pub usd_volume: Decimal,
237    /// Number of trades in this candle.
238    pub trades: u64,
239    /// Block height at candle start.
240    #[serde_as(as = "DisplayFromStr")]
241    pub starting_open_interest: Decimal,
242}
243
244////////////////////////////////////////////////////////////////////////////////
245// Accounts
246////////////////////////////////////////////////////////////////////////////////
247
248/// Response for subaccount endpoint.
249#[derive(Debug, Clone, Serialize, Deserialize)]
250pub struct SubaccountResponse {
251    /// Subaccount data.
252    pub subaccount: Subaccount,
253}
254
255/// Subaccount information.
256#[serde_as]
257#[derive(Debug, Clone, Serialize, Deserialize)]
258#[serde(rename_all = "camelCase")]
259pub struct Subaccount {
260    /// Subaccount address (dydx...).
261    pub address: String,
262    /// Subaccount number.
263    pub subaccount_number: u32,
264    /// Account equity in USD.
265    #[serde_as(as = "DisplayFromStr")]
266    pub equity: Decimal,
267    /// Free collateral.
268    #[serde_as(as = "DisplayFromStr")]
269    pub free_collateral: Decimal,
270    /// Open perpetual positions.
271    #[serde(default)]
272    pub open_perpetual_positions: HashMap<String, PerpetualPosition>,
273    /// Asset positions (e.g., USDC).
274    #[serde(default)]
275    pub asset_positions: HashMap<String, AssetPosition>,
276    /// Margin enabled flag.
277    #[serde(default)]
278    pub margin_enabled: bool,
279    /// Last updated height.
280    #[serde_as(as = "DisplayFromStr")]
281    pub updated_at_height: u64,
282    /// Latest processed block height (present in API response).
283    #[serde(default)]
284    #[serde_as(as = "Option<DisplayFromStr>")]
285    pub latest_processed_block_height: Option<u64>,
286}
287
288/// Perpetual position.
289#[serde_as]
290#[derive(Debug, Clone, Serialize, Deserialize)]
291#[serde(rename_all = "camelCase")]
292pub struct PerpetualPosition {
293    /// Market ticker.
294    pub market: String,
295    /// Position status.
296    pub status: DydxPositionStatus,
297    /// Position side (determined by size sign).
298    pub side: OrderSide,
299    /// Position size (negative for short).
300    #[serde_as(as = "DisplayFromStr")]
301    pub size: Decimal,
302    /// Maximum size reached.
303    #[serde_as(as = "DisplayFromStr")]
304    pub max_size: Decimal,
305    /// Average entry price.
306    #[serde_as(as = "DisplayFromStr")]
307    pub entry_price: Decimal,
308    /// Exit price (if closed).
309    #[serde_as(as = "Option<DisplayFromStr>")]
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub exit_price: Option<Decimal>,
312    /// Realized PnL.
313    #[serde_as(as = "DisplayFromStr")]
314    pub realized_pnl: Decimal,
315    /// Creation height.
316    #[serde_as(as = "DisplayFromStr")]
317    pub created_at_height: u64,
318    /// Creation time.
319    pub created_at: DateTime<Utc>,
320    /// Sum of all open order sizes.
321    #[serde_as(as = "DisplayFromStr")]
322    pub sum_open: Decimal,
323    /// Sum of all close order sizes.
324    #[serde_as(as = "DisplayFromStr")]
325    pub sum_close: Decimal,
326    /// Net funding paid/received.
327    #[serde_as(as = "DisplayFromStr")]
328    pub net_funding: Decimal,
329    /// Unrealized PnL.
330    #[serde_as(as = "DisplayFromStr")]
331    pub unrealized_pnl: Decimal,
332    /// Closed time (if closed).
333    #[serde(skip_serializing_if = "Option::is_none")]
334    pub closed_at: Option<DateTime<Utc>>,
335}
336
337/// Asset position (e.g., USDC balance).
338#[serde_as]
339#[derive(Debug, Clone, Serialize, Deserialize)]
340#[serde(rename_all = "camelCase")]
341pub struct AssetPosition {
342    /// Asset symbol.
343    pub symbol: Ustr,
344    /// Position side (API returns LONG/SHORT).
345    pub side: DydxPositionSide,
346    /// Asset size (balance).
347    #[serde_as(as = "DisplayFromStr")]
348    pub size: Decimal,
349    /// Asset ID.
350    pub asset_id: String,
351    /// Subaccount number (present in API response).
352    #[serde(default)]
353    pub subaccount_number: u32,
354}
355
356/// Response for orders endpoint - API returns array directly, not wrapped.
357pub type OrdersResponse = Vec<Order>;
358
359/// Order information.
360#[serde_as]
361#[derive(Debug, Clone, Serialize, Deserialize)]
362#[serde(rename_all = "camelCase")]
363pub struct Order {
364    /// Unique order ID.
365    pub id: String,
366    /// Subaccount ID.
367    pub subaccount_id: String,
368    /// Client-provided order ID.
369    pub client_id: String,
370    /// CLOB pair ID.
371    #[serde_as(as = "DisplayFromStr")]
372    pub clob_pair_id: u32,
373    /// Order side.
374    pub side: OrderSide,
375    /// Order size.
376    #[serde_as(as = "DisplayFromStr")]
377    pub size: Decimal,
378    /// Total filled size.
379    #[serde_as(as = "DisplayFromStr")]
380    pub total_filled: Decimal,
381    /// Limit price.
382    #[serde_as(as = "DisplayFromStr")]
383    pub price: Decimal,
384    /// Order status.
385    pub status: DydxOrderStatus,
386    /// Order type (LIMIT, MARKET, etc.).
387    #[serde(rename = "type")]
388    pub order_type: DydxOrderType,
389    /// Time-in-force.
390    pub time_in_force: DydxTimeInForce,
391    /// Reduce-only flag.
392    pub reduce_only: bool,
393    /// Post-only flag.
394    pub post_only: bool,
395    /// Order flags (bitfield).
396    #[serde_as(as = "DisplayFromStr")]
397    pub order_flags: u32,
398    /// Good-til-block (for short-term orders).
399    #[serde_as(as = "Option<DisplayFromStr>")]
400    #[serde(skip_serializing_if = "Option::is_none")]
401    pub good_til_block: Option<u64>,
402    /// Good-til-time (ISO8601).
403    #[serde(skip_serializing_if = "Option::is_none")]
404    pub good_til_block_time: Option<DateTime<Utc>>,
405    /// Creation height (not present for BEST_EFFORT_OPENED orders).
406    #[serde_as(as = "Option<DisplayFromStr>")]
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub created_at_height: Option<u64>,
409    /// Client metadata.
410    #[serde_as(as = "DisplayFromStr")]
411    pub client_metadata: u32,
412    /// Trigger price (for conditional orders).
413    #[serde_as(as = "Option<DisplayFromStr>")]
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub trigger_price: Option<Decimal>,
416    /// Condition type (STOP_LOSS, TAKE_PROFIT, UNSPECIFIED).
417    #[serde(skip_serializing_if = "Option::is_none")]
418    pub condition_type: Option<DydxConditionType>,
419    /// Conditional order trigger in subticks.
420    #[serde_as(as = "Option<DisplayFromStr>")]
421    #[serde(skip_serializing_if = "Option::is_none")]
422    pub conditional_order_trigger_subticks: Option<u64>,
423    /// Order execution type.
424    #[serde(skip_serializing_if = "Option::is_none")]
425    pub execution: Option<DydxOrderExecution>,
426    /// Updated timestamp (not present for BEST_EFFORT_OPENED orders).
427    #[serde(skip_serializing_if = "Option::is_none")]
428    pub updated_at: Option<DateTime<Utc>>,
429    /// Updated height (not present for BEST_EFFORT_OPENED orders).
430    #[serde_as(as = "Option<DisplayFromStr>")]
431    #[serde(skip_serializing_if = "Option::is_none")]
432    pub updated_at_height: Option<u64>,
433    /// Ticker symbol (e.g., "BTC-USD").
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub ticker: Option<String>,
436    /// Subaccount number.
437    #[serde(default)]
438    pub subaccount_number: u32,
439    /// Order router address (empty string treated as None).
440    #[serde(default, deserialize_with = "deserialize_empty_string_as_none")]
441    #[serde(skip_serializing_if = "Option::is_none")]
442    pub order_router_address: Option<String>,
443}
444
445/// Response for fills endpoint - API returns wrapped in {"fills": [...]}.
446#[derive(Debug, Clone, Serialize, Deserialize)]
447#[serde(rename_all = "camelCase")]
448pub struct FillsResponse {
449    /// Array of fills.
450    pub fills: Vec<Fill>,
451}
452
453/// Order fill information.
454#[serde_as]
455#[derive(Debug, Clone, Serialize, Deserialize)]
456#[serde(rename_all = "camelCase")]
457pub struct Fill {
458    /// Unique fill ID.
459    pub id: String,
460    /// Order side.
461    pub side: OrderSide,
462    /// Liquidity side (MAKER/TAKER).
463    pub liquidity: DydxLiquidity,
464    /// Fill type.
465    #[serde(rename = "type")]
466    pub fill_type: DydxFillType,
467    /// Market ticker.
468    pub market: String,
469    /// Market type.
470    pub market_type: DydxTickerType,
471    /// Fill price.
472    #[serde_as(as = "DisplayFromStr")]
473    pub price: Decimal,
474    /// Fill size.
475    #[serde_as(as = "DisplayFromStr")]
476    pub size: Decimal,
477    /// Fee paid.
478    #[serde_as(as = "DisplayFromStr")]
479    pub fee: Decimal,
480    /// Fill timestamp.
481    pub created_at: DateTime<Utc>,
482    /// Fill height.
483    #[serde_as(as = "DisplayFromStr")]
484    pub created_at_height: u64,
485    /// Order ID.
486    pub order_id: String,
487    /// Client order ID.
488    #[serde_as(as = "DisplayFromStr")]
489    pub client_metadata: u32,
490}
491
492/// Response for transfers endpoint.
493#[derive(Debug, Clone, Serialize, Deserialize)]
494pub struct TransfersResponse {
495    /// List of transfers.
496    pub transfers: Vec<Transfer>,
497}
498
499/// Transfer information.
500#[serde_as]
501#[derive(Debug, Clone, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase")]
503pub struct Transfer {
504    /// Unique transfer ID.
505    pub id: String,
506    /// Transfer type (DEPOSIT, WITHDRAWAL, TRANSFER_OUT, TRANSFER_IN).
507    #[serde(rename = "type")]
508    pub transfer_type: DydxTransferType,
509    /// Sender address.
510    pub sender: TransferAccount,
511    /// Recipient address.
512    pub recipient: TransferAccount,
513    /// Asset symbol.
514    pub asset: String,
515    /// Transfer amount.
516    #[serde_as(as = "DisplayFromStr")]
517    pub amount: Decimal,
518    /// Creation timestamp.
519    pub created_at: DateTime<Utc>,
520    /// Creation height.
521    #[serde_as(as = "DisplayFromStr")]
522    pub created_at_height: u64,
523    /// Transaction hash.
524    pub transaction_hash: String,
525}
526
527/// Transfer account information.
528#[derive(Debug, Clone, Serialize, Deserialize)]
529#[serde(rename_all = "camelCase")]
530pub struct TransferAccount {
531    /// Address.
532    pub address: String,
533    /// Subaccount number.
534    pub subaccount_number: u32,
535}
536
537////////////////////////////////////////////////////////////////////////////////
538// Utility
539////////////////////////////////////////////////////////////////////////////////
540
541/// Response for time endpoint.
542#[derive(Debug, Clone, Serialize, Deserialize)]
543pub struct TimeResponse {
544    /// Current ISO8601 timestamp.
545    pub iso: DateTime<Utc>,
546    /// Current Unix timestamp in milliseconds.
547    #[serde(rename = "epoch")]
548    pub epoch_ms: i64,
549}
550
551/// Response for height endpoint.
552#[serde_as]
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct HeightResponse {
555    /// Current blockchain height.
556    #[serde_as(as = "DisplayFromStr")]
557    pub height: u64,
558    /// Timestamp of the block.
559    pub time: DateTime<Utc>,
560}
561
562////////////////////////////////////////////////////////////////////////////////
563// Execution Models (Node API)
564////////////////////////////////////////////////////////////////////////////////
565
566/// Request to place an order via Node API.
567#[serde_as]
568#[derive(Debug, Clone, Serialize, Deserialize)]
569#[serde(rename_all = "camelCase")]
570pub struct PlaceOrderRequest {
571    /// Subaccount placing the order.
572    pub subaccount: SubaccountId,
573    /// Client-generated order ID.
574    pub client_id: u32,
575    /// Order type flags (bitfield for short-term, reduce-only, etc.).
576    pub order_flags: String,
577    /// CLOB pair ID.
578    pub clob_pair_id: u32,
579    /// Order side.
580    pub side: OrderSide,
581    /// Order size in quantums.
582    pub quantums: u64,
583    /// Order subticks (price representation).
584    pub subticks: u64,
585    /// Time-in-force.
586    pub time_in_force: DydxTimeInForce,
587    /// Good-til-block (for short-term orders).
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub good_til_block: Option<u32>,
590    /// Good-til-block-time (Unix seconds, for stateful orders).
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub good_til_block_time: Option<u32>,
593    /// Reduce-only flag.
594    pub reduce_only: bool,
595    /// Optional authenticator IDs for permissioned keys.
596    #[serde(skip_serializing_if = "Option::is_none")]
597    pub authenticator_ids: Option<Vec<u64>>,
598}
599
600/// Subaccount identifier.
601#[derive(Debug, Clone, Serialize, Deserialize)]
602pub struct SubaccountId {
603    /// Owner address.
604    pub owner: String,
605    /// Subaccount number.
606    pub number: u32,
607}
608
609/// Request to cancel an order.
610#[derive(Debug, Clone, Serialize, Deserialize)]
611#[serde(rename_all = "camelCase")]
612pub struct CancelOrderRequest {
613    /// Subaccount ID.
614    pub subaccount_id: SubaccountId,
615    /// Client order ID to cancel.
616    pub client_id: u32,
617    /// CLOB pair ID.
618    pub clob_pair_id: u32,
619    /// Order flags.
620    pub order_flags: String,
621    /// Good-til-block or good-til-block-time for the cancel.
622    pub good_til_block: Option<u32>,
623    pub good_til_block_time: Option<u32>,
624}
625
626/// Transaction response from Node.
627#[serde_as]
628#[derive(Debug, Clone, Serialize, Deserialize)]
629pub struct TransactionResponse {
630    /// Transaction hash.
631    pub tx_hash: String,
632    /// Block height.
633    #[serde_as(as = "DisplayFromStr")]
634    pub height: u64,
635    /// Result code (0 = success).
636    pub code: u32,
637    /// Raw log output.
638    pub raw_log: String,
639}