Skip to main content

circle_developer_controlled_wallets/models/
common.rs

1//! Common types shared across the Developer-Controlled Wallets API.
2//!
3//! Includes shared pagination, blockchain, error, and identifier types used
4//! across developer-controlled wallet endpoints.
5
6/// Blockchain network identifier.
7#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
8pub enum Blockchain {
9    /// Ethereum mainnet.
10    #[serde(rename = "ETH")]
11    Eth,
12    /// Ethereum Sepolia testnet.
13    #[serde(rename = "ETH-SEPOLIA")]
14    EthSepolia,
15    /// Avalanche C-Chain mainnet.
16    #[serde(rename = "AVAX")]
17    Avax,
18    /// Avalanche Fuji testnet.
19    #[serde(rename = "AVAX-FUJI")]
20    AvaxFuji,
21    /// Polygon PoS mainnet.
22    #[serde(rename = "MATIC")]
23    Matic,
24    /// Polygon Amoy testnet.
25    #[serde(rename = "MATIC-AMOY")]
26    MaticAmoy,
27    /// Solana mainnet.
28    #[serde(rename = "SOL")]
29    Sol,
30    /// Solana devnet.
31    #[serde(rename = "SOL-DEVNET")]
32    SolDevnet,
33    /// Arbitrum One mainnet.
34    #[serde(rename = "ARB")]
35    Arb,
36    /// Arbitrum Sepolia testnet.
37    #[serde(rename = "ARB-SEPOLIA")]
38    ArbSepolia,
39    /// NEAR mainnet.
40    #[serde(rename = "NEAR")]
41    Near,
42    /// NEAR testnet.
43    #[serde(rename = "NEAR-TESTNET")]
44    NearTestnet,
45    /// Generic EVM mainnet.
46    #[serde(rename = "EVM")]
47    Evm,
48    /// Generic EVM testnet.
49    #[serde(rename = "EVM-TESTNET")]
50    EvmTestnet,
51    /// Unichain mainnet.
52    #[serde(rename = "UNI")]
53    Uni,
54    /// Unichain Sepolia testnet.
55    #[serde(rename = "UNI-SEPOLIA")]
56    UniSepolia,
57    /// Base mainnet.
58    #[serde(rename = "BASE")]
59    Base,
60    /// Base Sepolia testnet.
61    #[serde(rename = "BASE-SEPOLIA")]
62    BaseSepolia,
63    /// Optimism mainnet.
64    #[serde(rename = "OP")]
65    Op,
66    /// Optimism Sepolia testnet.
67    #[serde(rename = "OP-SEPOLIA")]
68    OpSepolia,
69    /// Aptos mainnet.
70    #[serde(rename = "APTOS")]
71    Aptos,
72    /// Aptos testnet.
73    #[serde(rename = "APTOS-TESTNET")]
74    AptosTestnet,
75    /// ARC testnet.
76    #[serde(rename = "ARC-TESTNET")]
77    ArcTestnet,
78    /// Monad mainnet.
79    #[serde(rename = "MONAD")]
80    Monad,
81    /// Monad testnet.
82    #[serde(rename = "MONAD-TESTNET")]
83    MonadTestnet,
84}
85
86/// EVM-compatible blockchain network identifier.
87#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
88pub enum EvmBlockchain {
89    /// Ethereum mainnet.
90    #[serde(rename = "ETH")]
91    Eth,
92    /// Ethereum Sepolia testnet.
93    #[serde(rename = "ETH-SEPOLIA")]
94    EthSepolia,
95    /// Avalanche C-Chain mainnet.
96    #[serde(rename = "AVAX")]
97    Avax,
98    /// Avalanche Fuji testnet.
99    #[serde(rename = "AVAX-FUJI")]
100    AvaxFuji,
101    /// Polygon PoS mainnet.
102    #[serde(rename = "MATIC")]
103    Matic,
104    /// Polygon Amoy testnet.
105    #[serde(rename = "MATIC-AMOY")]
106    MaticAmoy,
107    /// Arbitrum One mainnet.
108    #[serde(rename = "ARB")]
109    Arb,
110    /// Arbitrum Sepolia testnet.
111    #[serde(rename = "ARB-SEPOLIA")]
112    ArbSepolia,
113    /// Unichain mainnet.
114    #[serde(rename = "UNI")]
115    Uni,
116    /// Unichain Sepolia testnet.
117    #[serde(rename = "UNI-SEPOLIA")]
118    UniSepolia,
119    /// Base mainnet.
120    #[serde(rename = "BASE")]
121    Base,
122    /// Base Sepolia testnet.
123    #[serde(rename = "BASE-SEPOLIA")]
124    BaseSepolia,
125    /// Optimism mainnet.
126    #[serde(rename = "OP")]
127    Op,
128    /// Optimism Sepolia testnet.
129    #[serde(rename = "OP-SEPOLIA")]
130    OpSepolia,
131    /// Generic EVM mainnet.
132    #[serde(rename = "EVM")]
133    Evm,
134    /// Generic EVM testnet.
135    #[serde(rename = "EVM-TESTNET")]
136    EvmTestnet,
137    /// ARC testnet.
138    #[serde(rename = "ARC-TESTNET")]
139    ArcTestnet,
140    /// Monad mainnet.
141    #[serde(rename = "MONAD")]
142    Monad,
143    /// Monad testnet.
144    #[serde(rename = "MONAD-TESTNET")]
145    MonadTestnet,
146}
147
148/// Custody type for a wallet.
149#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
150#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
151pub enum CustodyType {
152    /// Wallet is under developer custody.
153    Developer,
154    /// Wallet is under end-user custody.
155    Enduser,
156}
157
158/// Smart contract account type.
159#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
160#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
161pub enum AccountType {
162    /// Smart Contract Account (ERC-4337).
163    Sca,
164    /// Externally Owned Account.
165    Eoa,
166}
167
168/// Wallet lifecycle state.
169#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
170#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
171pub enum WalletState {
172    /// Wallet is active and can send transactions.
173    Live,
174    /// Wallet is frozen and cannot send transactions.
175    Frozen,
176}
177
178/// Transaction fee priority level.
179#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
180#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
181pub enum FeeLevel {
182    /// Low priority.
183    Low,
184    /// Medium priority.
185    Medium,
186    /// High priority.
187    High,
188}
189
190/// Token standard.
191#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
192pub enum TokenStandard {
193    /// ERC-20 fungible token.
194    #[serde(rename = "ERC20")]
195    Erc20,
196    /// ERC-721 non-fungible token.
197    #[serde(rename = "ERC721")]
198    Erc721,
199    /// ERC-1155 multi-token.
200    #[serde(rename = "ERC1155")]
201    Erc1155,
202    /// Solana fungible token.
203    #[serde(rename = "Fungible")]
204    Fungible,
205    /// Aptos fungible asset.
206    #[serde(rename = "FungibleAsset")]
207    FungibleAsset,
208    /// Solana non-fungible token.
209    #[serde(rename = "NonFungible")]
210    NonFungible,
211    /// Solana non-fungible edition.
212    #[serde(rename = "NonFungibleEdition")]
213    NonFungibleEdition,
214    /// Solana programmable non-fungible token.
215    #[serde(rename = "ProgrammableNonFungible")]
216    ProgrammableNonFungible,
217    /// Solana programmable non-fungible edition.
218    #[serde(rename = "ProgrammableNonFungibleEdition")]
219    ProgrammableNonFungibleEdition,
220}
221
222/// Pagination cursor parameters shared across list endpoints.
223#[derive(Debug, Default, Clone, serde::Serialize)]
224#[serde(rename_all = "camelCase")]
225pub struct PageParams {
226    /// Start of date-time range (ISO-8601, inclusive).
227    #[serde(skip_serializing_if = "Option::is_none")]
228    pub from: Option<String>,
229    /// End of date-time range (ISO-8601, inclusive).
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub to: Option<String>,
232    /// Cursor for the previous page (exclusive end).
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub page_before: Option<String>,
235    /// Cursor for the next page (exclusive start).
236    #[serde(skip_serializing_if = "Option::is_none")]
237    pub page_after: Option<String>,
238    /// Maximum number of items to return (1–50, default 10).
239    #[serde(skip_serializing_if = "Option::is_none")]
240    pub page_size: Option<u32>,
241}
242
243/// Transaction fee breakdown (all fields optional / chain-dependent).
244#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
245#[serde(rename_all = "camelCase")]
246pub struct TransactionFee {
247    /// Gas limit (EVM).
248    #[serde(skip_serializing_if = "Option::is_none")]
249    pub gas_limit: Option<String>,
250    /// Gas price in wei (legacy transactions).
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub gas_price: Option<String>,
253    /// Max fee per gas (EIP-1559).
254    #[serde(skip_serializing_if = "Option::is_none")]
255    pub max_fee: Option<String>,
256    /// Max priority fee per gas (EIP-1559).
257    #[serde(skip_serializing_if = "Option::is_none")]
258    pub priority_fee: Option<String>,
259    /// Base fee per gas (EIP-1559).
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub base_fee: Option<String>,
262    /// Total network fee in native currency.
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub network_fee: Option<String>,
265    /// Raw network fee value.
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub network_fee_raw: Option<String>,
268    /// L1 data fee (Optimism and Base).
269    #[serde(skip_serializing_if = "Option::is_none")]
270    pub l1_fee: Option<String>,
271}
272
273/// Error response body returned by the Circle API on non-2xx status codes.
274#[derive(Debug, Clone, serde::Deserialize)]
275pub struct ApiErrorBody {
276    /// Numeric error code.
277    pub code: i32,
278    /// Human-readable error message.
279    pub message: String,
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285
286    #[test]
287    fn blockchain_eth_serializes() -> Result<(), Box<dyn std::error::Error>> {
288        let json = serde_json::to_string(&Blockchain::Eth)?;
289        assert_eq!(json, "\"ETH\"");
290        Ok(())
291    }
292
293    #[test]
294    fn blockchain_eth_sepolia_round_trips() -> Result<(), Box<dyn std::error::Error>> {
295        let serialized = serde_json::to_string(&Blockchain::EthSepolia)?;
296        assert_eq!(serialized, "\"ETH-SEPOLIA\"");
297        let deserialized: Blockchain = serde_json::from_str(&serialized)?;
298        assert_eq!(deserialized, Blockchain::EthSepolia);
299        Ok(())
300    }
301
302    #[test]
303    fn blockchain_sol_devnet_round_trips() -> Result<(), Box<dyn std::error::Error>> {
304        let serialized = serde_json::to_string(&Blockchain::SolDevnet)?;
305        assert_eq!(serialized, "\"SOL-DEVNET\"");
306        let deserialized: Blockchain = serde_json::from_str(&serialized)?;
307        assert_eq!(deserialized, Blockchain::SolDevnet);
308        Ok(())
309    }
310
311    #[test]
312    fn custody_type_serializes() -> Result<(), Box<dyn std::error::Error>> {
313        let json = serde_json::to_string(&CustodyType::Developer)?;
314        assert_eq!(json, "\"DEVELOPER\"");
315        Ok(())
316    }
317
318    #[test]
319    fn token_standard_erc20_round_trips() -> Result<(), Box<dyn std::error::Error>> {
320        let json = serde_json::to_string(&TokenStandard::Erc20)?;
321        assert_eq!(json, "\"ERC20\"");
322        let back: TokenStandard = serde_json::from_str(&json)?;
323        assert_eq!(back, TokenStandard::Erc20);
324        Ok(())
325    }
326
327    #[test]
328    fn fee_level_serializes() -> Result<(), Box<dyn std::error::Error>> {
329        let json = serde_json::to_string(&FeeLevel::Medium)?;
330        assert_eq!(json, "\"MEDIUM\"");
331        Ok(())
332    }
333}