Skip to main content

deribit_http/model/response/
transfer.rs

1/******************************************************************************
2   Author: Joaquín Béjar García
3   Email: jb@taunais.com
4   Date: 15/9/25
5******************************************************************************/
6//! Transfer response models for internal transfers between subaccounts.
7
8use serde::{Deserialize, Serialize};
9
10/// State of an internal transfer
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
12#[serde(rename_all = "snake_case")]
13pub enum InternalTransferState {
14    /// Transfer is prepared but not yet confirmed
15    #[default]
16    Prepared,
17    /// Transfer has been confirmed
18    Confirmed,
19    /// Transfer has been cancelled
20    Cancelled,
21    /// Transfer is waiting for admin approval
22    WaitingForAdmin,
23}
24
25/// Direction of an internal transfer
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
27#[serde(rename_all = "snake_case")]
28pub enum TransferDirection {
29    /// Outgoing payment
30    #[default]
31    Payment,
32    /// Incoming receipt
33    Income,
34}
35
36/// Type of internal transfer
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
38#[serde(rename_all = "snake_case")]
39pub enum InternalTransferType {
40    /// Transfer between subaccounts
41    #[default]
42    Subaccount,
43    /// Transfer to/from user
44    User,
45}
46
47/// Internal transfer information (between subaccounts or users)
48///
49/// This model represents transfers within the Deribit platform,
50/// not blockchain withdrawals/deposits.
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct InternalTransfer {
53    /// Transfer ID
54    pub id: i64,
55    /// Currency being transferred (e.g., "BTC", "ETH")
56    pub currency: String,
57    /// Transfer amount
58    pub amount: f64,
59    /// Direction of the transfer (payment or income)
60    pub direction: TransferDirection,
61    /// The other party in the transfer (username or subaccount name)
62    pub other_side: String,
63    /// Current transfer state
64    pub state: InternalTransferState,
65    /// Type of transfer (subaccount or user)
66    #[serde(rename = "type")]
67    pub transfer_type: InternalTransferType,
68    /// Creation timestamp in milliseconds since Unix epoch
69    pub created_timestamp: i64,
70    /// Last update timestamp in milliseconds since Unix epoch
71    pub updated_timestamp: i64,
72}
73
74impl InternalTransfer {
75    /// Returns `true` if the transfer is confirmed
76    #[must_use]
77    pub fn is_confirmed(&self) -> bool {
78        self.state == InternalTransferState::Confirmed
79    }
80
81    /// Returns `true` if the transfer is cancelled
82    #[must_use]
83    pub fn is_cancelled(&self) -> bool {
84        self.state == InternalTransferState::Cancelled
85    }
86
87    /// Returns `true` if the transfer is pending (prepared or waiting for admin)
88    #[must_use]
89    pub fn is_pending(&self) -> bool {
90        matches!(
91            self.state,
92            InternalTransferState::Prepared | InternalTransferState::WaitingForAdmin
93        )
94    }
95
96    /// Returns `true` if this is an outgoing payment
97    #[must_use]
98    pub fn is_payment(&self) -> bool {
99        self.direction == TransferDirection::Payment
100    }
101
102    /// Returns `true` if this is an incoming transfer
103    #[must_use]
104    pub fn is_income(&self) -> bool {
105        self.direction == TransferDirection::Income
106    }
107}
108
109/// Response for get_transfers endpoint
110///
111/// Contains a paginated list of internal transfers with total count.
112#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
113pub struct TransfersResponse {
114    /// Total number of transfers matching the query
115    pub count: u32,
116    /// List of transfer items
117    pub data: Vec<InternalTransfer>,
118}
119
120impl TransfersResponse {
121    /// Returns `true` if there are no transfers
122    #[must_use]
123    pub fn is_empty(&self) -> bool {
124        self.data.is_empty()
125    }
126
127    /// Returns the number of transfers in this response
128    #[must_use]
129    pub fn len(&self) -> usize {
130        self.data.len()
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_internal_transfer_deserialization() {
140        let json = r#"{
141            "id": 2,
142            "created_timestamp": 1550579457727,
143            "updated_timestamp": 1550579457727,
144            "currency": "BTC",
145            "amount": 0.2,
146            "direction": "payment",
147            "other_side": "new_user_1_1",
148            "state": "confirmed",
149            "type": "subaccount"
150        }"#;
151
152        let transfer: InternalTransfer = serde_json::from_str(json).unwrap();
153        assert_eq!(transfer.id, 2);
154        assert_eq!(transfer.currency, "BTC");
155        assert!((transfer.amount - 0.2).abs() < f64::EPSILON);
156        assert_eq!(transfer.direction, TransferDirection::Payment);
157        assert_eq!(transfer.other_side, "new_user_1_1");
158        assert_eq!(transfer.state, InternalTransferState::Confirmed);
159        assert_eq!(transfer.transfer_type, InternalTransferType::Subaccount);
160        assert!(transfer.is_confirmed());
161        assert!(transfer.is_payment());
162    }
163
164    #[test]
165    fn test_transfers_response_deserialization() {
166        let json = r#"{
167            "count": 1,
168            "data": [{
169                "id": 2,
170                "created_timestamp": 1550579457727,
171                "updated_timestamp": 1550579457727,
172                "currency": "BTC",
173                "amount": 0.2,
174                "direction": "payment",
175                "other_side": "new_user_1_1",
176                "state": "confirmed",
177                "type": "subaccount"
178            }]
179        }"#;
180
181        let response: TransfersResponse = serde_json::from_str(json).unwrap();
182        assert_eq!(response.count, 1);
183        assert_eq!(response.len(), 1);
184        assert!(!response.is_empty());
185    }
186
187    #[test]
188    fn test_transfer_state_helpers() {
189        let mut transfer = InternalTransfer {
190            id: 1,
191            currency: "BTC".to_string(),
192            amount: 1.0,
193            direction: TransferDirection::Payment,
194            other_side: "test".to_string(),
195            state: InternalTransferState::Prepared,
196            transfer_type: InternalTransferType::Subaccount,
197            created_timestamp: 0,
198            updated_timestamp: 0,
199        };
200
201        assert!(transfer.is_pending());
202        assert!(!transfer.is_confirmed());
203        assert!(!transfer.is_cancelled());
204
205        transfer.state = InternalTransferState::Confirmed;
206        assert!(!transfer.is_pending());
207        assert!(transfer.is_confirmed());
208
209        transfer.state = InternalTransferState::Cancelled;
210        assert!(transfer.is_cancelled());
211    }
212}