Skip to main content

circle_user_controlled_wallets/models/
wallet.rs

1//! Wallet resource models for the Circle User-Controlled Wallets API.
2//!
3//! Contains request parameters and response types for wallet management
4//! endpoints, including balances, NFTs, and associated token data.
5
6use serde::{Deserialize, Serialize};
7
8use super::common::{
9    AccountType, Blockchain, CustodyType, PageParams, ScaCore, TokenStandard, WalletState,
10};
11
12// ── Wallet metadata ───────────────────────────────────────────────────────────
13
14/// Optional name / reference metadata for a wallet.
15#[derive(Debug, Clone, Default, Deserialize, Serialize)]
16#[serde(rename_all = "camelCase")]
17pub struct WalletMetadata {
18    /// Display name for the wallet.
19    pub name: Option<String>,
20    /// Application-defined reference identifier.
21    pub ref_id: Option<String>,
22}
23
24// ── Token ─────────────────────────────────────────────────────────────────────
25
26/// On-chain token descriptor.
27#[derive(Debug, Clone, Deserialize, Serialize)]
28#[serde(rename_all = "camelCase")]
29pub struct Token {
30    /// Circle-assigned token ID.
31    pub id: String,
32    /// Blockchain this token lives on.
33    pub blockchain: Blockchain,
34    /// `true` if this is the native coin of the chain (e.g. ETH, MATIC).
35    pub is_native: bool,
36    /// Human-readable token name.
37    pub name: Option<String>,
38    /// Token standard.
39    pub standard: Option<TokenStandard>,
40    /// Number of decimal places.
41    pub decimals: Option<i32>,
42    /// Token ticker symbol.
43    pub symbol: Option<String>,
44    /// On-chain contract address (absent for native coins).
45    pub token_address: Option<String>,
46    /// ISO 8601 last-updated timestamp.
47    pub update_date: String,
48    /// ISO 8601 creation timestamp.
49    pub create_date: String,
50}
51
52// ── Balance ───────────────────────────────────────────────────────────────────
53
54/// A token balance held by a wallet.
55#[derive(Debug, Clone, Deserialize, Serialize)]
56#[serde(rename_all = "camelCase")]
57pub struct Balance {
58    /// Decimal string representation of the amount.
59    pub amount: String,
60    /// Token descriptor.
61    pub token: Token,
62    /// ISO 8601 timestamp of the last balance update.
63    pub update_date: String,
64}
65
66/// `data` payload wrapping a list of balances.
67#[derive(Debug, Clone, Deserialize, Serialize)]
68#[serde(rename_all = "camelCase")]
69pub struct BalancesData {
70    /// Token balances held by the wallet.
71    pub token_balances: Vec<Balance>,
72}
73
74/// Response envelope for list-wallet-balances.
75#[derive(Debug, Clone, Deserialize, Serialize)]
76#[serde(rename_all = "camelCase")]
77pub struct Balances {
78    /// List of balances.
79    pub data: BalancesData,
80}
81
82// ── NFT ───────────────────────────────────────────────────────────────────────
83
84/// NFT on-chain metadata.
85#[derive(Debug, Clone, Default, Deserialize, Serialize)]
86#[serde(rename_all = "camelCase")]
87pub struct NftMetadata {
88    /// NFT display name.
89    pub name: Option<String>,
90    /// NFT description.
91    pub description: Option<String>,
92    /// URL of the NFT image.
93    pub image: Option<String>,
94}
95
96/// An NFT held by a wallet.
97#[derive(Debug, Clone, Deserialize, Serialize)]
98#[serde(rename_all = "camelCase")]
99pub struct Nft {
100    /// Token descriptor for this NFT collection.
101    pub token: Token,
102    /// Quantity held (usually "1" for ERC-721).
103    pub amount: String,
104    /// ISO 8601 last-updated timestamp.
105    pub update_date: String,
106    /// On-chain token ID within the collection.
107    pub nft_token_id: Option<String>,
108    /// Off-chain metadata (name, description, image).
109    pub metadata: Option<NftMetadata>,
110}
111
112/// `data` payload wrapping a list of NFTs.
113#[derive(Debug, Clone, Deserialize, Serialize)]
114#[serde(rename_all = "camelCase")]
115pub struct NftsData {
116    /// NFTs held by the wallet.
117    pub nfts: Vec<Nft>,
118}
119
120/// Response envelope for list-wallet-nfts.
121#[derive(Debug, Clone, Deserialize, Serialize)]
122#[serde(rename_all = "camelCase")]
123pub struct Nfts {
124    /// List of NFTs.
125    pub data: NftsData,
126}
127
128// ── Wallet ────────────────────────────────────────────────────────────────────
129
130/// A user-controlled wallet.
131#[derive(Debug, Clone, Deserialize, Serialize)]
132#[serde(rename_all = "camelCase")]
133pub struct Wallet {
134    /// Circle-assigned wallet ID.
135    pub id: String,
136    /// On-chain address for this wallet.
137    pub address: String,
138    /// Blockchain this wallet is deployed on.
139    pub blockchain: Blockchain,
140    /// ISO 8601 creation timestamp.
141    pub create_date: String,
142    /// ISO 8601 last-updated timestamp.
143    pub update_date: String,
144    /// Custody model (always `Enduser` for this API).
145    pub custody_type: CustodyType,
146    /// Optional display name.
147    pub name: Option<String>,
148    /// Application-defined reference identifier.
149    pub ref_id: Option<String>,
150    /// Current wallet state.
151    pub state: WalletState,
152    /// ID of the end-user who owns this wallet.
153    pub user_id: Option<String>,
154    /// ID of the wallet set this wallet belongs to.
155    pub wallet_set_id: String,
156    /// Initial public key at wallet creation.
157    pub initial_public_key: Option<String>,
158    /// Account type (EOA or SCA).
159    pub account_type: Option<AccountType>,
160    /// SCA core version (for smart contract accounts).
161    pub sca_core: Option<ScaCore>,
162}
163
164/// `data` payload wrapping a list of wallets.
165#[derive(Debug, Clone, Deserialize, Serialize)]
166#[serde(rename_all = "camelCase")]
167pub struct WalletsData {
168    /// List of wallets.
169    pub wallets: Vec<Wallet>,
170}
171
172/// Response envelope for list-wallets.
173#[derive(Debug, Clone, Deserialize, Serialize)]
174#[serde(rename_all = "camelCase")]
175pub struct Wallets {
176    /// Paginated wallets.
177    pub data: WalletsData,
178}
179
180/// `data` payload wrapping a single wallet.
181#[derive(Debug, Clone, Deserialize, Serialize)]
182#[serde(rename_all = "camelCase")]
183pub struct WalletData {
184    /// The wallet record.
185    pub wallet: Wallet,
186}
187
188/// Response envelope for a single-wallet operation.
189#[derive(Debug, Clone, Deserialize, Serialize)]
190#[serde(rename_all = "camelCase")]
191pub struct WalletResponse {
192    /// Wallet data.
193    pub data: WalletData,
194}
195
196// ── Token response types ──────────────────────────────────────────────────────
197
198/// `data` payload wrapping a single token.
199#[derive(Debug, Clone, Deserialize, Serialize)]
200#[serde(rename_all = "camelCase")]
201pub struct TokenData {
202    /// The token descriptor.
203    pub token: Token,
204}
205
206/// Response envelope for `getToken`.
207#[derive(Debug, Clone, Deserialize, Serialize)]
208#[serde(rename_all = "camelCase")]
209pub struct TokenResponse {
210    /// Token data.
211    pub data: TokenData,
212}
213
214// ── Request / param types ─────────────────────────────────────────────────────
215
216/// Request body for `updateWallet`.
217#[derive(Debug, Clone, Default, Deserialize, Serialize)]
218#[serde(rename_all = "camelCase")]
219pub struct UpdateWalletRequest {
220    /// New display name.
221    pub name: Option<String>,
222    /// New application reference ID.
223    pub ref_id: Option<String>,
224}
225
226/// Query parameters for `listWallets`.
227#[derive(Debug, Clone, Default, Deserialize, Serialize)]
228#[serde(rename_all = "camelCase")]
229pub struct ListWalletsParams {
230    /// Filter by on-chain address.
231    pub address: Option<String>,
232    /// Filter by blockchain.
233    pub blockchain: Option<Blockchain>,
234    /// Filter by SCA core version.
235    pub sca_core: Option<ScaCore>,
236    /// Filter by wallet set ID.
237    pub wallet_set_id: Option<String>,
238    /// Filter by application reference ID.
239    pub ref_id: Option<String>,
240    /// Pagination cursors.
241    #[serde(flatten)]
242    pub page: PageParams,
243}
244
245/// Query parameters for `listWalletBalances`.
246#[derive(Debug, Clone, Default, Deserialize, Serialize)]
247#[serde(rename_all = "camelCase")]
248pub struct ListWalletBalancesParams {
249    /// If `true`, include all tokens even if balance is zero.
250    pub include_all: Option<bool>,
251    /// Filter by token name.
252    pub name: Option<String>,
253    /// Filter by token contract address.
254    pub token_address: Option<String>,
255    /// Filter by token standard.
256    pub standard: Option<TokenStandard>,
257    /// Pagination cursors.
258    #[serde(flatten)]
259    pub page: PageParams,
260}
261
262/// Query parameters for `listWalletNfts`.
263#[derive(Debug, Clone, Default, Deserialize, Serialize)]
264#[serde(rename_all = "camelCase")]
265pub struct ListWalletNftsParams {
266    /// If `true`, include all NFTs even if amount is zero.
267    pub include_all: Option<bool>,
268    /// Filter by NFT name.
269    pub name: Option<String>,
270    /// Filter by token contract address.
271    pub token_address: Option<String>,
272    /// Filter by token standard.
273    pub standard: Option<TokenStandard>,
274    /// Pagination cursors.
275    #[serde(flatten)]
276    pub page: PageParams,
277}
278
279/// Request body for `createEndUserWallet`.
280#[derive(Debug, Clone, Deserialize, Serialize)]
281#[serde(rename_all = "camelCase")]
282pub struct CreateEndUserWalletRequest {
283    /// Client-generated idempotency key (UUID).
284    pub idempotency_key: String,
285    /// Blockchains on which to create wallets.
286    pub blockchains: Vec<Blockchain>,
287    /// Account type for the new wallet.
288    pub account_type: Option<AccountType>,
289    /// Optional per-wallet metadata.
290    pub metadata: Option<Vec<WalletMetadata>>,
291}
292
293// ── Tests ─────────────────────────────────────────────────────────────────────
294
295#[cfg(test)]
296mod tests {
297    use super::*;
298
299    #[test]
300    fn wallet_state_screaming() -> Result<(), Box<dyn std::error::Error>> {
301        assert_eq!(serde_json::to_string(&WalletState::Live)?, "\"LIVE\"");
302        assert_eq!(serde_json::to_string(&WalletState::Frozen)?, "\"FROZEN\"");
303        Ok(())
304    }
305
306    #[test]
307    fn update_wallet_request_camel_case() -> Result<(), Box<dyn std::error::Error>> {
308        let req = UpdateWalletRequest { name: Some("myWallet".to_string()), ref_id: None };
309        let s = serde_json::to_string(&req)?;
310        assert!(s.contains("\"name\""), "expected name in {s}");
311        Ok(())
312    }
313
314    #[test]
315    fn create_wallet_request_camel_case() -> Result<(), Box<dyn std::error::Error>> {
316        let req = CreateEndUserWalletRequest {
317            idempotency_key: "abc-def".to_string(),
318            blockchains: vec![Blockchain::Eth, Blockchain::EthSepolia],
319            account_type: None,
320            metadata: None,
321        };
322        let s = serde_json::to_string(&req)?;
323        assert!(s.contains("idempotencyKey"), "expected idempotencyKey in {s}");
324        assert!(s.contains("ETH-SEPOLIA"), "expected ETH-SEPOLIA in {s}");
325        Ok(())
326    }
327}