1use super::common::{Blockchain, CustodyType, FeeLevel, TransactionFee};
7
8#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
10#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
11pub enum TransactionState {
12 Cancelled,
14 Confirmed,
16 Complete,
18 Denied,
20 Failed,
22 Initiated,
24 Cleared,
26 Queued,
28 Sent,
30 Stuck,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
36#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
37pub enum TransactionType {
38 Inbound,
40 Outbound,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
46#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
47pub enum Operation {
48 Transfer,
50 ContractExecution,
52 ContractDeployment,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
58#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
59pub enum RiskScore {
60 Unknown,
62 Low,
64 Medium,
66 High,
68 Severe,
70 Blocklist,
72}
73
74#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
76#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
77pub enum RiskCategory {
78 Sanctions,
80 Csam,
82 IllicitBehavior,
84 Gambling,
86 TerroristFinancing,
88 Unsupported,
90 Frozen,
92 Other,
94 HighRiskIndustry,
96 Pep,
98 Trusted,
100 Hacking,
102 HumanTrafficking,
104 SpecialMeasures,
106}
107
108#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
110#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
111pub enum RiskType {
112 Ownership,
114 Counterparty,
116 Indirect,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
122#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
123pub enum RiskAction {
124 Approve,
126 Review,
128 FreezeWallet,
130 Deny,
132}
133
134#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
136#[serde(rename_all = "camelCase")]
137pub struct RiskSignal {
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub source: Option<String>,
141 #[serde(skip_serializing_if = "Option::is_none")]
143 pub source_value: Option<String>,
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub risk_score: Option<RiskScore>,
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub risk_categories: Option<Vec<RiskCategory>>,
150 #[serde(rename = "type")]
152 #[serde(skip_serializing_if = "Option::is_none")]
153 pub risk_type: Option<RiskType>,
154}
155
156#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct TransactionScreeningDecision {
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub screening_date: Option<String>,
163 #[serde(skip_serializing_if = "Option::is_none")]
165 pub rule_name: Option<String>,
166 #[serde(skip_serializing_if = "Option::is_none")]
168 pub actions: Option<Vec<RiskAction>>,
169 #[serde(skip_serializing_if = "Option::is_none")]
171 pub reasons: Option<Vec<RiskSignal>>,
172}
173
174#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct Transaction {
178 pub id: String,
180 pub state: TransactionState,
182 #[serde(skip_serializing_if = "Option::is_none")]
184 pub blockchain: Option<Blockchain>,
185 #[serde(skip_serializing_if = "Option::is_none")]
187 pub transaction_type: Option<TransactionType>,
188 pub create_date: String,
190 pub update_date: String,
192 #[serde(skip_serializing_if = "Option::is_none")]
194 pub abi_function_signature: Option<String>,
195 #[serde(skip_serializing_if = "Option::is_none")]
197 pub abi_parameters: Option<Vec<serde_json::Value>>,
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub amounts: Option<Vec<String>>,
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub amount_in_usd: Option<String>,
204 #[serde(skip_serializing_if = "Option::is_none")]
206 pub block_hash: Option<String>,
207 #[serde(skip_serializing_if = "Option::is_none")]
209 pub block_height: Option<u64>,
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub contract_address: Option<String>,
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub custody_type: Option<CustodyType>,
216 #[serde(skip_serializing_if = "Option::is_none")]
218 pub destination_address: Option<String>,
219 #[serde(skip_serializing_if = "Option::is_none")]
221 pub error_reason: Option<String>,
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub error_details: Option<String>,
225 #[serde(skip_serializing_if = "Option::is_none")]
227 pub estimated_fee: Option<TransactionFee>,
228 #[serde(skip_serializing_if = "Option::is_none")]
230 pub fee_level: Option<FeeLevel>,
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub first_confirm_date: Option<String>,
234 #[serde(skip_serializing_if = "Option::is_none")]
236 pub network_fee: Option<String>,
237 #[serde(skip_serializing_if = "Option::is_none")]
239 pub network_fee_in_usd: Option<String>,
240 #[serde(skip_serializing_if = "Option::is_none")]
242 pub nfts: Option<Vec<String>>,
243 #[serde(skip_serializing_if = "Option::is_none")]
245 pub operation: Option<Operation>,
246 #[serde(skip_serializing_if = "Option::is_none")]
248 pub ref_id: Option<String>,
249 #[serde(skip_serializing_if = "Option::is_none")]
251 pub source_address: Option<String>,
252 #[serde(skip_serializing_if = "Option::is_none")]
254 pub token_id: Option<String>,
255 #[serde(skip_serializing_if = "Option::is_none")]
257 pub tx_hash: Option<String>,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub user_id: Option<String>,
261 #[serde(skip_serializing_if = "Option::is_none")]
263 pub wallet_id: Option<String>,
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub transaction_screening_evaluation: Option<TransactionScreeningDecision>,
267}
268
269#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
271#[serde(rename_all = "camelCase")]
272pub struct TransactionsData {
273 pub transactions: Vec<Transaction>,
275}
276
277#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
279pub struct Transactions {
280 pub data: TransactionsData,
282}
283
284#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
286#[serde(rename_all = "camelCase")]
287pub struct TransactionData {
288 pub transaction: Transaction,
290}
291
292#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
294pub struct TransactionResponse {
295 pub data: TransactionData,
297}
298
299#[derive(Debug, Default, Clone, serde::Serialize)]
301#[serde(rename_all = "camelCase")]
302pub struct ListTransactionsParams {
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub blockchain: Option<Blockchain>,
306 #[serde(skip_serializing_if = "Option::is_none")]
308 pub custody_type: Option<CustodyType>,
309 #[serde(skip_serializing_if = "Option::is_none")]
311 pub destination_address: Option<String>,
312 #[serde(skip_serializing_if = "Option::is_none")]
314 pub include_all: Option<bool>,
315 #[serde(skip_serializing_if = "Option::is_none")]
317 pub operation: Option<Operation>,
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub ref_id: Option<String>,
321 #[serde(skip_serializing_if = "Option::is_none")]
323 pub source_address: Option<String>,
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub state: Option<TransactionState>,
327 #[serde(skip_serializing_if = "Option::is_none")]
329 pub token_address: Option<String>,
330 #[serde(skip_serializing_if = "Option::is_none")]
332 pub transaction_hash: Option<String>,
333 #[serde(skip_serializing_if = "Option::is_none")]
335 pub transaction_type: Option<TransactionType>,
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub wallet_ids: Option<String>,
339 #[serde(skip_serializing_if = "Option::is_none")]
341 pub from: Option<String>,
342 #[serde(skip_serializing_if = "Option::is_none")]
344 pub to: Option<String>,
345 #[serde(skip_serializing_if = "Option::is_none")]
347 pub page_before: Option<String>,
348 #[serde(skip_serializing_if = "Option::is_none")]
350 pub page_after: Option<String>,
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub page_size: Option<u32>,
354}
355
356#[derive(Debug, Clone, serde::Serialize)]
358#[serde(rename_all = "camelCase")]
359pub struct CreateTransferTxRequest {
360 pub idempotency_key: String,
362 pub entity_secret_ciphertext: String,
364 pub wallet_id: String,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub blockchain: Option<Blockchain>,
369 #[serde(skip_serializing_if = "Option::is_none")]
371 pub token_id: Option<String>,
372 pub destination_address: String,
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub amounts: Option<Vec<String>>,
377 #[serde(skip_serializing_if = "Option::is_none")]
379 pub nft_token_ids: Option<Vec<String>>,
380 #[serde(skip_serializing_if = "Option::is_none")]
382 pub ref_id: Option<String>,
383 #[serde(skip_serializing_if = "Option::is_none")]
385 pub fee_level: Option<FeeLevel>,
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub gas_limit: Option<String>,
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub gas_price: Option<String>,
392 #[serde(skip_serializing_if = "Option::is_none")]
394 pub max_fee: Option<String>,
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub priority_fee: Option<String>,
398}
399
400#[derive(Debug, Clone, serde::Serialize)]
402#[serde(rename_all = "camelCase")]
403pub struct CreateContractExecutionTxRequest {
404 pub idempotency_key: String,
406 pub entity_secret_ciphertext: String,
408 pub wallet_id: String,
410 #[serde(skip_serializing_if = "Option::is_none")]
412 pub blockchain: Option<Blockchain>,
413 pub contract_address: String,
415 #[serde(skip_serializing_if = "Option::is_none")]
417 pub abi_function_signature: Option<String>,
418 #[serde(skip_serializing_if = "Option::is_none")]
420 pub abi_parameters: Option<Vec<serde_json::Value>>,
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub call_data: Option<String>,
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub fee_level: Option<FeeLevel>,
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub gas_limit: Option<String>,
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub max_fee: Option<String>,
433 #[serde(skip_serializing_if = "Option::is_none")]
435 pub priority_fee: Option<String>,
436 #[serde(skip_serializing_if = "Option::is_none")]
438 pub ref_id: Option<String>,
439 #[serde(skip_serializing_if = "Option::is_none")]
441 pub amount: Option<String>,
442}
443
444#[derive(Debug, Clone, serde::Serialize)]
446#[serde(rename_all = "camelCase")]
447pub struct CancelTxRequest {
448 pub entity_secret_ciphertext: String,
450}
451
452#[derive(Debug, Clone, serde::Serialize)]
454#[serde(rename_all = "camelCase")]
455pub struct AccelerateTxRequest {
456 pub entity_secret_ciphertext: String,
458}
459
460#[derive(Debug, Clone, serde::Serialize)]
462#[serde(rename_all = "camelCase")]
463pub struct ValidateAddressRequest {
464 pub blockchain: Blockchain,
466 pub address: String,
468}
469
470#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
472#[serde(rename_all = "camelCase")]
473pub struct ValidateAddressData {
474 pub is_valid: bool,
476}
477
478#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
480pub struct ValidateAddressResponse {
481 pub data: ValidateAddressData,
483}
484
485#[derive(Debug, Clone, Default, serde::Serialize)]
487#[serde(rename_all = "camelCase")]
488pub struct EstimateTransferFeeRequest {
489 #[serde(skip_serializing_if = "Option::is_none")]
491 pub source_address: Option<String>,
492 #[serde(skip_serializing_if = "Option::is_none")]
494 pub blockchain: Option<Blockchain>,
495 #[serde(skip_serializing_if = "Option::is_none")]
497 pub destination_address: Option<String>,
498 #[serde(skip_serializing_if = "Option::is_none")]
500 pub amounts: Option<Vec<String>>,
501 #[serde(skip_serializing_if = "Option::is_none")]
503 pub nfts: Option<Vec<String>>,
504 #[serde(skip_serializing_if = "Option::is_none")]
506 pub token_id: Option<String>,
507 #[serde(skip_serializing_if = "Option::is_none")]
509 pub fee_level: Option<FeeLevel>,
510 #[serde(skip_serializing_if = "Option::is_none")]
512 pub gas_limit: Option<String>,
513 #[serde(skip_serializing_if = "Option::is_none")]
515 pub gas_price: Option<String>,
516}
517
518#[derive(Debug, Clone, serde::Deserialize)]
520#[serde(rename_all = "camelCase")]
521pub struct EstimateFeeData {
522 #[serde(skip_serializing_if = "Option::is_none")]
524 pub low: Option<TransactionFee>,
525 #[serde(skip_serializing_if = "Option::is_none")]
527 pub medium: Option<TransactionFee>,
528 #[serde(skip_serializing_if = "Option::is_none")]
530 pub high: Option<TransactionFee>,
531 #[serde(skip_serializing_if = "Option::is_none")]
533 pub call_gas_limit: Option<String>,
534 #[serde(skip_serializing_if = "Option::is_none")]
536 pub verification_gas_limit: Option<String>,
537 #[serde(skip_serializing_if = "Option::is_none")]
539 pub pre_verification_gas: Option<String>,
540}
541
542#[derive(Debug, Clone, serde::Deserialize)]
544pub struct EstimateFeeResponse {
545 pub data: EstimateFeeData,
547}
548
549#[cfg(test)]
550mod tests {
551 use super::*;
552
553 #[test]
554 fn transaction_response_deserializes() -> Result<(), Box<dyn std::error::Error>> {
555 let json = r#"{
556 "data": {
557 "transaction": {
558 "id": "tx-id-1",
559 "state": "COMPLETE",
560 "blockchain": "ETH",
561 "createDate": "2024-01-01T00:00:00Z",
562 "updateDate": "2024-01-02T00:00:00Z",
563 "txHash": "0xdeadbeef",
564 "operation": "TRANSFER"
565 }
566 }
567 }"#;
568 let resp: TransactionResponse = serde_json::from_str(json)?;
569 assert_eq!(resp.data.transaction.id, "tx-id-1");
570 assert_eq!(resp.data.transaction.state, TransactionState::Complete);
571 assert_eq!(resp.data.transaction.tx_hash.as_deref(), Some("0xdeadbeef"));
572 Ok(())
573 }
574
575 #[test]
576 fn transactions_list_deserializes() -> Result<(), Box<dyn std::error::Error>> {
577 let json = r#"{
578 "data": {
579 "transactions": [
580 {
581 "id": "tx-1",
582 "state": "SENT",
583 "createDate": "2024-01-01T00:00:00Z",
584 "updateDate": "2024-01-01T00:00:00Z"
585 }
586 ]
587 }
588 }"#;
589 let resp: Transactions = serde_json::from_str(json)?;
590 assert_eq!(resp.data.transactions.len(), 1);
591 assert_eq!(resp.data.transactions[0].state, TransactionState::Sent);
592 Ok(())
593 }
594
595 #[test]
596 fn validate_address_response_deserializes() -> Result<(), Box<dyn std::error::Error>> {
597 let json = r#"{"data": {"isValid": true}}"#;
598 let resp: ValidateAddressResponse = serde_json::from_str(json)?;
599 assert!(resp.data.is_valid);
600 Ok(())
601 }
602
603 #[test]
604 fn create_transfer_request_serializes() -> Result<(), Box<dyn std::error::Error>> {
605 let req = CreateTransferTxRequest {
606 idempotency_key: "key".to_string(),
607 entity_secret_ciphertext: "cipher".to_string(),
608 wallet_id: "wallet-1".to_string(),
609 blockchain: None,
610 token_id: Some("token-1".to_string()),
611 destination_address: "0xdest".to_string(),
612 amounts: Some(vec!["1.0".to_string()]),
613 nft_token_ids: None,
614 ref_id: None,
615 fee_level: Some(FeeLevel::Medium),
616 gas_limit: None,
617 gas_price: None,
618 max_fee: None,
619 priority_fee: None,
620 };
621 let json = serde_json::to_string(&req)?;
622 assert!(json.contains("walletId"));
623 assert!(json.contains("destinationAddress"));
624 assert!(json.contains("MEDIUM"));
625 Ok(())
626 }
627
628 #[test]
629 fn transaction_state_all_variants_deserialize() -> Result<(), Box<dyn std::error::Error>> {
630 for (s, expected) in [
631 ("\"CANCELLED\"", TransactionState::Cancelled),
632 ("\"CONFIRMED\"", TransactionState::Confirmed),
633 ("\"COMPLETE\"", TransactionState::Complete),
634 ("\"INITIATED\"", TransactionState::Initiated),
635 ("\"STUCK\"", TransactionState::Stuck),
636 ] {
637 let state: TransactionState = serde_json::from_str(s)?;
638 assert_eq!(state, expected);
639 }
640 Ok(())
641 }
642}