use std::collections::HashMap;
use std::str::FromStr;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use super::amount::{Amount, CurrencyUnit};
use super::keys::PublicKey;
use super::mint::MintUrl;
use super::proof::Proofs;
use super::quote::PaymentMethod;
use crate::error::FfiError;
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct Transaction {
pub id: TransactionId,
pub mint_url: MintUrl,
pub direction: TransactionDirection,
pub amount: Amount,
pub fee: Amount,
pub unit: CurrencyUnit,
pub ys: Vec<PublicKey>,
pub timestamp: u64,
pub memo: Option<String>,
pub metadata: HashMap<String, String>,
pub quote_id: Option<String>,
pub payment_request: Option<String>,
pub payment_proof: Option<String>,
pub payment_method: Option<PaymentMethod>,
pub saga_id: Option<String>,
}
impl From<cdk::wallet::types::Transaction> for Transaction {
fn from(tx: cdk::wallet::types::Transaction) -> Self {
Self {
id: tx.id().into(),
mint_url: tx.mint_url.into(),
direction: tx.direction.into(),
amount: tx.amount.into(),
fee: tx.fee.into(),
unit: tx.unit.into(),
ys: tx.ys.into_iter().map(Into::into).collect(),
timestamp: tx.timestamp,
memo: tx.memo,
metadata: tx.metadata,
quote_id: tx.quote_id,
payment_request: tx.payment_request,
payment_proof: tx.payment_proof,
payment_method: tx.payment_method.map(Into::into),
saga_id: tx.saga_id.map(|id| id.to_string()),
}
}
}
impl TryFrom<Transaction> for cdk::wallet::types::Transaction {
type Error = FfiError;
fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, _> =
tx.ys.into_iter().map(|pk| pk.try_into()).collect();
let cdk_ys = cdk_ys?;
Ok(Self {
mint_url: tx.mint_url.try_into()?,
direction: tx.direction.into(),
amount: tx.amount.into(),
fee: tx.fee.into(),
unit: tx.unit.into(),
ys: cdk_ys,
timestamp: tx.timestamp,
memo: tx.memo,
metadata: tx.metadata,
quote_id: tx.quote_id,
payment_request: tx.payment_request,
payment_proof: tx.payment_proof,
payment_method: tx.payment_method.map(Into::into),
saga_id: tx
.saga_id
.map(|id| Uuid::from_str(&id))
.transpose()
.map_err(|e| FfiError::internal(format!("Invalid saga_id: {}", e)))?,
})
}
}
impl Transaction {
pub fn to_json(&self) -> Result<String, FfiError> {
Ok(serde_json::to_string(self)?)
}
}
#[uniffi::export]
pub fn decode_transaction(json: String) -> Result<Transaction, FfiError> {
Ok(serde_json::from_str(&json)?)
}
#[uniffi::export]
pub fn encode_transaction(transaction: Transaction) -> Result<String, FfiError> {
Ok(serde_json::to_string(&transaction)?)
}
#[uniffi::export]
pub fn transaction_matches_conditions(
transaction: &Transaction,
mint_url: Option<MintUrl>,
direction: Option<TransactionDirection>,
unit: Option<CurrencyUnit>,
) -> Result<bool, FfiError> {
let cdk_transaction: cdk::wallet::types::Transaction = transaction.clone().try_into()?;
let cdk_mint_url = mint_url.map(|url| url.try_into()).transpose()?;
let cdk_direction = direction.map(Into::into);
let cdk_unit = unit.map(Into::into);
Ok(cdk_transaction.matches_conditions(&cdk_mint_url, &cdk_direction, &cdk_unit))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
pub enum TransactionDirection {
Incoming,
Outgoing,
}
impl From<cdk::wallet::types::TransactionDirection> for TransactionDirection {
fn from(direction: cdk::wallet::types::TransactionDirection) -> Self {
match direction {
cdk::wallet::types::TransactionDirection::Incoming => TransactionDirection::Incoming,
cdk::wallet::types::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
}
}
}
impl From<TransactionDirection> for cdk::wallet::types::TransactionDirection {
fn from(direction: TransactionDirection) -> Self {
match direction {
TransactionDirection::Incoming => cdk::wallet::types::TransactionDirection::Incoming,
TransactionDirection::Outgoing => cdk::wallet::types::TransactionDirection::Outgoing,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
#[serde(transparent)]
pub struct TransactionId {
pub hex: String,
}
impl TransactionId {
pub fn from_hex(hex: String) -> Result<Self, FfiError> {
if hex.len() != 64 {
return Err(FfiError::internal(
"Transaction ID hex must be exactly 64 characters (32 bytes)",
));
}
if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(FfiError::internal(
"Transaction ID hex contains invalid characters",
));
}
Ok(Self { hex })
}
pub fn from_proofs(proofs: &Proofs) -> Result<Self, FfiError> {
let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
proofs.iter().map(|p| p.clone().try_into()).collect();
let cdk_proofs = cdk_proofs?;
let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?;
Ok(Self {
hex: id.to_string(),
})
}
}
impl From<cdk::wallet::types::TransactionId> for TransactionId {
fn from(id: cdk::wallet::types::TransactionId) -> Self {
Self {
hex: id.to_string(),
}
}
}
impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
type Error = FfiError;
fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
cdk::wallet::types::TransactionId::from_hex(&id.hex)
.map_err(|e| FfiError::internal(format!("Invalid transaction ID: {}", e)))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
pub struct AuthProof {
pub keyset_id: String,
pub secret: String,
pub c: String,
pub y: String,
}
impl From<cdk::nuts::AuthProof> for AuthProof {
fn from(auth_proof: cdk::nuts::AuthProof) -> Self {
Self {
keyset_id: auth_proof.keyset_id.to_string(),
secret: auth_proof.secret.to_string(),
c: auth_proof.c.to_string(),
y: auth_proof
.y()
.map(|y| y.to_string())
.unwrap_or_else(|_| "".to_string()),
}
}
}
impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
type Error = FfiError;
fn try_from(auth_proof: AuthProof) -> Result<Self, Self::Error> {
use std::str::FromStr;
Ok(Self {
keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
.map_err(|e| FfiError::internal(format!("Invalid keyset ID: {}", e)))?,
secret: {
use std::str::FromStr;
cdk::secret::Secret::from_str(&auth_proof.secret)
.map_err(|e| FfiError::internal(format!("Invalid secret: {}", e)))?
},
c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
.map_err(|e| FfiError::internal(format!("Invalid public key: {}", e)))?,
dleq: None, })
}
}
impl AuthProof {
pub fn to_json(&self) -> Result<String, FfiError> {
Ok(serde_json::to_string(self)?)
}
}
#[uniffi::export]
pub fn decode_auth_proof(json: String) -> Result<AuthProof, FfiError> {
Ok(serde_json::from_str(&json)?)
}
#[uniffi::export]
pub fn encode_auth_proof(proof: AuthProof) -> Result<String, FfiError> {
Ok(serde_json::to_string(&proof)?)
}