Skip to main content

nautilus_dydx/http/
models.rs

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