use super::common::{Blockchain, CustodyType, FeeLevel, TransactionFee};
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TransactionState {
Cancelled,
Confirmed,
Complete,
Denied,
Failed,
Initiated,
Cleared,
Queued,
Sent,
Stuck,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum TransactionType {
Inbound,
Outbound,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum Operation {
Transfer,
ContractExecution,
ContractDeployment,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RiskScore {
Unknown,
Low,
Medium,
High,
Severe,
Blocklist,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RiskCategory {
Sanctions,
Csam,
IllicitBehavior,
Gambling,
TerroristFinancing,
Unsupported,
Frozen,
Other,
HighRiskIndustry,
Pep,
Trusted,
Hacking,
HumanTrafficking,
SpecialMeasures,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RiskType {
Ownership,
Counterparty,
Indirect,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum RiskAction {
Approve,
Review,
FreezeWallet,
Deny,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RiskSignal {
#[serde(skip_serializing_if = "Option::is_none")]
pub source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_value: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub risk_score: Option<RiskScore>,
#[serde(skip_serializing_if = "Option::is_none")]
pub risk_categories: Option<Vec<RiskCategory>>,
#[serde(rename = "type")]
#[serde(skip_serializing_if = "Option::is_none")]
pub risk_type: Option<RiskType>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionScreeningDecision {
#[serde(skip_serializing_if = "Option::is_none")]
pub screening_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rule_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub actions: Option<Vec<RiskAction>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reasons: Option<Vec<RiskSignal>>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Transaction {
pub id: String,
pub state: TransactionState,
#[serde(skip_serializing_if = "Option::is_none")]
pub blockchain: Option<Blockchain>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<TransactionType>,
pub create_date: String,
pub update_date: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub abi_function_signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub abi_parameters: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amounts: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount_in_usd: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub block_height: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub contract_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custody_type: Option<CustodyType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_reason: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error_details: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub estimated_fee: Option<TransactionFee>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_level: Option<FeeLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub first_confirm_date: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network_fee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub network_fee_in_usd: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nfts: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ref_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tx_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wallet_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_screening_evaluation: Option<TransactionScreeningDecision>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionsData {
pub transactions: Vec<Transaction>,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct Transactions {
pub data: TransactionsData,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TransactionData {
pub transaction: Transaction,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TransactionResponse {
pub data: TransactionData,
}
#[derive(Debug, Default, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ListTransactionsParams {
#[serde(skip_serializing_if = "Option::is_none")]
pub blockchain: Option<Blockchain>,
#[serde(skip_serializing_if = "Option::is_none")]
pub custody_type: Option<CustodyType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub include_all: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub operation: Option<Operation>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ref_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub state: Option<TransactionState>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<TransactionType>,
#[serde(skip_serializing_if = "Option::is_none")]
pub wallet_ids: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_before: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_after: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_size: Option<u32>,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateTransferTxRequest {
pub idempotency_key: String,
pub entity_secret_ciphertext: String,
pub wallet_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub blockchain: Option<Blockchain>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_id: Option<String>,
pub destination_address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub amounts: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nft_token_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ref_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_level: Option<FeeLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_fee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority_fee: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateContractExecutionTxRequest {
pub idempotency_key: String,
pub entity_secret_ciphertext: String,
pub wallet_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub blockchain: Option<Blockchain>,
pub contract_address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub abi_function_signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub abi_parameters: Option<Vec<serde_json::Value>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub call_data: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_level: Option<FeeLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_fee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub priority_fee: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ref_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amount: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct CancelTxRequest {
pub entity_secret_ciphertext: String,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AccelerateTxRequest {
pub entity_secret_ciphertext: String,
}
#[derive(Debug, Clone, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct ValidateAddressRequest {
pub blockchain: Blockchain,
pub address: String,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ValidateAddressData {
pub is_valid: bool,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ValidateAddressResponse {
pub data: ValidateAddressData,
}
#[derive(Debug, Clone, Default, serde::Serialize)]
#[serde(rename_all = "camelCase")]
pub struct EstimateTransferFeeRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub source_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub blockchain: Option<Blockchain>,
#[serde(skip_serializing_if = "Option::is_none")]
pub destination_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub amounts: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub nfts: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub fee_level: Option<FeeLevel>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_limit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub gas_price: Option<String>,
}
#[derive(Debug, Clone, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct EstimateFeeData {
#[serde(skip_serializing_if = "Option::is_none")]
pub low: Option<TransactionFee>,
#[serde(skip_serializing_if = "Option::is_none")]
pub medium: Option<TransactionFee>,
#[serde(skip_serializing_if = "Option::is_none")]
pub high: Option<TransactionFee>,
#[serde(skip_serializing_if = "Option::is_none")]
pub call_gas_limit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub verification_gas_limit: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_verification_gas: Option<String>,
}
#[derive(Debug, Clone, serde::Deserialize)]
pub struct EstimateFeeResponse {
pub data: EstimateFeeData,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn transaction_response_deserializes() -> Result<(), Box<dyn std::error::Error>> {
let json = r#"{
"data": {
"transaction": {
"id": "tx-id-1",
"state": "COMPLETE",
"blockchain": "ETH",
"createDate": "2024-01-01T00:00:00Z",
"updateDate": "2024-01-02T00:00:00Z",
"txHash": "0xdeadbeef",
"operation": "TRANSFER"
}
}
}"#;
let resp: TransactionResponse = serde_json::from_str(json)?;
assert_eq!(resp.data.transaction.id, "tx-id-1");
assert_eq!(resp.data.transaction.state, TransactionState::Complete);
assert_eq!(resp.data.transaction.tx_hash.as_deref(), Some("0xdeadbeef"));
Ok(())
}
#[test]
fn transactions_list_deserializes() -> Result<(), Box<dyn std::error::Error>> {
let json = r#"{
"data": {
"transactions": [
{
"id": "tx-1",
"state": "SENT",
"createDate": "2024-01-01T00:00:00Z",
"updateDate": "2024-01-01T00:00:00Z"
}
]
}
}"#;
let resp: Transactions = serde_json::from_str(json)?;
assert_eq!(resp.data.transactions.len(), 1);
assert_eq!(resp.data.transactions[0].state, TransactionState::Sent);
Ok(())
}
#[test]
fn validate_address_response_deserializes() -> Result<(), Box<dyn std::error::Error>> {
let json = r#"{"data": {"isValid": true}}"#;
let resp: ValidateAddressResponse = serde_json::from_str(json)?;
assert!(resp.data.is_valid);
Ok(())
}
#[test]
fn create_transfer_request_serializes() -> Result<(), Box<dyn std::error::Error>> {
let req = CreateTransferTxRequest {
idempotency_key: "key".to_string(),
entity_secret_ciphertext: "cipher".to_string(),
wallet_id: "wallet-1".to_string(),
blockchain: None,
token_id: Some("token-1".to_string()),
destination_address: "0xdest".to_string(),
amounts: Some(vec!["1.0".to_string()]),
nft_token_ids: None,
ref_id: None,
fee_level: Some(FeeLevel::Medium),
gas_limit: None,
gas_price: None,
max_fee: None,
priority_fee: None,
};
let json = serde_json::to_string(&req)?;
assert!(json.contains("walletId"));
assert!(json.contains("destinationAddress"));
assert!(json.contains("MEDIUM"));
Ok(())
}
#[test]
fn transaction_state_all_variants_deserialize() -> Result<(), Box<dyn std::error::Error>> {
for (s, expected) in [
("\"CANCELLED\"", TransactionState::Cancelled),
("\"CONFIRMED\"", TransactionState::Confirmed),
("\"COMPLETE\"", TransactionState::Complete),
("\"INITIATED\"", TransactionState::Initiated),
("\"STUCK\"", TransactionState::Stuck),
] {
let state: TransactionState = serde_json::from_str(s)?;
assert_eq!(state, expected);
}
Ok(())
}
}