1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use super::amount::{Amount, CurrencyUnit};
8use super::keys::PublicKey;
9use super::mint::MintUrl;
10use super::proof::Proofs;
11use crate::error::FfiError;
12
13#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
15pub struct Transaction {
16 pub id: TransactionId,
18 pub mint_url: MintUrl,
20 pub direction: TransactionDirection,
22 pub amount: Amount,
24 pub fee: Amount,
26 pub unit: CurrencyUnit,
28 pub ys: Vec<PublicKey>,
30 pub timestamp: u64,
32 pub memo: Option<String>,
34 pub metadata: HashMap<String, String>,
36 pub quote_id: Option<String>,
38 pub payment_request: Option<String>,
40 pub payment_proof: Option<String>,
42}
43
44impl From<cdk::wallet::types::Transaction> for Transaction {
45 fn from(tx: cdk::wallet::types::Transaction) -> Self {
46 Self {
47 id: tx.id().into(),
48 mint_url: tx.mint_url.into(),
49 direction: tx.direction.into(),
50 amount: tx.amount.into(),
51 fee: tx.fee.into(),
52 unit: tx.unit.into(),
53 ys: tx.ys.into_iter().map(Into::into).collect(),
54 timestamp: tx.timestamp,
55 memo: tx.memo,
56 metadata: tx.metadata,
57 quote_id: tx.quote_id,
58 payment_request: tx.payment_request,
59 payment_proof: tx.payment_proof,
60 }
61 }
62}
63
64impl TryFrom<Transaction> for cdk::wallet::types::Transaction {
66 type Error = FfiError;
67
68 fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
69 let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, _> =
70 tx.ys.into_iter().map(|pk| pk.try_into()).collect();
71 let cdk_ys = cdk_ys?;
72
73 Ok(Self {
74 mint_url: tx.mint_url.try_into()?,
75 direction: tx.direction.into(),
76 amount: tx.amount.into(),
77 fee: tx.fee.into(),
78 unit: tx.unit.into(),
79 ys: cdk_ys,
80 timestamp: tx.timestamp,
81 memo: tx.memo,
82 metadata: tx.metadata,
83 quote_id: tx.quote_id,
84 payment_request: tx.payment_request,
85 payment_proof: tx.payment_proof,
86 })
87 }
88}
89
90impl Transaction {
91 pub fn to_json(&self) -> Result<String, FfiError> {
93 Ok(serde_json::to_string(self)?)
94 }
95}
96
97#[uniffi::export]
99pub fn decode_transaction(json: String) -> Result<Transaction, FfiError> {
100 Ok(serde_json::from_str(&json)?)
101}
102
103#[uniffi::export]
105pub fn encode_transaction(transaction: Transaction) -> Result<String, FfiError> {
106 Ok(serde_json::to_string(&transaction)?)
107}
108
109#[uniffi::export]
111pub fn transaction_matches_conditions(
112 transaction: &Transaction,
113 mint_url: Option<MintUrl>,
114 direction: Option<TransactionDirection>,
115 unit: Option<CurrencyUnit>,
116) -> Result<bool, FfiError> {
117 let cdk_transaction: cdk::wallet::types::Transaction = transaction.clone().try_into()?;
118 let cdk_mint_url = mint_url.map(|url| url.try_into()).transpose()?;
119 let cdk_direction = direction.map(Into::into);
120 let cdk_unit = unit.map(Into::into);
121 Ok(cdk_transaction.matches_conditions(&cdk_mint_url, &cdk_direction, &cdk_unit))
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
126pub enum TransactionDirection {
127 Incoming,
129 Outgoing,
131}
132
133impl From<cdk::wallet::types::TransactionDirection> for TransactionDirection {
134 fn from(direction: cdk::wallet::types::TransactionDirection) -> Self {
135 match direction {
136 cdk::wallet::types::TransactionDirection::Incoming => TransactionDirection::Incoming,
137 cdk::wallet::types::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
138 }
139 }
140}
141
142impl From<TransactionDirection> for cdk::wallet::types::TransactionDirection {
143 fn from(direction: TransactionDirection) -> Self {
144 match direction {
145 TransactionDirection::Incoming => cdk::wallet::types::TransactionDirection::Incoming,
146 TransactionDirection::Outgoing => cdk::wallet::types::TransactionDirection::Outgoing,
147 }
148 }
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
153#[serde(transparent)]
154pub struct TransactionId {
155 pub hex: String,
157}
158
159impl TransactionId {
160 pub fn from_hex(hex: String) -> Result<Self, FfiError> {
162 if hex.len() != 64 {
164 return Err(FfiError::InvalidHex {
165 msg: "Transaction ID hex must be exactly 64 characters (32 bytes)".to_string(),
166 });
167 }
168
169 if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
171 return Err(FfiError::InvalidHex {
172 msg: "Transaction ID hex contains invalid characters".to_string(),
173 });
174 }
175
176 Ok(Self { hex })
177 }
178
179 pub fn from_proofs(proofs: &Proofs) -> Result<Self, FfiError> {
181 let cdk_proofs: Result<Vec<cdk::nuts::Proof>, _> =
182 proofs.iter().map(|p| p.clone().try_into()).collect();
183 let cdk_proofs = cdk_proofs?;
184 let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?;
185 Ok(Self {
186 hex: id.to_string(),
187 })
188 }
189}
190
191impl From<cdk::wallet::types::TransactionId> for TransactionId {
192 fn from(id: cdk::wallet::types::TransactionId) -> Self {
193 Self {
194 hex: id.to_string(),
195 }
196 }
197}
198
199impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
200 type Error = FfiError;
201
202 fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
203 cdk::wallet::types::TransactionId::from_hex(&id.hex)
204 .map_err(|e| FfiError::InvalidHex { msg: e.to_string() })
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
210pub struct AuthProof {
211 pub keyset_id: String,
213 pub secret: String,
215 pub c: String,
217 pub y: String,
219}
220
221impl From<cdk::nuts::AuthProof> for AuthProof {
222 fn from(auth_proof: cdk::nuts::AuthProof) -> Self {
223 Self {
224 keyset_id: auth_proof.keyset_id.to_string(),
225 secret: auth_proof.secret.to_string(),
226 c: auth_proof.c.to_string(),
227 y: auth_proof
228 .y()
229 .map(|y| y.to_string())
230 .unwrap_or_else(|_| "".to_string()),
231 }
232 }
233}
234
235impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
236 type Error = FfiError;
237
238 fn try_from(auth_proof: AuthProof) -> Result<Self, Self::Error> {
239 use std::str::FromStr;
240 Ok(Self {
241 keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
242 .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
243 secret: {
244 use std::str::FromStr;
245 cdk::secret::Secret::from_str(&auth_proof.secret)
246 .map_err(|e| FfiError::Serialization { msg: e.to_string() })?
247 },
248 c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
249 .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
250 dleq: None, })
252 }
253}
254
255impl AuthProof {
256 pub fn to_json(&self) -> Result<String, FfiError> {
258 Ok(serde_json::to_string(self)?)
259 }
260}
261
262#[uniffi::export]
264pub fn decode_auth_proof(json: String) -> Result<AuthProof, FfiError> {
265 Ok(serde_json::from_str(&json)?)
266}
267
268#[uniffi::export]
270pub fn encode_auth_proof(proof: AuthProof) -> Result<String, FfiError> {
271 Ok(serde_json::to_string(&proof)?)
272}