1use 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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
169pub enum InstrumentStatus {
170 #[default]
172 Active,
173 ExpiredPendingPrice,
175 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 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#[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#[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#[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#[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#[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#[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#[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 #[convert_field(custom_fn = ".parse().expect(\"corrupt margin_mode in user_tiers row\")")]
405 pub margin_mode: String,
406 pub version: i64,
408 pub max_open_orders: Option<i32>,
410 pub max_open_positions: i32,
412 pub orders_per_minute: i32,
414 pub cancels_per_minute: i32,
416 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 #[diesel(treat_none_as_default_value = true)]
427 pub margin_mode: Option<String>,
428 #[diesel(treat_none_as_default_value = true)]
430 pub version: Option<i64>,
431 #[diesel(treat_none_as_default_value = true)]
433 pub max_open_orders: Option<i32>,
434 #[diesel(treat_none_as_default_value = true)]
436 pub max_open_positions: Option<i32>,
437 #[diesel(treat_none_as_default_value = true)]
439 pub orders_per_minute: Option<i32>,
440 #[diesel(treat_none_as_default_value = true)]
442 pub cancels_per_minute: Option<i32>,
443 #[diesel(treat_none_as_default_value = true)]
445 pub api_requests_per_minute: Option<i32>,
446}
447
448#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
1128pub struct JournalCommandForReplay {
1129 pub command_id: i64,
1130 pub request_id: String,
1131 pub command_type: String,
1132 pub command_data: Vec<u8>,
1134 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#[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}