Skip to main content

hypercall_db_diesel/
models.rs

1//! Diesel model structs (`Queryable`, `Insertable`, `AsChangeset`) for every
2//! database table.
3//!
4//! These are internal to the crate. Public consumers use the ORM-free domain
5//! types from `hypercall-db` and the trait impls convert at the boundary.
6
7use chrono::{DateTime, NaiveDateTime, Utc};
8use diesel::prelude::*;
9use hypercall_types::WalletAddress;
10use rust_decimal::Decimal;
11use serde::{Deserialize, Serialize};
12use struct_convert::Convert;
13
14// Order infos - static order metadata plus the materialized latest state.
15#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
16#[diesel(table_name = crate::schema::order_infos)]
17pub struct OrderInfoRecord {
18    pub order_id: i64,
19    pub wallet_address: WalletAddress,
20    pub symbol: String,
21    pub side: String,
22    pub price: Decimal,
23    pub size: Decimal,
24    pub tif: String,
25    pub client_id: Option<String>,
26    pub is_perp: bool,
27    pub underlying: Option<String>,
28    pub reduce_only: Option<bool>,
29    pub nonce: Option<i64>,
30    pub signature: Option<String>,
31    pub mmp_enabled: bool,
32    pub timestamp: i64,
33    pub status: String,
34    pub filled_size: Decimal,
35    pub created_at: Option<NaiveDateTime>,
36    pub updated_at: NaiveDateTime,
37    #[serde(skip_serializing, skip_deserializing)]
38    pub last_materialized_order_update_id: i32,
39}
40
41#[derive(Debug, Clone, Insertable)]
42#[diesel(table_name = crate::schema::order_infos)]
43pub struct NewOrderInfo {
44    pub order_id: i64,
45    pub wallet_address: WalletAddress,
46    pub symbol: String,
47    pub side: String,
48    pub price: Decimal,
49    pub size: Decimal,
50    pub tif: String,
51    pub client_id: Option<String>,
52    pub is_perp: bool,
53    pub underlying: Option<String>,
54    pub reduce_only: Option<bool>,
55    pub nonce: Option<i64>,
56    pub signature: Option<String>,
57    pub mmp_enabled: bool,
58    pub timestamp: i64,
59    pub status: String,
60    pub filled_size: Decimal,
61    pub last_materialized_order_update_id: i32,
62}
63
64// Trades - public trade information
65#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
66#[diesel(table_name = crate::schema::trades)]
67pub struct Trade {
68    pub trade_id: i64,
69    pub symbol: String,
70    pub price: Decimal,
71    pub size: Decimal,
72    pub maker_address: WalletAddress,
73    pub taker_address: WalletAddress,
74    pub maker_fee: Decimal,
75    pub taker_fee: Decimal,
76    pub timestamp: i64,
77    pub created_at: Option<NaiveDateTime>,
78}
79
80#[derive(Debug, Clone, Insertable)]
81#[diesel(table_name = crate::schema::trades)]
82pub struct NewTrade {
83    pub trade_id: i64,
84    pub symbol: String,
85    pub price: Decimal,
86    pub size: Decimal,
87    pub maker_address: WalletAddress,
88    pub taker_address: WalletAddress,
89    pub maker_fee: Decimal,
90    pub taker_fee: Decimal,
91    pub timestamp: i64,
92}
93
94// Fills - wallet-specific fill information
95#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
96#[diesel(table_name = crate::schema::fills)]
97pub struct Fill {
98    pub fill_id: Option<i64>,
99    pub trade_id: i64,
100    pub wallet_address: WalletAddress,
101    pub symbol: String,
102    pub price: Decimal,
103    pub size: Decimal,
104    pub fee: Decimal,
105    pub is_taker: bool,
106    pub timestamp: i64,
107    pub builder_code_address: Option<WalletAddress>,
108    pub builder_code_fee: Option<Decimal>,
109    pub realized_pnl: Option<Decimal>,
110    pub underlying_notional: Option<Decimal>,
111    pub created_at: Option<NaiveDateTime>,
112}
113
114#[derive(Debug, Clone, Insertable)]
115#[diesel(table_name = crate::schema::fills)]
116pub struct NewFill {
117    pub trade_id: i64,
118    pub wallet_address: WalletAddress,
119    pub symbol: String,
120    pub price: Decimal,
121    pub size: Decimal,
122    pub fee: Decimal,
123    pub is_taker: bool,
124    pub timestamp: i64,
125    pub builder_code_address: Option<WalletAddress>,
126    pub builder_code_fee: Option<Decimal>,
127    pub underlying_notional: Option<Decimal>,
128    pub realized_pnl: Option<Decimal>,
129}
130
131// Rejected Orders
132#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
133#[diesel(table_name = crate::schema::rejected_orders)]
134pub struct RejectedOrder {
135    pub id: Option<i32>,
136    pub wallet_address: WalletAddress,
137    pub symbol: String,
138    pub side: String,
139    pub price: Decimal,
140    pub size: Decimal,
141    pub reason: String,
142    pub timestamp: i64,
143    pub created_at: Option<NaiveDateTime>,
144}
145
146#[derive(Debug, Clone, Insertable)]
147#[diesel(table_name = crate::schema::rejected_orders)]
148pub struct NewRejectedOrder {
149    pub wallet_address: WalletAddress,
150    pub symbol: String,
151    pub side: String,
152    pub price: Decimal,
153    pub size: Decimal,
154    pub reason: String,
155    pub timestamp: i64,
156}
157
158// Markets
159#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
160#[diesel(table_name = crate::schema::markets)]
161#[diesel(primary_key(underlying, expiry))]
162pub struct Market {
163    pub underlying: String,
164    pub expiry: i64,
165}
166
167/// Instrument status for expiry/settlement state machine
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
169pub enum InstrumentStatus {
170    /// Instrument is active and tradeable
171    #[default]
172    Active,
173    /// Instrument has expired but settlement price not yet available
174    ExpiredPendingPrice,
175    /// Instrument has been settled
176    Settled,
177}
178
179impl InstrumentStatus {
180    pub fn as_str(&self) -> &'static str {
181        match self {
182            InstrumentStatus::Active => "ACTIVE",
183            InstrumentStatus::ExpiredPendingPrice => "EXPIRED_PENDING_PRICE",
184            InstrumentStatus::Settled => "SETTLED",
185        }
186    }
187
188    /// Parse status from database string. Returns error for unknown values
189    /// to fail-closed rather than silently allowing trading on invalid instruments.
190    pub fn from_str(s: &str) -> Result<Self, String> {
191        match s {
192            "ACTIVE" => Ok(InstrumentStatus::Active),
193            "EXPIRED_PENDING_PRICE" => Ok(InstrumentStatus::ExpiredPendingPrice),
194            "SETTLED" => Ok(InstrumentStatus::Settled),
195            other => Err(format!(
196                "Invalid instrument status in DB: '{}'. Expected ACTIVE, EXPIRED_PENDING_PRICE, or SETTLED",
197                other
198            )),
199        }
200    }
201}
202
203// Instruments
204#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
205#[diesel(table_name = crate::schema::instruments)]
206pub struct Instrument {
207    pub instrument_numeric_id: i32,
208    pub id: String,
209    pub underlying: String,
210    pub strike: Decimal,
211    pub expiry: i64,
212    pub option_type: String,
213    pub option_token_address: Option<WalletAddress>,
214    pub status: String,
215    pub trading_mode: String,
216}
217
218// Order Actions (for OrderActionMessage)
219#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
220#[diesel(table_name = crate::schema::order_actions)]
221pub struct OrderActionRecord {
222    pub id: Option<i32>,
223    pub timestamp: i64,
224    pub wallet: WalletAddress,
225    pub action: String,
226    pub symbol: String,
227    pub price: Decimal,
228    pub size: Decimal,
229    pub side: String,
230    pub tif: String,
231    pub client_id: Option<String>,
232    pub created_at: Option<NaiveDateTime>,
233}
234
235#[derive(Debug, Clone, Insertable)]
236#[diesel(table_name = crate::schema::order_actions)]
237pub struct NewOrderAction {
238    pub timestamp: i64,
239    pub wallet: WalletAddress,
240    pub action: String,
241    pub symbol: String,
242    pub price: Decimal,
243    pub size: Decimal,
244    pub side: String,
245    pub tif: String,
246    pub client_id: Option<String>,
247}
248
249// Order Updates (for OrderUpdateMessage)
250#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
251#[diesel(table_name = crate::schema::order_updates)]
252pub struct OrderUpdateRecord {
253    pub id: Option<i32>,
254    pub timestamp: i64,
255    pub order_id: Option<i64>,
256    pub status: String,
257    pub reason: Option<String>,
258    pub filled_size: Decimal,
259    pub symbol: String,
260    pub price: Decimal,
261    pub size: Decimal,
262    pub side: String,
263    pub tif: String,
264    pub client_id: Option<String>,
265    pub created_at: Option<NaiveDateTime>,
266}
267
268#[derive(Debug, Clone, Insertable)]
269#[diesel(table_name = crate::schema::order_updates)]
270pub struct NewOrderUpdate {
271    pub timestamp: i64,
272    pub order_id: Option<i64>,
273    pub status: String,
274    pub reason: Option<String>,
275    pub filled_size: Decimal,
276    pub symbol: String,
277    pub price: Decimal,
278    pub size: Decimal,
279    pub side: String,
280    pub tif: String,
281    pub client_id: Option<String>,
282}
283
284// Market Actions (for MarketActionMessage)
285#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
286#[diesel(table_name = crate::schema::market_actions)]
287pub struct MarketActionRecord {
288    pub id: Option<i32>,
289    pub timestamp: i64,
290    pub action: String,
291    pub symbol: String,
292    pub underlying: String,
293    pub expiry: i64,
294    pub strike: Decimal,
295    pub option_type: String,
296    pub created_at: Option<NaiveDateTime>,
297}
298
299#[derive(Debug, Clone, Insertable)]
300#[diesel(table_name = crate::schema::market_actions)]
301pub struct NewMarketAction {
302    pub timestamp: i64,
303    pub action: String,
304    pub symbol: String,
305    pub underlying: String,
306    pub expiry: i64,
307    pub strike: Decimal,
308    pub option_type: String,
309}
310
311// Market Updates (for MarketUpdateMessage)
312#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
313#[diesel(table_name = crate::schema::market_updates)]
314pub struct MarketUpdateRecord {
315    pub id: Option<i32>,
316    pub timestamp: i64,
317    pub status: String,
318    pub symbol: String,
319    pub underlying: String,
320    pub expiry: i64,
321    pub strike: Decimal,
322    pub option_type: String,
323    pub created_at: Option<NaiveDateTime>,
324}
325
326#[derive(Debug, Clone, Insertable)]
327#[diesel(table_name = crate::schema::market_updates)]
328pub struct NewMarketUpdate {
329    pub timestamp: i64,
330    pub status: String,
331    pub symbol: String,
332    pub underlying: String,
333    pub expiry: i64,
334    pub strike: Decimal,
335    pub option_type: String,
336}
337
338// Portfolio Snapshots
339#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize)]
340#[diesel(table_name = crate::schema::portfolio_snapshots)]
341pub struct PortfolioSnapshot {
342    pub id: Option<i32>,
343    pub account: WalletAddress,
344    pub symbol: String,
345    pub amount: Decimal,
346    pub cash_balance: Decimal,
347    pub last_fill_seq: i64,
348    pub timestamp: i64,
349    pub created_at: Option<NaiveDateTime>,
350}
351
352#[derive(Debug, Clone, Insertable)]
353#[diesel(table_name = crate::schema::portfolio_snapshots)]
354pub struct NewPortfolioSnapshot {
355    pub account: WalletAddress,
356    pub symbol: String,
357    pub amount: Decimal,
358    pub cash_balance: Decimal,
359    pub last_fill_seq: i64,
360    pub timestamp: i64,
361}
362
363// MMP Configs
364#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize, Convert)]
365#[convert(into = "hypercall_db::MmpConfigRecord")]
366#[diesel(table_name = crate::schema::mmp_configs)]
367#[diesel(primary_key(wallet_address, currency))]
368pub struct MmpConfig {
369    pub wallet_address: WalletAddress,
370    pub currency: String,
371    pub interval_ms: i64,
372    pub frozen_time_ms: i64,
373    pub qty_limit: Option<Decimal>,
374    pub delta_limit: Option<Decimal>,
375    pub vega_limit: Option<Decimal>,
376    pub enabled: bool,
377    pub created_at: Option<NaiveDateTime>,
378    pub updated_at: Option<NaiveDateTime>,
379}
380
381#[derive(Debug, Clone, Insertable, AsChangeset)]
382#[diesel(table_name = crate::schema::mmp_configs)]
383pub struct NewMmpConfig {
384    pub wallet_address: WalletAddress,
385    pub currency: String,
386    pub interval_ms: i64,
387    pub frozen_time_ms: i64,
388    pub qty_limit: Option<Decimal>,
389    pub delta_limit: Option<Decimal>,
390    pub vega_limit: Option<Decimal>,
391    pub enabled: bool,
392}
393
394#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize, Convert)]
395#[convert(into = "hypercall_db::UserTierRecord")]
396#[diesel(table_name = crate::schema::user_tiers)]
397#[diesel(primary_key(wallet_address))]
398pub struct UserTier {
399    pub wallet_address: WalletAddress,
400    pub tier: String,
401    pub created_at: Option<NaiveDateTime>,
402    pub updated_at: Option<NaiveDateTime>,
403    /// Margin mode: "standard" (Deribit-style) or "portfolio" (SPAN-based)
404    #[convert_field(custom_fn = ".parse().expect(\"corrupt margin_mode in user_tiers row\")")]
405    pub margin_mode: String,
406    /// Version for ordering tier updates across processes
407    pub version: i64,
408    /// Maximum number of open orders allowed (NULL = use configured default)
409    pub max_open_orders: Option<i32>,
410    /// Maximum number of open positions allowed (-1 = unlimited)
411    pub max_open_positions: i32,
412    /// Maximum order placements per minute
413    pub orders_per_minute: i32,
414    /// Maximum order cancellations per minute
415    pub cancels_per_minute: i32,
416    /// Maximum API requests per minute
417    pub api_requests_per_minute: i32,
418}
419
420#[derive(Debug, Clone, Insertable, AsChangeset)]
421#[diesel(table_name = crate::schema::user_tiers)]
422pub struct NewUserTier {
423    pub wallet_address: WalletAddress,
424    pub tier: String,
425    /// Margin mode: "standard" (Deribit-style) or "portfolio" (SPAN-based)
426    #[diesel(treat_none_as_default_value = true)]
427    pub margin_mode: Option<String>,
428    /// Version for ordering tier updates (defaults to 0)
429    #[diesel(treat_none_as_default_value = true)]
430    pub version: Option<i64>,
431    /// Maximum number of open orders allowed
432    #[diesel(treat_none_as_default_value = true)]
433    pub max_open_orders: Option<i32>,
434    /// Maximum number of open positions allowed (-1 = unlimited)
435    #[diesel(treat_none_as_default_value = true)]
436    pub max_open_positions: Option<i32>,
437    /// Maximum order placements per minute
438    #[diesel(treat_none_as_default_value = true)]
439    pub orders_per_minute: Option<i32>,
440    /// Maximum order cancellations per minute
441    #[diesel(treat_none_as_default_value = true)]
442    pub cancels_per_minute: Option<i32>,
443    /// Maximum API requests per minute
444    #[diesel(treat_none_as_default_value = true)]
445    pub api_requests_per_minute: Option<i32>,
446}
447
448/// Default trading limits for each tier
449#[derive(Debug, Clone, Queryable, Insertable, Selectable, Serialize, Deserialize, Convert)]
450#[convert(into = "hypercall_db::TierDefaultsRecord")]
451#[diesel(table_name = crate::schema::tier_defaults)]
452#[diesel(primary_key(tier))]
453pub struct TierDefaults {
454    pub tier: String,
455    pub max_open_orders: i32,
456    pub max_open_positions: i32,
457    pub orders_per_minute: i32,
458    pub cancels_per_minute: i32,
459    pub api_requests_per_minute: i32,
460}
461
462// === Oracle Price Samples ===
463
464/// Oracle price sample record for TWAP computation
465#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
466#[diesel(table_name = crate::schema::oracle_price_samples)]
467pub struct OraclePriceSample {
468    pub id: i64,
469    pub symbol: String,
470    pub expiry_timestamp: i64,
471    pub sample_timestamp_ms: i64,
472    pub price: f64,
473    pub source: String,
474    pub created_at: chrono::DateTime<chrono::Utc>,
475}
476
477/// New oracle price sample for insertion
478#[derive(Debug, Clone, Insertable)]
479#[diesel(table_name = crate::schema::oracle_price_samples)]
480pub struct NewOraclePriceSample {
481    pub symbol: String,
482    pub expiry_timestamp: i64,
483    pub sample_timestamp_ms: i64,
484    pub price: f64,
485    pub source: String,
486}
487
488// === Oracle Settlement Prices ===
489
490/// Oracle settlement price record (finalized TWAP)
491#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
492#[diesel(table_name = crate::schema::oracle_settlement_prices)]
493pub struct OracleSettlementPrice {
494    pub id: i64,
495    pub symbol: String,
496    pub expiry_timestamp: i64,
497    pub settlement_price: f64,
498    pub sample_count: i32,
499    pub window_start: i64,
500    pub window_end: i64,
501    pub algorithm: String,
502    pub computed_at: chrono::DateTime<chrono::Utc>,
503}
504
505/// New oracle settlement price for insertion
506#[derive(Debug, Clone, Insertable)]
507#[diesel(table_name = crate::schema::oracle_settlement_prices)]
508pub struct NewOracleSettlementPrice {
509    pub symbol: String,
510    pub expiry_timestamp: i64,
511    pub settlement_price: f64,
512    pub sample_count: i32,
513    pub window_start: i64,
514    pub window_end: i64,
515    pub algorithm: String,
516}
517
518// === Liquidation Service Models ===
519
520/// Liquidation state record - current state for a wallet
521#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, Convert)]
522#[convert(into = "hypercall_db::LiquidationStateRecord")]
523#[diesel(table_name = crate::schema::liquidation_states)]
524#[diesel(primary_key(wallet_address))]
525pub struct LiquidationStateRecord {
526    pub wallet_address: WalletAddress,
527    pub state: String,
528    pub margin_mode: String,
529    pub equity: Decimal,
530    pub mm_required: Decimal,
531    pub maintenance_margin: Decimal,
532    pub liquidation_mode: Option<String>,
533    pub target_equity: Option<Decimal>,
534    pub entered_pre_liq_at: Option<i64>,
535    pub mm_shortfall: Option<Decimal>,
536    pub escalation_deadline: Option<i64>,
537    pub last_reprice_at: Option<i64>,
538    pub partial_order_request_ids: Option<serde_json::Value>,
539    pub partial_order_client_ids: Option<serde_json::Value>,
540    pub partial_bonus_bps: Option<i32>,
541    pub auction_id: Option<String>,
542    pub request_id: Option<String>,
543    pub tx_hash: Option<String>,
544    pub auction_started_at: Option<i64>,
545    pub chain_start_time: Option<i64>,
546    pub margin_needed: Option<Decimal>,
547    pub stop_request_id: Option<String>,
548    pub stop_tx_hash: Option<String>,
549    pub liquidated_at: Option<i64>,
550    pub resolved_winner: Option<WalletAddress>,
551    pub resolved_bonus: Option<Decimal>,
552    pub resolution_tx_hash: Option<String>,
553    pub last_observed_block: Option<i64>,
554    pub updated_at_ms: Option<i64>,
555    pub created_at: Option<NaiveDateTime>,
556    pub updated_at: Option<NaiveDateTime>,
557}
558
559/// New liquidation state for insertion
560#[derive(Debug, Clone, Insertable, AsChangeset)]
561#[diesel(table_name = crate::schema::liquidation_states)]
562#[diesel(treat_none_as_null = true)]
563pub struct NewLiquidationState {
564    pub wallet_address: WalletAddress,
565    pub state: String,
566    pub margin_mode: String,
567    pub equity: Decimal,
568    pub mm_required: Decimal,
569    pub maintenance_margin: Decimal,
570    pub liquidation_mode: Option<String>,
571    pub target_equity: Option<Decimal>,
572    pub entered_pre_liq_at: Option<i64>,
573    pub mm_shortfall: Option<Decimal>,
574    pub escalation_deadline: Option<i64>,
575    pub last_reprice_at: Option<i64>,
576    pub partial_order_request_ids: Option<serde_json::Value>,
577    pub partial_order_client_ids: Option<serde_json::Value>,
578    pub partial_bonus_bps: Option<i32>,
579    pub auction_id: Option<String>,
580    pub request_id: Option<String>,
581    pub tx_hash: Option<String>,
582    pub auction_started_at: Option<i64>,
583    pub chain_start_time: Option<i64>,
584    pub margin_needed: Option<Decimal>,
585    pub stop_request_id: Option<String>,
586    pub stop_tx_hash: Option<String>,
587    pub liquidated_at: Option<i64>,
588    pub resolved_winner: Option<WalletAddress>,
589    pub resolved_bonus: Option<Decimal>,
590    pub resolution_tx_hash: Option<String>,
591    pub last_observed_block: Option<i64>,
592    pub updated_at_ms: Option<i64>,
593}
594
595/// Liquidation history record - state transition audit trail
596#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, Convert)]
597#[convert(into = "hypercall_db::LiquidationHistoryRecord")]
598#[diesel(table_name = crate::schema::liquidation_history)]
599pub struct LiquidationHistoryRecord {
600    pub id: i64,
601    pub wallet_address: WalletAddress,
602    pub previous_state: String,
603    pub new_state: String,
604    pub liquidation_mode: Option<String>,
605    pub equity: Decimal,
606    pub mm_required: Decimal,
607    pub maintenance_margin: Decimal,
608    pub shortfall: Decimal,
609    pub auction_id: Option<String>,
610    pub request_id: Option<String>,
611    pub tx_hash: Option<String>,
612    pub margin_needed: Option<Decimal>,
613    pub winner_address: Option<WalletAddress>,
614    pub bonus: Option<Decimal>,
615    pub details: serde_json::Value,
616    pub timestamp: i64,
617    pub created_at: Option<NaiveDateTime>,
618}
619
620/// New liquidation history entry for insertion
621#[derive(Debug, Clone, Insertable)]
622#[diesel(table_name = crate::schema::liquidation_history)]
623pub struct NewLiquidationHistory {
624    pub wallet_address: WalletAddress,
625    pub previous_state: String,
626    pub new_state: String,
627    pub liquidation_mode: Option<String>,
628    pub equity: Decimal,
629    pub mm_required: Decimal,
630    pub maintenance_margin: Decimal,
631    pub shortfall: Decimal,
632    pub auction_id: Option<String>,
633    pub request_id: Option<String>,
634    pub tx_hash: Option<String>,
635    pub margin_needed: Option<Decimal>,
636    pub winner_address: Option<WalletAddress>,
637    pub bonus: Option<Decimal>,
638    pub details: serde_json::Value,
639    pub timestamp: i64,
640}
641
642/// Liquidation auction record
643#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, Convert)]
644#[convert(into = "hypercall_db::LiquidationAuctionRecord")]
645#[diesel(table_name = crate::schema::liquidation_auctions)]
646pub struct LiquidationAuctionRecord {
647    pub auction_id: String,
648    pub wallet_address: WalletAddress,
649    pub status: String,
650    pub positions: serde_json::Value,
651    pub equity_at_start: Decimal,
652    pub mm_shortfall_at_start: Decimal,
653    pub target_equity: Option<Decimal>,
654    pub request_id: Option<String>,
655    pub tx_hash: Option<String>,
656    pub started_at: i64,
657    pub chain_start_time: Option<i64>,
658    pub margin_needed: Option<Decimal>,
659    pub stop_request_id: Option<String>,
660    pub stop_tx_hash: Option<String>,
661    pub completed_at: Option<i64>,
662    pub liquidator_address: Option<WalletAddress>,
663    pub bonus: Option<Decimal>,
664    pub settlement_value: Option<Decimal>,
665    pub last_observed_block: Option<i64>,
666    pub created_at: Option<NaiveDateTime>,
667    pub updated_at: Option<NaiveDateTime>,
668}
669
670/// New liquidation auction for insertion
671#[derive(Debug, Clone, Insertable)]
672#[diesel(table_name = crate::schema::liquidation_auctions)]
673pub struct NewLiquidationAuction {
674    pub auction_id: String,
675    pub wallet_address: WalletAddress,
676    pub status: String,
677    pub positions: serde_json::Value,
678    pub equity_at_start: Decimal,
679    pub mm_shortfall_at_start: Decimal,
680    pub target_equity: Option<Decimal>,
681    pub request_id: Option<String>,
682    pub tx_hash: Option<String>,
683    pub started_at: i64,
684    pub chain_start_time: Option<i64>,
685    pub margin_needed: Option<Decimal>,
686    pub stop_request_id: Option<String>,
687    pub stop_tx_hash: Option<String>,
688    pub completed_at: Option<i64>,
689    pub liquidator_address: Option<WalletAddress>,
690    pub bonus: Option<Decimal>,
691    pub settlement_value: Option<Decimal>,
692    pub last_observed_block: Option<i64>,
693}
694
695/// Auction update changeset
696#[derive(Debug, Clone, AsChangeset)]
697#[diesel(table_name = crate::schema::liquidation_auctions)]
698pub struct UpdateLiquidationAuction {
699    pub status: Option<String>,
700    pub request_id: Option<String>,
701    pub tx_hash: Option<String>,
702    pub chain_start_time: Option<i64>,
703    pub margin_needed: Option<Decimal>,
704    pub stop_request_id: Option<String>,
705    pub stop_tx_hash: Option<String>,
706    pub completed_at: Option<i64>,
707    pub liquidator_address: Option<WalletAddress>,
708    pub bonus: Option<Decimal>,
709    pub settlement_value: Option<Decimal>,
710    pub last_observed_block: Option<i64>,
711}
712
713#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, Convert)]
714#[convert(into = "hypercall_db::RsmSignerNonceRecord")]
715#[diesel(table_name = crate::schema::rsm_signer_nonces)]
716#[diesel(primary_key(signer_address))]
717pub struct RsmSignerNonceRecord {
718    pub signer_address: WalletAddress,
719    pub next_nonce: i64,
720    pub last_synced_nonce: Option<i64>,
721    pub created_at: Option<NaiveDateTime>,
722    pub updated_at: Option<NaiveDateTime>,
723}
724
725#[derive(Debug, Clone, Insertable, AsChangeset)]
726#[diesel(table_name = crate::schema::rsm_signer_nonces)]
727pub struct NewRsmSignerNonce {
728    pub signer_address: WalletAddress,
729    pub next_nonce: i64,
730    pub last_synced_nonce: Option<i64>,
731}
732
733#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
734#[diesel(table_name = crate::schema::rsm_signer_requests)]
735#[diesel(primary_key(request_id))]
736pub struct RsmSignerRequestRecord {
737    pub request_id: String,
738    pub signer_address: WalletAddress,
739    pub account_address: WalletAddress,
740    pub action: Vec<u8>,
741    pub nonce: i64,
742    pub status: String,
743    pub directive: Option<Vec<u8>>,
744    pub signature: Option<String>,
745    pub created_at: Option<NaiveDateTime>,
746    pub updated_at: Option<NaiveDateTime>,
747}
748
749// === Position Expirations (idempotency) ===
750
751/// Position expiration record for settlement idempotency
752#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
753#[diesel(table_name = crate::schema::position_expirations)]
754pub struct PositionExpiration {
755    pub id: i64,
756    pub wallet: WalletAddress,
757    pub symbol: String,
758    pub expiry_ts: i64,
759    pub settlement_price: Decimal,
760    pub settlement_value: Decimal,
761    pub applied_at: Option<chrono::DateTime<chrono::Utc>>,
762}
763
764/// New position expiration for insertion
765#[derive(Debug, Clone, Insertable)]
766#[diesel(table_name = crate::schema::position_expirations)]
767pub struct NewPositionExpiration {
768    pub wallet: WalletAddress,
769    pub symbol: String,
770    pub expiry_ts: i64,
771    pub settlement_price: Decimal,
772    pub settlement_value: Decimal,
773}
774
775// === Settlement Payouts (audit trail) ===
776
777/// Settlement payout record for audit trail
778#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, Convert)]
779#[convert(into = "hypercall_db::SettlementPayoutRecord")]
780#[diesel(table_name = crate::schema::settlement_payouts)]
781pub struct SettlementPayout {
782    pub id: i64,
783    pub wallet: WalletAddress,
784    pub symbol: String,
785    pub expiry_ts: i64,
786    pub position_size: Decimal,
787    pub settlement_price: Decimal,
788    pub payout_amount: Decimal,
789    pub ledger_applied: bool,
790    pub created_at: Option<chrono::DateTime<chrono::Utc>>,
791    pub settlement_entry_price: Option<Decimal>,
792    pub cost_basis: Option<Decimal>,
793    pub net_pnl: Option<Decimal>,
794}
795
796/// New settlement payout for insertion
797#[derive(Debug, Clone, Insertable)]
798#[diesel(table_name = crate::schema::settlement_payouts)]
799pub struct NewSettlementPayout {
800    pub wallet: WalletAddress,
801    pub symbol: String,
802    pub expiry_ts: i64,
803    pub position_size: Decimal,
804    pub settlement_price: Decimal,
805    pub payout_amount: Decimal,
806    pub ledger_applied: bool,
807    pub settlement_entry_price: Option<Decimal>,
808    pub cost_basis: Option<Decimal>,
809    pub net_pnl: Option<Decimal>,
810}
811
812/// Historical equity snapshot record for charting PnL/equity over time.
813#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
814#[diesel(table_name = crate::schema::historical_pnl_snapshots)]
815pub struct HistoricalPnlSnapshot {
816    pub id: i64,
817    pub wallet_address: WalletAddress,
818    pub interval_ms: i64,
819    pub timestamp_ms: i64,
820    pub total_equity: Decimal,
821    pub created_at: chrono::DateTime<chrono::Utc>,
822    pub attribution: Option<Vec<u8>>,
823}
824
825/// New historical equity snapshot for insertion/upsert.
826#[derive(Debug, Clone, Insertable)]
827#[diesel(table_name = crate::schema::historical_pnl_snapshots)]
828pub struct NewHistoricalPnlSnapshot {
829    pub wallet_address: WalletAddress,
830    pub interval_ms: i64,
831    pub timestamp_ms: i64,
832    pub total_equity: Decimal,
833    pub attribution: Option<Vec<u8>>,
834}
835
836/// Historical theo snapshot record for charting theoretical option prices over time.
837#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
838#[diesel(table_name = crate::schema::historical_theo_snapshots)]
839pub struct HistoricalTheoSnapshot {
840    pub id: i64,
841    pub symbol: String,
842    pub interval_ms: i64,
843    pub timestamp_ms: i64,
844    pub theoretical_price: Decimal,
845    pub created_at: chrono::DateTime<chrono::Utc>,
846}
847
848/// New historical theo snapshot for insertion/upsert.
849#[derive(Debug, Clone, Insertable)]
850#[diesel(table_name = crate::schema::historical_theo_snapshots)]
851pub struct NewHistoricalTheoSnapshot {
852    pub symbol: String,
853    pub interval_ms: i64,
854    pub timestamp_ms: i64,
855    pub theoretical_price: Decimal,
856}
857
858/// Persisted vol surface snapshot for charting volatility surfaces over time.
859#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
860#[diesel(table_name = crate::schema::vol_surface_snapshots)]
861pub struct VolSurfaceSnapshotRow {
862    pub id: i64,
863    pub underlying: String,
864    pub interval_ms: i64,
865    pub timestamp_ms: i64,
866    pub surface_json: serde_json::Value,
867    pub created_at: chrono::DateTime<chrono::Utc>,
868}
869
870/// New vol surface snapshot for insertion/upsert.
871#[derive(Debug, Clone, Insertable)]
872#[diesel(table_name = crate::schema::vol_surface_snapshots)]
873pub struct NewVolSurfaceSnapshotRow {
874    pub underlying: String,
875    pub interval_ms: i64,
876    pub timestamp_ms: i64,
877    pub surface_json: serde_json::Value,
878}
879
880/// Persisted BBO snapshot used for 24h options summary change calculations.
881#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
882#[diesel(table_name = crate::schema::bbo_snapshots)]
883pub struct BboSnapshot {
884    pub id: i64,
885    pub symbol: String,
886    pub best_bid: Decimal,
887    pub best_ask: Decimal,
888    pub best_bid_size: Option<Decimal>,
889    pub best_ask_size: Option<Decimal>,
890    pub snapshot_ts: i64,
891    pub created_at: chrono::DateTime<chrono::Utc>,
892}
893
894/// New persisted BBO snapshot for insertion/upsert.
895#[derive(Debug, Clone, Insertable)]
896#[diesel(table_name = crate::schema::bbo_snapshots)]
897pub struct NewBboSnapshot {
898    pub symbol: String,
899    pub best_bid: Decimal,
900    pub best_ask: Decimal,
901    pub best_bid_size: Option<Decimal>,
902    pub best_ask_size: Option<Decimal>,
903    pub snapshot_ts: i64,
904}
905
906/// Seen marker for a settlement payout (per wallet).
907#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
908#[diesel(table_name = crate::schema::settlement_payout_seen)]
909#[diesel(primary_key(wallet, payout_id))]
910pub struct SettlementPayoutSeen {
911    pub wallet: WalletAddress,
912    pub payout_id: i64,
913    pub seen_at: chrono::DateTime<chrono::Utc>,
914}
915
916/// New seen marker for insertion.
917#[derive(Debug, Clone, Insertable)]
918#[diesel(table_name = crate::schema::settlement_payout_seen)]
919pub struct NewSettlementPayoutSeen {
920    pub wallet: WalletAddress,
921    pub payout_id: i64,
922}
923
924// === Competition / Profile Models ===
925
926#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
927#[diesel(table_name = crate::schema::competitions)]
928pub struct Competition {
929    pub id: i64,
930    pub name: String,
931    pub description: Option<String>,
932    pub rules_url: Option<String>,
933    pub rules_content: Option<String>,
934    pub win_conditions: Vec<String>,
935    pub primary_win_condition: String,
936    pub start_ts_ms: i64,
937    pub end_ts_ms: i64,
938    pub created_at: chrono::DateTime<chrono::Utc>,
939    pub updated_at: chrono::DateTime<chrono::Utc>,
940}
941
942#[derive(Debug, Clone, Insertable)]
943#[diesel(table_name = crate::schema::competitions)]
944pub struct NewCompetition {
945    pub name: String,
946    pub description: Option<String>,
947    pub rules_url: Option<String>,
948    pub rules_content: Option<String>,
949    pub win_conditions: Vec<String>,
950    pub primary_win_condition: String,
951    pub start_ts_ms: i64,
952    pub end_ts_ms: i64,
953}
954
955#[derive(Debug, Clone, AsChangeset, Default)]
956#[diesel(table_name = crate::schema::competitions)]
957pub struct UpdateCompetition {
958    pub name: Option<String>,
959    pub description: Option<Option<String>>,
960    pub rules_url: Option<Option<String>>,
961    pub rules_content: Option<Option<String>>,
962    pub win_conditions: Option<Vec<String>>,
963    pub primary_win_condition: Option<String>,
964    pub start_ts_ms: Option<i64>,
965    pub end_ts_ms: Option<i64>,
966}
967
968#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
969#[diesel(table_name = crate::schema::competition_final_stats)]
970pub struct CompetitionFinalStat {
971    pub id: i64,
972    pub competition_id: i64,
973    pub wallet: WalletAddress,
974    pub rank: i32,
975    pub pnl: Decimal,
976    pub volume: Decimal,
977    pub efficiency: Decimal,
978    pub medal: Option<i32>,
979    pub finalized_at: chrono::DateTime<chrono::Utc>,
980    pub created_at: chrono::DateTime<chrono::Utc>,
981}
982
983#[derive(Debug, Clone, Insertable)]
984#[diesel(table_name = crate::schema::competition_final_stats)]
985pub struct NewCompetitionFinalStat {
986    pub competition_id: i64,
987    pub wallet: WalletAddress,
988    pub rank: i32,
989    pub pnl: Decimal,
990    pub volume: Decimal,
991    pub efficiency: Decimal,
992    pub medal: Option<i32>,
993    pub finalized_at: chrono::DateTime<chrono::Utc>,
994}
995
996#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
997#[diesel(table_name = crate::schema::user_profiles)]
998#[diesel(primary_key(wallet_address))]
999pub struct UserProfile {
1000    pub wallet_address: WalletAddress,
1001    pub approved_username: Option<String>,
1002    pub profile_image_url: Option<String>,
1003    pub profile_image_updated_at: Option<chrono::DateTime<chrono::Utc>>,
1004    pub created_at: chrono::DateTime<chrono::Utc>,
1005    pub updated_at: chrono::DateTime<chrono::Utc>,
1006}
1007
1008#[derive(Debug, Clone, Insertable)]
1009#[diesel(table_name = crate::schema::user_profiles)]
1010pub struct NewUserProfile {
1011    pub wallet_address: WalletAddress,
1012    pub approved_username: Option<String>,
1013    pub profile_image_url: Option<String>,
1014    pub profile_image_updated_at: Option<chrono::DateTime<chrono::Utc>>,
1015}
1016
1017#[derive(Debug, Clone, AsChangeset, Default)]
1018#[diesel(table_name = crate::schema::user_profiles)]
1019pub struct UpdateUserProfile {
1020    pub approved_username: Option<Option<String>>,
1021    pub profile_image_url: Option<Option<String>>,
1022    pub profile_image_updated_at: Option<Option<chrono::DateTime<chrono::Utc>>>,
1023}
1024
1025#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1026#[diesel(table_name = crate::schema::ledger_events)]
1027pub struct LedgerEvent {
1028    pub id: i64,
1029    pub wallet: WalletAddress,
1030    pub event_ts_ms: i64,
1031    pub delta: Decimal,
1032    pub event_type: String,
1033    pub reference_trade_id: Option<i64>,
1034    pub reference_symbol: Option<String>,
1035    pub created_at: chrono::DateTime<chrono::Utc>,
1036}
1037
1038#[derive(Debug, Clone, Insertable)]
1039#[diesel(table_name = crate::schema::ledger_events)]
1040pub struct NewLedgerEvent {
1041    pub wallet: WalletAddress,
1042    pub event_ts_ms: i64,
1043    pub delta: Decimal,
1044    pub event_type: String,
1045    pub reference_trade_id: Option<i64>,
1046    pub reference_symbol: Option<String>,
1047}
1048
1049#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1050#[diesel(table_name = crate::schema::competition_fill_events)]
1051pub struct CompetitionFillEvent {
1052    pub id: i64,
1053    pub trade_id: i64,
1054    pub wallet: WalletAddress,
1055    pub symbol: String,
1056    pub side: String,
1057    pub price: Decimal,
1058    pub size: Decimal,
1059    pub fee: Decimal,
1060    pub timestamp_ms: i64,
1061    pub created_at: chrono::DateTime<chrono::Utc>,
1062}
1063
1064#[derive(Debug, Clone, Insertable)]
1065#[diesel(table_name = crate::schema::competition_fill_events)]
1066pub struct NewCompetitionFillEvent {
1067    pub trade_id: i64,
1068    pub wallet: WalletAddress,
1069    pub symbol: String,
1070    pub side: String,
1071    pub price: Decimal,
1072    pub size: Decimal,
1073    pub fee: Decimal,
1074    pub timestamp_ms: i64,
1075}
1076
1077// === Push Subscription Models ===
1078
1079#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1080#[diesel(table_name = crate::schema::push_subscriptions)]
1081pub struct PushSubscription {
1082    pub id: i64,
1083    pub wallet_address: String,
1084    pub endpoint: String,
1085    pub auth_key: String,
1086    pub p256dh_key: String,
1087    pub preferences: serde_json::Value,
1088    pub created_at: chrono::DateTime<chrono::Utc>,
1089}
1090
1091#[derive(Debug, Clone, Insertable)]
1092#[diesel(table_name = crate::schema::push_subscriptions)]
1093pub struct NewPushSubscription {
1094    pub wallet_address: String,
1095    pub endpoint: String,
1096    pub auth_key: String,
1097    pub p256dh_key: String,
1098    pub preferences: serde_json::Value,
1099}
1100
1101// === Notification Feed Models ===
1102
1103#[derive(Debug, Clone, Queryable, Selectable)]
1104#[diesel(table_name = crate::schema::notifications)]
1105pub struct Notification {
1106    pub id: i64,
1107    pub wallet_address: String,
1108    pub notification_type: String,
1109    pub payload: Vec<u8>,
1110    pub read_at: Option<chrono::DateTime<chrono::Utc>>,
1111    pub created_at: chrono::DateTime<chrono::Utc>,
1112}
1113
1114#[derive(Debug, Clone, Insertable)]
1115#[diesel(table_name = crate::schema::notifications)]
1116pub struct NewNotification {
1117    pub wallet_address: String,
1118    pub notification_type: String,
1119    pub payload: Vec<u8>,
1120}
1121
1122// === Journal Replay Models ===
1123
1124/// Command data for engine replay after snapshot load.
1125/// Contains the minimal data needed to replay a command through the engine.
1126/// Stores command/response as wire bytes (version byte + msgpack).
1127#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct JournalCommandForReplay {
1129    pub command_id: i64,
1130    pub request_id: String,
1131    pub command_type: String,
1132    /// Wire-format bytes: [version: u8][msgpack payload]
1133    pub command_data: Vec<u8>,
1134    /// Response wire bytes — used to determine final order state
1135    /// (filled, open, partially_filled, cancelled) so replay can reconstruct
1136    /// the correct resting quantity without re-running matching.
1137    pub response_data: Option<Vec<u8>>,
1138}
1139
1140impl From<JournalCommandForReplay> for hypercall_db::ReplayCommand {
1141    fn from(cmd: JournalCommandForReplay) -> Self {
1142        Self {
1143            command_id: cmd.command_id,
1144            request_id: cmd.request_id,
1145            command_type: cmd.command_type,
1146            command_data: cmd.command_data,
1147            response_data: cmd.response_data,
1148        }
1149    }
1150}
1151
1152// RFQ Models
1153
1154#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize, Convert)]
1155#[convert(into = "hypercall_db::QuoteProviderRecord")]
1156#[diesel(table_name = crate::schema::quote_providers)]
1157pub struct QuoteProviderRecord {
1158    pub wallet_address: WalletAddress,
1159    pub tier: String,
1160    pub status: String,
1161    pub allowed_underlyings: Option<Vec<String>>,
1162    pub max_notional_per_quote: Decimal,
1163    pub max_open_notional: Decimal,
1164    pub created_at: DateTime<Utc>,
1165    pub updated_at: DateTime<Utc>,
1166}
1167
1168#[derive(Debug, Clone, Insertable)]
1169#[diesel(table_name = crate::schema::quote_providers)]
1170pub struct NewQuoteProvider {
1171    pub wallet_address: WalletAddress,
1172    pub tier: String,
1173    pub status: String,
1174    pub allowed_underlyings: Option<Vec<String>>,
1175    pub max_notional_per_quote: Decimal,
1176    pub max_open_notional: Decimal,
1177}
1178
1179#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1180#[diesel(table_name = crate::schema::rfq_records)]
1181pub struct RfqRecordRow {
1182    pub rfq_id: String,
1183    pub taker_wallet: WalletAddress,
1184    pub underlying: String,
1185    pub status: String,
1186    pub taker_signature: String,
1187    pub taker_nonce: i64,
1188    pub legs_hash: Vec<u8>,
1189    pub created_at: DateTime<Utc>,
1190    pub expires_at: DateTime<Utc>,
1191    pub executed_at: Option<DateTime<Utc>>,
1192}
1193
1194#[derive(Debug, Clone, Insertable)]
1195#[diesel(table_name = crate::schema::rfq_records)]
1196pub struct NewRfqRecord {
1197    pub rfq_id: String,
1198    pub taker_wallet: WalletAddress,
1199    pub underlying: String,
1200    pub status: String,
1201    pub taker_signature: String,
1202    pub taker_nonce: i64,
1203    pub legs_hash: Vec<u8>,
1204    pub expires_at: DateTime<Utc>,
1205}
1206
1207#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1208#[diesel(table_name = crate::schema::rfq_legs)]
1209pub struct RfqLegRow {
1210    pub id: String,
1211    pub rfq_id: String,
1212    pub instrument: String,
1213    pub side: String,
1214    pub size: Decimal,
1215    pub leg_index: i16,
1216}
1217
1218#[derive(Debug, Clone, Insertable)]
1219#[diesel(table_name = crate::schema::rfq_legs)]
1220pub struct NewRfqLeg {
1221    pub rfq_id: String,
1222    pub instrument: String,
1223    pub side: String,
1224    pub size: Decimal,
1225    pub leg_index: i16,
1226}
1227
1228#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1229#[diesel(table_name = crate::schema::rfq_quotes)]
1230pub struct RfqQuoteRow {
1231    pub quote_id: String,
1232    pub rfq_id: String,
1233    pub qp_wallet: WalletAddress,
1234    pub net_premium: Decimal,
1235    pub valid_for_ms: i64,
1236    pub qp_signature: String,
1237    pub qp_nonce: i64,
1238    pub created_at: DateTime<Utc>,
1239    pub expires_at: DateTime<Utc>,
1240}
1241
1242#[derive(Debug, Clone, Insertable)]
1243#[diesel(table_name = crate::schema::rfq_quotes)]
1244pub struct NewRfqQuote {
1245    pub quote_id: String,
1246    pub rfq_id: String,
1247    pub qp_wallet: WalletAddress,
1248    pub net_premium: Decimal,
1249    pub valid_for_ms: i64,
1250    pub qp_signature: String,
1251    pub qp_nonce: i64,
1252    pub expires_at: DateTime<Utc>,
1253}
1254
1255#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1256#[diesel(table_name = crate::schema::rfq_quote_legs)]
1257pub struct RfqQuoteLegRow {
1258    pub id: String,
1259    pub quote_id: String,
1260    pub instrument: String,
1261    pub side: String,
1262    pub price: Decimal,
1263    pub size: Decimal,
1264    pub leg_index: i16,
1265}
1266
1267#[derive(Debug, Clone, Insertable)]
1268#[diesel(table_name = crate::schema::rfq_quote_legs)]
1269pub struct NewRfqQuoteLeg {
1270    pub quote_id: String,
1271    pub instrument: String,
1272    pub side: String,
1273    pub price: Decimal,
1274    pub size: Decimal,
1275    pub leg_index: i16,
1276}
1277
1278#[derive(Debug, Clone, Queryable, Selectable, Serialize, Deserialize)]
1279#[diesel(table_name = crate::schema::rfq_fills)]
1280pub struct RfqFillRow {
1281    pub fill_id: String,
1282    pub rfq_id: String,
1283    pub quote_id: String,
1284    pub taker_wallet: WalletAddress,
1285    pub qp_wallet: WalletAddress,
1286    pub net_premium: Decimal,
1287    pub taker_accept_signature: String,
1288    pub executed_at: DateTime<Utc>,
1289}
1290
1291#[derive(Debug, Clone, Insertable)]
1292#[diesel(table_name = crate::schema::rfq_fills)]
1293pub struct NewRfqFill {
1294    pub fill_id: String,
1295    pub rfq_id: String,
1296    pub quote_id: String,
1297    pub taker_wallet: WalletAddress,
1298    pub qp_wallet: WalletAddress,
1299    pub net_premium: Decimal,
1300    pub taker_accept_signature: String,
1301}
1302
1303impl From<Instrument> for hypercall_db::InstrumentRecord {
1304    fn from(r: Instrument) -> Self {
1305        Self {
1306            instrument_numeric_id: r.instrument_numeric_id,
1307            id: r.id,
1308            underlying: r.underlying,
1309            strike: r.strike,
1310            expiry: r.expiry,
1311            option_type: r.option_type.parse().unwrap_or_else(|_| {
1312                panic!(
1313                    "invalid option_type '{}' in instruments table",
1314                    r.option_type
1315                )
1316            }),
1317            option_token_address: r.option_token_address,
1318            status: hypercall_types::api_models::InstrumentStatus::from_db_str(&r.status)
1319                .unwrap_or_else(|| panic!("invalid status '{}' in instruments table", r.status)),
1320            trading_mode: r.trading_mode,
1321        }
1322    }
1323}
1324
1325impl From<OrderInfoRecord> for hypercall_db::OrderInfoRecord {
1326    fn from(r: OrderInfoRecord) -> Self {
1327        use hypercall_types::{OrderStatus, Side, TimeInForce};
1328
1329        let side = match r.side.as_str() {
1330            "Buy" => Side::Buy,
1331            "Sell" => Side::Sell,
1332            other => panic!("invalid side '{}' in order_infos", other),
1333        };
1334        let tif = match r.tif.as_str() {
1335            "GTC" => TimeInForce::GTC,
1336            "IOC" => TimeInForce::IOC,
1337            "FOK" => TimeInForce::FOK,
1338            other => panic!("invalid tif '{}' in order_infos", other),
1339        };
1340        let status = match r.status.as_str() {
1341            "ACKED" => OrderStatus::Acked,
1342            "OPEN" => OrderStatus::OpenOrder,
1343            "PARTIALLY_FILLED" => OrderStatus::PartiallyFilled,
1344            "FILLED" => OrderStatus::Filled,
1345            "CANCELED" => OrderStatus::Canceled,
1346            "REJECTED" => OrderStatus::RejectOrder,
1347            other => panic!("invalid status '{}' in order_infos", other),
1348        };
1349
1350        Self {
1351            order_id: r.order_id,
1352            wallet_address: r.wallet_address,
1353            symbol: r.symbol,
1354            side,
1355            price: r.price,
1356            size: r.size,
1357            tif,
1358            client_id: r.client_id,
1359            is_perp: r.is_perp,
1360            underlying: r.underlying,
1361            reduce_only: r.reduce_only,
1362            nonce: r.nonce,
1363            signature: r.signature,
1364            mmp_enabled: r.mmp_enabled,
1365            timestamp: r.timestamp,
1366            status,
1367            filled_size: r.filled_size,
1368            created_at: r.created_at,
1369            updated_at: r.updated_at,
1370        }
1371    }
1372}
1373
1374#[cfg(test)]
1375mod conversion_tests {
1376    use super::*;
1377    use rust_decimal_macros::dec;
1378
1379    #[test]
1380    fn instrument_roundtrip() {
1381        let diesel_row = Instrument {
1382            instrument_numeric_id: 42,
1383            id: "BTC-20260115-100000-C".to_string(),
1384            underlying: "BTC".to_string(),
1385            strike: dec!(100000),
1386            expiry: 20260115,
1387            option_type: "call".to_string(),
1388            option_token_address: None,
1389            status: "ACTIVE".to_string(),
1390            trading_mode: "orderbook".to_string(),
1391        };
1392        let db_type: hypercall_db::InstrumentRecord = diesel_row.into();
1393        assert_eq!(db_type.id, "BTC-20260115-100000-C");
1394        assert_eq!(db_type.strike, dec!(100000));
1395        assert_eq!(
1396            db_type.status,
1397            hypercall_types::api_models::InstrumentStatus::Active
1398        );
1399        assert_eq!(db_type.option_type, hypercall_types::OptionType::Call);
1400    }
1401
1402    #[test]
1403    fn mmp_config_roundtrip() {
1404        let diesel_row = MmpConfig {
1405            wallet_address: hypercall_types::test_wallet!(1),
1406            currency: "BTC".to_string(),
1407            interval_ms: 1000,
1408            frozen_time_ms: 5000,
1409            qty_limit: Some(dec!(10)),
1410            delta_limit: None,
1411            vega_limit: None,
1412            enabled: true,
1413            created_at: None,
1414            updated_at: None,
1415        };
1416        let db_type: hypercall_db::MmpConfigRecord = diesel_row.into();
1417        assert_eq!(db_type.currency, "BTC");
1418        assert_eq!(db_type.qty_limit, Some(dec!(10)));
1419        assert!(db_type.enabled);
1420    }
1421
1422    #[test]
1423    fn user_tier_roundtrip() {
1424        let diesel_row = UserTier {
1425            wallet_address: hypercall_types::test_wallet!(2),
1426            tier: "tier2".to_string(),
1427            created_at: None,
1428            updated_at: None,
1429            margin_mode: "standard".to_string(),
1430            version: 3,
1431            max_open_orders: Some(100),
1432            max_open_positions: 50,
1433            orders_per_minute: 60,
1434            cancels_per_minute: 120,
1435            api_requests_per_minute: 600,
1436        };
1437        let db_type: hypercall_db::UserTierRecord = diesel_row.into();
1438        assert_eq!(db_type.margin_mode, hypercall_types::MarginMode::Standard);
1439        assert_eq!(db_type.version, 3);
1440        assert_eq!(db_type.max_open_orders, Some(100));
1441    }
1442
1443    #[test]
1444    fn tier_defaults_roundtrip() {
1445        let diesel_row = TierDefaults {
1446            tier: "tier1".to_string(),
1447            max_open_orders: 200,
1448            max_open_positions: 100,
1449            orders_per_minute: 120,
1450            cancels_per_minute: 240,
1451            api_requests_per_minute: 1200,
1452        };
1453        let db_type: hypercall_db::TierDefaultsRecord = diesel_row.into();
1454        assert_eq!(db_type.tier, "tier1");
1455        assert_eq!(db_type.max_open_orders, 200);
1456    }
1457
1458    #[test]
1459    fn quote_provider_roundtrip() {
1460        let diesel_row = QuoteProviderRecord {
1461            wallet_address: hypercall_types::test_wallet!(3),
1462            tier: "gold".to_string(),
1463            status: "active".to_string(),
1464            allowed_underlyings: Some(vec!["BTC".to_string(), "ETH".to_string()]),
1465            max_notional_per_quote: dec!(100000),
1466            max_open_notional: dec!(500000),
1467            created_at: chrono::Utc::now(),
1468            updated_at: chrono::Utc::now(),
1469        };
1470        let db_type: hypercall_db::QuoteProviderRecord = diesel_row.into();
1471        assert_eq!(db_type.tier, "gold");
1472        assert_eq!(db_type.allowed_underlyings.unwrap().len(), 2);
1473    }
1474
1475    #[test]
1476    fn settlement_payout_roundtrip() {
1477        let now = chrono::Utc::now();
1478        let diesel_row = SettlementPayout {
1479            id: 101,
1480            wallet: hypercall_types::test_wallet!(4),
1481            symbol: "BTC-20260115-95000-C".to_string(),
1482            expiry_ts: 1_736_928_000,
1483            position_size: dec!(5),
1484            settlement_price: dec!(97000),
1485            payout_amount: dec!(10000),
1486            ledger_applied: true,
1487            created_at: Some(now),
1488            settlement_entry_price: Some(dec!(95000)),
1489            cost_basis: Some(dec!(475000)),
1490            net_pnl: Some(dec!(10000)),
1491        };
1492        let db_type: hypercall_db::SettlementPayoutRecord = diesel_row.into();
1493        assert_eq!(db_type.id, 101);
1494        assert_eq!(db_type.wallet, hypercall_types::test_wallet!(4));
1495        assert_eq!(db_type.symbol, "BTC-20260115-95000-C");
1496        assert_eq!(db_type.expiry_ts, 1_736_928_000);
1497        assert_eq!(db_type.position_size, dec!(5));
1498        assert_eq!(db_type.settlement_price, dec!(97000));
1499        assert_eq!(db_type.payout_amount, dec!(10000));
1500        assert!(db_type.ledger_applied);
1501        assert_eq!(db_type.created_at, Some(now));
1502        assert_eq!(db_type.settlement_entry_price, Some(dec!(95000)));
1503        assert_eq!(db_type.cost_basis, Some(dec!(475000)));
1504        assert_eq!(db_type.net_pnl, Some(dec!(10000)));
1505    }
1506
1507    #[test]
1508    fn settlement_payout_optionals_none() {
1509        let diesel_row = SettlementPayout {
1510            id: 1,
1511            wallet: hypercall_types::test_wallet!(5),
1512            symbol: "ETH-20260115-3000-P".to_string(),
1513            expiry_ts: 0,
1514            position_size: dec!(0),
1515            settlement_price: dec!(0),
1516            payout_amount: dec!(0),
1517            ledger_applied: false,
1518            created_at: None,
1519            settlement_entry_price: None,
1520            cost_basis: None,
1521            net_pnl: None,
1522        };
1523        let db_type: hypercall_db::SettlementPayoutRecord = diesel_row.into();
1524        assert!(!db_type.ledger_applied);
1525        assert!(db_type.created_at.is_none());
1526        assert!(db_type.settlement_entry_price.is_none());
1527        assert!(db_type.cost_basis.is_none());
1528        assert!(db_type.net_pnl.is_none());
1529    }
1530
1531    #[test]
1532    fn liquidation_state_roundtrip() {
1533        let naive_dt = chrono::DateTime::from_timestamp(1_700_000_000, 0)
1534            .unwrap()
1535            .naive_utc();
1536        let diesel_row = super::LiquidationStateRecord {
1537            wallet_address: hypercall_types::test_wallet!(10),
1538            state: "PreLiquidation".to_string(),
1539            margin_mode: "portfolio".to_string(),
1540            equity: dec!(50000),
1541            mm_required: dec!(45000),
1542            maintenance_margin: dec!(40000),
1543            liquidation_mode: Some("partial".to_string()),
1544            target_equity: Some(dec!(55000)),
1545            entered_pre_liq_at: Some(1_700_000_000_000),
1546            mm_shortfall: Some(dec!(5000)),
1547            escalation_deadline: Some(1_700_000_060_000),
1548            last_reprice_at: Some(1_700_000_010_000),
1549            partial_order_request_ids: Some(serde_json::json!(["req-1"])),
1550            partial_order_client_ids: Some(serde_json::json!(["cid-1"])),
1551            partial_bonus_bps: Some(50),
1552            auction_id: Some("auction-abc".to_string()),
1553            request_id: Some("req-xyz".to_string()),
1554            tx_hash: Some("0xdead".to_string()),
1555            auction_started_at: Some(1_700_000_020_000),
1556            chain_start_time: Some(1_700_000_015),
1557            margin_needed: Some(dec!(10000)),
1558            stop_request_id: Some("stop-1".to_string()),
1559            stop_tx_hash: Some("0xbeef".to_string()),
1560            liquidated_at: None,
1561            resolved_winner: Some(hypercall_types::test_wallet!(11)),
1562            resolved_bonus: Some(dec!(500)),
1563            resolution_tx_hash: Some("0xcafe".to_string()),
1564            last_observed_block: Some(12345),
1565            updated_at_ms: Some(1_700_000_050_000),
1566            created_at: Some(naive_dt),
1567            updated_at: Some(naive_dt),
1568        };
1569        let db_type: hypercall_db::LiquidationStateRecord = diesel_row.into();
1570        assert_eq!(db_type.wallet_address, hypercall_types::test_wallet!(10));
1571        assert_eq!(db_type.state, "PreLiquidation");
1572        assert_eq!(db_type.margin_mode, "portfolio");
1573        assert_eq!(db_type.equity, dec!(50000));
1574        assert_eq!(db_type.mm_required, dec!(45000));
1575        assert_eq!(db_type.maintenance_margin, dec!(40000));
1576        assert_eq!(db_type.liquidation_mode, Some("partial".to_string()));
1577        assert_eq!(db_type.target_equity, Some(dec!(55000)));
1578        assert_eq!(db_type.entered_pre_liq_at, Some(1_700_000_000_000));
1579        assert_eq!(db_type.mm_shortfall, Some(dec!(5000)));
1580        assert_eq!(db_type.escalation_deadline, Some(1_700_000_060_000));
1581        assert_eq!(db_type.last_reprice_at, Some(1_700_000_010_000));
1582        assert!(db_type.partial_order_request_ids.is_some());
1583        assert!(db_type.partial_order_client_ids.is_some());
1584        assert_eq!(db_type.partial_bonus_bps, Some(50));
1585        assert_eq!(db_type.auction_id, Some("auction-abc".to_string()));
1586        assert_eq!(db_type.request_id, Some("req-xyz".to_string()));
1587        assert_eq!(db_type.tx_hash, Some("0xdead".to_string()));
1588        assert_eq!(db_type.auction_started_at, Some(1_700_000_020_000));
1589        assert_eq!(db_type.chain_start_time, Some(1_700_000_015));
1590        assert_eq!(db_type.margin_needed, Some(dec!(10000)));
1591        assert_eq!(db_type.stop_request_id, Some("stop-1".to_string()));
1592        assert_eq!(db_type.stop_tx_hash, Some("0xbeef".to_string()));
1593        assert!(db_type.liquidated_at.is_none());
1594        assert_eq!(
1595            db_type.resolved_winner,
1596            Some(hypercall_types::test_wallet!(11))
1597        );
1598        assert_eq!(db_type.resolved_bonus, Some(dec!(500)));
1599        assert_eq!(db_type.resolution_tx_hash, Some("0xcafe".to_string()));
1600        assert_eq!(db_type.last_observed_block, Some(12345));
1601        assert_eq!(db_type.updated_at_ms, Some(1_700_000_050_000));
1602        assert_eq!(db_type.created_at, Some(naive_dt));
1603        assert_eq!(db_type.updated_at, Some(naive_dt));
1604    }
1605
1606    #[test]
1607    fn liquidation_state_all_optionals_none() {
1608        let diesel_row = super::LiquidationStateRecord {
1609            wallet_address: hypercall_types::test_wallet!(12),
1610            state: "Healthy".to_string(),
1611            margin_mode: "standard".to_string(),
1612            equity: dec!(100000),
1613            mm_required: dec!(10000),
1614            maintenance_margin: dec!(15000),
1615            liquidation_mode: None,
1616            target_equity: None,
1617            entered_pre_liq_at: None,
1618            mm_shortfall: None,
1619            escalation_deadline: None,
1620            last_reprice_at: None,
1621            partial_order_request_ids: None,
1622            partial_order_client_ids: None,
1623            partial_bonus_bps: None,
1624            auction_id: None,
1625            request_id: None,
1626            tx_hash: None,
1627            auction_started_at: None,
1628            chain_start_time: None,
1629            margin_needed: None,
1630            stop_request_id: None,
1631            stop_tx_hash: None,
1632            liquidated_at: None,
1633            resolved_winner: None,
1634            resolved_bonus: None,
1635            resolution_tx_hash: None,
1636            last_observed_block: None,
1637            updated_at_ms: None,
1638            created_at: None,
1639            updated_at: None,
1640        };
1641        let db_type: hypercall_db::LiquidationStateRecord = diesel_row.into();
1642        assert_eq!(db_type.state, "Healthy");
1643        assert!(db_type.liquidation_mode.is_none());
1644        assert!(db_type.target_equity.is_none());
1645        assert!(db_type.created_at.is_none());
1646        assert!(db_type.updated_at.is_none());
1647    }
1648
1649    #[test]
1650    fn liquidation_history_roundtrip() {
1651        let naive_dt = chrono::DateTime::from_timestamp(1_700_000_000, 0)
1652            .unwrap()
1653            .naive_utc();
1654        let details = serde_json::json!({"margin_ratio": 0.85});
1655        let diesel_row = super::LiquidationHistoryRecord {
1656            id: 201,
1657            wallet_address: hypercall_types::test_wallet!(13),
1658            previous_state: "PreLiquidation".to_string(),
1659            new_state: "InLiquidation".to_string(),
1660            liquidation_mode: Some("full".to_string()),
1661            equity: dec!(30000),
1662            mm_required: dec!(40000),
1663            maintenance_margin: dec!(35000),
1664            shortfall: dec!(10000),
1665            auction_id: Some("auction-123".to_string()),
1666            request_id: Some("req-456".to_string()),
1667            tx_hash: Some("0xabcd".to_string()),
1668            margin_needed: Some(dec!(15000)),
1669            winner_address: Some(hypercall_types::test_wallet!(14)),
1670            bonus: Some(dec!(1000)),
1671            details: details.clone(),
1672            timestamp: 1_700_000_000_000,
1673            created_at: Some(naive_dt),
1674        };
1675        let db_type: hypercall_db::LiquidationHistoryRecord = diesel_row.into();
1676        assert_eq!(db_type.id, 201);
1677        assert_eq!(db_type.wallet_address, hypercall_types::test_wallet!(13));
1678        assert_eq!(db_type.previous_state, "PreLiquidation");
1679        assert_eq!(db_type.new_state, "InLiquidation");
1680        assert_eq!(db_type.liquidation_mode, Some("full".to_string()));
1681        assert_eq!(db_type.equity, dec!(30000));
1682        assert_eq!(db_type.mm_required, dec!(40000));
1683        assert_eq!(db_type.maintenance_margin, dec!(35000));
1684        assert_eq!(db_type.shortfall, dec!(10000));
1685        assert_eq!(db_type.auction_id, Some("auction-123".to_string()));
1686        assert_eq!(db_type.request_id, Some("req-456".to_string()));
1687        assert_eq!(db_type.tx_hash, Some("0xabcd".to_string()));
1688        assert_eq!(db_type.margin_needed, Some(dec!(15000)));
1689        assert_eq!(
1690            db_type.winner_address,
1691            Some(hypercall_types::test_wallet!(14))
1692        );
1693        assert_eq!(db_type.bonus, Some(dec!(1000)));
1694        assert_eq!(db_type.details, details);
1695        assert_eq!(db_type.timestamp, 1_700_000_000_000);
1696        assert_eq!(db_type.created_at, Some(naive_dt));
1697    }
1698
1699    #[test]
1700    fn liquidation_history_optionals_none() {
1701        let diesel_row = super::LiquidationHistoryRecord {
1702            id: 1,
1703            wallet_address: hypercall_types::test_wallet!(15),
1704            previous_state: "Healthy".to_string(),
1705            new_state: "PreLiquidation".to_string(),
1706            liquidation_mode: None,
1707            equity: dec!(100),
1708            mm_required: dec!(200),
1709            maintenance_margin: dec!(150),
1710            shortfall: dec!(100),
1711            auction_id: None,
1712            request_id: None,
1713            tx_hash: None,
1714            margin_needed: None,
1715            winner_address: None,
1716            bonus: None,
1717            details: serde_json::json!({}),
1718            timestamp: 0,
1719            created_at: None,
1720        };
1721        let db_type: hypercall_db::LiquidationHistoryRecord = diesel_row.into();
1722        assert!(db_type.liquidation_mode.is_none());
1723        assert!(db_type.auction_id.is_none());
1724        assert!(db_type.winner_address.is_none());
1725        assert!(db_type.bonus.is_none());
1726        assert!(db_type.created_at.is_none());
1727    }
1728
1729    #[test]
1730    fn liquidation_auction_roundtrip() {
1731        let naive_dt = chrono::DateTime::from_timestamp(1_700_000_000, 0)
1732            .unwrap()
1733            .naive_utc();
1734        let positions = serde_json::json!([{"symbol": "BTC-PERP", "size": 1.0}]);
1735        let diesel_row = super::LiquidationAuctionRecord {
1736            auction_id: "auction-999".to_string(),
1737            wallet_address: hypercall_types::test_wallet!(16),
1738            status: "active".to_string(),
1739            positions: positions.clone(),
1740            equity_at_start: dec!(20000),
1741            mm_shortfall_at_start: dec!(5000),
1742            target_equity: Some(dec!(25000)),
1743            request_id: Some("req-auc".to_string()),
1744            tx_hash: Some("0x1111".to_string()),
1745            started_at: 1_700_000_000_000,
1746            chain_start_time: Some(1_700_000_005),
1747            margin_needed: Some(dec!(8000)),
1748            stop_request_id: Some("stop-auc".to_string()),
1749            stop_tx_hash: Some("0x2222".to_string()),
1750            completed_at: Some(1_700_000_100_000),
1751            liquidator_address: Some(hypercall_types::test_wallet!(17)),
1752            bonus: Some(dec!(750)),
1753            settlement_value: Some(dec!(19000)),
1754            last_observed_block: Some(99999),
1755            created_at: Some(naive_dt),
1756            updated_at: Some(naive_dt),
1757        };
1758        let db_type: hypercall_db::LiquidationAuctionRecord = diesel_row.into();
1759        assert_eq!(db_type.auction_id, "auction-999");
1760        assert_eq!(db_type.wallet_address, hypercall_types::test_wallet!(16));
1761        assert_eq!(db_type.status, "active");
1762        assert_eq!(db_type.positions, positions);
1763        assert_eq!(db_type.equity_at_start, dec!(20000));
1764        assert_eq!(db_type.mm_shortfall_at_start, dec!(5000));
1765        assert_eq!(db_type.target_equity, Some(dec!(25000)));
1766        assert_eq!(db_type.request_id, Some("req-auc".to_string()));
1767        assert_eq!(db_type.tx_hash, Some("0x1111".to_string()));
1768        assert_eq!(db_type.started_at, 1_700_000_000_000);
1769        assert_eq!(db_type.chain_start_time, Some(1_700_000_005));
1770        assert_eq!(db_type.margin_needed, Some(dec!(8000)));
1771        assert_eq!(db_type.stop_request_id, Some("stop-auc".to_string()));
1772        assert_eq!(db_type.stop_tx_hash, Some("0x2222".to_string()));
1773        assert_eq!(db_type.completed_at, Some(1_700_000_100_000));
1774        assert_eq!(
1775            db_type.liquidator_address,
1776            Some(hypercall_types::test_wallet!(17))
1777        );
1778        assert_eq!(db_type.bonus, Some(dec!(750)));
1779        assert_eq!(db_type.settlement_value, Some(dec!(19000)));
1780        assert_eq!(db_type.last_observed_block, Some(99999));
1781        assert_eq!(db_type.created_at, Some(naive_dt));
1782        assert_eq!(db_type.updated_at, Some(naive_dt));
1783    }
1784
1785    #[test]
1786    fn liquidation_auction_optionals_none() {
1787        let diesel_row = super::LiquidationAuctionRecord {
1788            auction_id: "auction-1".to_string(),
1789            wallet_address: hypercall_types::test_wallet!(18),
1790            status: "pending".to_string(),
1791            positions: serde_json::json!([]),
1792            equity_at_start: dec!(1000),
1793            mm_shortfall_at_start: dec!(100),
1794            target_equity: None,
1795            request_id: None,
1796            tx_hash: None,
1797            started_at: 0,
1798            chain_start_time: None,
1799            margin_needed: None,
1800            stop_request_id: None,
1801            stop_tx_hash: None,
1802            completed_at: None,
1803            liquidator_address: None,
1804            bonus: None,
1805            settlement_value: None,
1806            last_observed_block: None,
1807            created_at: None,
1808            updated_at: None,
1809        };
1810        let db_type: hypercall_db::LiquidationAuctionRecord = diesel_row.into();
1811        assert_eq!(db_type.status, "pending");
1812        assert!(db_type.target_equity.is_none());
1813        assert!(db_type.request_id.is_none());
1814        assert!(db_type.completed_at.is_none());
1815        assert!(db_type.liquidator_address.is_none());
1816        assert!(db_type.bonus.is_none());
1817        assert!(db_type.created_at.is_none());
1818        assert!(db_type.updated_at.is_none());
1819    }
1820
1821    #[test]
1822    fn rsm_signer_nonce_roundtrip() {
1823        let naive_dt = chrono::DateTime::from_timestamp(1_700_000_000, 0)
1824            .unwrap()
1825            .naive_utc();
1826        let diesel_row = RsmSignerNonceRecord {
1827            signer_address: hypercall_types::test_wallet!(20),
1828            next_nonce: 42,
1829            last_synced_nonce: Some(40),
1830            created_at: Some(naive_dt),
1831            updated_at: Some(naive_dt),
1832        };
1833        let db_type: hypercall_db::RsmSignerNonceRecord = diesel_row.into();
1834        assert_eq!(db_type.signer_address, hypercall_types::test_wallet!(20));
1835        assert_eq!(db_type.next_nonce, 42);
1836        assert_eq!(db_type.last_synced_nonce, Some(40));
1837        assert_eq!(db_type.created_at, Some(naive_dt));
1838        assert_eq!(db_type.updated_at, Some(naive_dt));
1839    }
1840
1841    #[test]
1842    fn rsm_signer_nonce_optionals_none() {
1843        let diesel_row = RsmSignerNonceRecord {
1844            signer_address: hypercall_types::test_wallet!(21),
1845            next_nonce: 0,
1846            last_synced_nonce: None,
1847            created_at: None,
1848            updated_at: None,
1849        };
1850        let db_type: hypercall_db::RsmSignerNonceRecord = diesel_row.into();
1851        assert_eq!(db_type.next_nonce, 0);
1852        assert!(db_type.last_synced_nonce.is_none());
1853        assert!(db_type.created_at.is_none());
1854        assert!(db_type.updated_at.is_none());
1855    }
1856}