types.rs 88 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974
  1. //! FFI-compatible types
  2. use std::collections::HashMap;
  3. use std::str::FromStr;
  4. use std::sync::Mutex;
  5. use cdk::nuts::{CurrencyUnit as CdkCurrencyUnit, State as CdkState};
  6. use cdk::pub_sub::SubId;
  7. use cdk::Amount as CdkAmount;
  8. use serde::{Deserialize, Serialize};
  9. use crate::error::FfiError;
  10. /// FFI-compatible Amount type
  11. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
  12. #[serde(transparent)]
  13. pub struct Amount {
  14. pub value: u64,
  15. }
  16. impl Amount {
  17. pub fn new(value: u64) -> Self {
  18. Self { value }
  19. }
  20. pub fn zero() -> Self {
  21. Self { value: 0 }
  22. }
  23. pub fn is_zero(&self) -> bool {
  24. self.value == 0
  25. }
  26. pub fn convert_unit(
  27. &self,
  28. current_unit: CurrencyUnit,
  29. target_unit: CurrencyUnit,
  30. ) -> Result<Amount, FfiError> {
  31. Ok(CdkAmount::from(self.value)
  32. .convert_unit(&current_unit.into(), &target_unit.into())
  33. .map(Into::into)?)
  34. }
  35. pub fn add(&self, other: Amount) -> Result<Amount, FfiError> {
  36. let self_amount = CdkAmount::from(self.value);
  37. let other_amount = CdkAmount::from(other.value);
  38. self_amount
  39. .checked_add(other_amount)
  40. .map(Into::into)
  41. .ok_or(FfiError::AmountOverflow)
  42. }
  43. pub fn subtract(&self, other: Amount) -> Result<Amount, FfiError> {
  44. let self_amount = CdkAmount::from(self.value);
  45. let other_amount = CdkAmount::from(other.value);
  46. self_amount
  47. .checked_sub(other_amount)
  48. .map(Into::into)
  49. .ok_or(FfiError::AmountOverflow)
  50. }
  51. pub fn multiply(&self, factor: u64) -> Result<Amount, FfiError> {
  52. let self_amount = CdkAmount::from(self.value);
  53. let factor_amount = CdkAmount::from(factor);
  54. self_amount
  55. .checked_mul(factor_amount)
  56. .map(Into::into)
  57. .ok_or(FfiError::AmountOverflow)
  58. }
  59. pub fn divide(&self, divisor: u64) -> Result<Amount, FfiError> {
  60. if divisor == 0 {
  61. return Err(FfiError::DivisionByZero);
  62. }
  63. let self_amount = CdkAmount::from(self.value);
  64. let divisor_amount = CdkAmount::from(divisor);
  65. self_amount
  66. .checked_div(divisor_amount)
  67. .map(Into::into)
  68. .ok_or(FfiError::AmountOverflow)
  69. }
  70. }
  71. impl From<CdkAmount> for Amount {
  72. fn from(amount: CdkAmount) -> Self {
  73. Self {
  74. value: u64::from(amount),
  75. }
  76. }
  77. }
  78. impl From<Amount> for CdkAmount {
  79. fn from(amount: Amount) -> Self {
  80. CdkAmount::from(amount.value)
  81. }
  82. }
  83. /// FFI-compatible Currency Unit
  84. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
  85. pub enum CurrencyUnit {
  86. Sat,
  87. Msat,
  88. Usd,
  89. Eur,
  90. Auth,
  91. Custom { unit: String },
  92. }
  93. impl From<CdkCurrencyUnit> for CurrencyUnit {
  94. fn from(unit: CdkCurrencyUnit) -> Self {
  95. match unit {
  96. CdkCurrencyUnit::Sat => CurrencyUnit::Sat,
  97. CdkCurrencyUnit::Msat => CurrencyUnit::Msat,
  98. CdkCurrencyUnit::Usd => CurrencyUnit::Usd,
  99. CdkCurrencyUnit::Eur => CurrencyUnit::Eur,
  100. CdkCurrencyUnit::Auth => CurrencyUnit::Auth,
  101. CdkCurrencyUnit::Custom(s) => CurrencyUnit::Custom { unit: s },
  102. _ => CurrencyUnit::Sat, // Default for unknown units
  103. }
  104. }
  105. }
  106. impl From<CurrencyUnit> for CdkCurrencyUnit {
  107. fn from(unit: CurrencyUnit) -> Self {
  108. match unit {
  109. CurrencyUnit::Sat => CdkCurrencyUnit::Sat,
  110. CurrencyUnit::Msat => CdkCurrencyUnit::Msat,
  111. CurrencyUnit::Usd => CdkCurrencyUnit::Usd,
  112. CurrencyUnit::Eur => CdkCurrencyUnit::Eur,
  113. CurrencyUnit::Auth => CdkCurrencyUnit::Auth,
  114. CurrencyUnit::Custom { unit } => CdkCurrencyUnit::Custom(unit),
  115. }
  116. }
  117. }
  118. /// FFI-compatible Mint URL
  119. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, uniffi::Record)]
  120. #[serde(transparent)]
  121. pub struct MintUrl {
  122. pub url: String,
  123. }
  124. impl MintUrl {
  125. pub fn new(url: String) -> Result<Self, FfiError> {
  126. // Validate URL format
  127. url::Url::parse(&url).map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })?;
  128. Ok(Self { url })
  129. }
  130. }
  131. impl From<cdk::mint_url::MintUrl> for MintUrl {
  132. fn from(mint_url: cdk::mint_url::MintUrl) -> Self {
  133. Self {
  134. url: mint_url.to_string(),
  135. }
  136. }
  137. }
  138. impl TryFrom<MintUrl> for cdk::mint_url::MintUrl {
  139. type Error = FfiError;
  140. fn try_from(mint_url: MintUrl) -> Result<Self, Self::Error> {
  141. cdk::mint_url::MintUrl::from_str(&mint_url.url)
  142. .map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })
  143. }
  144. }
  145. /// FFI-compatible Proof state
  146. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
  147. pub enum ProofState {
  148. Unspent,
  149. Pending,
  150. Spent,
  151. Reserved,
  152. PendingSpent,
  153. }
  154. impl From<CdkState> for ProofState {
  155. fn from(state: CdkState) -> Self {
  156. match state {
  157. CdkState::Unspent => ProofState::Unspent,
  158. CdkState::Pending => ProofState::Pending,
  159. CdkState::Spent => ProofState::Spent,
  160. CdkState::Reserved => ProofState::Reserved,
  161. CdkState::PendingSpent => ProofState::PendingSpent,
  162. }
  163. }
  164. }
  165. impl From<ProofState> for CdkState {
  166. fn from(state: ProofState) -> Self {
  167. match state {
  168. ProofState::Unspent => CdkState::Unspent,
  169. ProofState::Pending => CdkState::Pending,
  170. ProofState::Spent => CdkState::Spent,
  171. ProofState::Reserved => CdkState::Reserved,
  172. ProofState::PendingSpent => CdkState::PendingSpent,
  173. }
  174. }
  175. }
  176. /// FFI-compatible Token
  177. #[derive(Debug, uniffi::Object)]
  178. pub struct Token {
  179. pub(crate) inner: cdk::nuts::Token,
  180. }
  181. impl std::fmt::Display for Token {
  182. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  183. write!(f, "{}", self.inner)
  184. }
  185. }
  186. impl FromStr for Token {
  187. type Err = FfiError;
  188. fn from_str(s: &str) -> Result<Self, Self::Err> {
  189. let token = cdk::nuts::Token::from_str(s)
  190. .map_err(|e| FfiError::InvalidToken { msg: e.to_string() })?;
  191. Ok(Token { inner: token })
  192. }
  193. }
  194. impl From<cdk::nuts::Token> for Token {
  195. fn from(token: cdk::nuts::Token) -> Self {
  196. Self { inner: token }
  197. }
  198. }
  199. impl From<Token> for cdk::nuts::Token {
  200. fn from(token: Token) -> Self {
  201. token.inner
  202. }
  203. }
  204. #[uniffi::export]
  205. impl Token {
  206. /// Create a new Token from string
  207. #[uniffi::constructor]
  208. pub fn from_string(encoded_token: String) -> Result<Token, FfiError> {
  209. let token = cdk::nuts::Token::from_str(&encoded_token)
  210. .map_err(|e| FfiError::InvalidToken { msg: e.to_string() })?;
  211. Ok(Token { inner: token })
  212. }
  213. /// Get the total value of the token
  214. pub fn value(&self) -> Result<Amount, FfiError> {
  215. Ok(self.inner.value()?.into())
  216. }
  217. /// Get the memo from the token
  218. pub fn memo(&self) -> Option<String> {
  219. self.inner.memo().clone()
  220. }
  221. /// Get the currency unit
  222. pub fn unit(&self) -> Option<CurrencyUnit> {
  223. self.inner.unit().map(Into::into)
  224. }
  225. /// Get the mint URL
  226. pub fn mint_url(&self) -> Result<MintUrl, FfiError> {
  227. Ok(self.inner.mint_url()?.into())
  228. }
  229. /// Get proofs from the token (simplified - no keyset filtering for now)
  230. pub fn proofs_simple(&self) -> Result<Proofs, FfiError> {
  231. // For now, return empty keysets to get all proofs
  232. let empty_keysets = vec![];
  233. let proofs = self.inner.proofs(&empty_keysets)?;
  234. Ok(proofs
  235. .into_iter()
  236. .map(|p| std::sync::Arc::new(p.into()))
  237. .collect())
  238. }
  239. /// Convert token to raw bytes
  240. pub fn to_raw_bytes(&self) -> Result<Vec<u8>, FfiError> {
  241. Ok(self.inner.to_raw_bytes()?)
  242. }
  243. /// Encode token to string representation
  244. pub fn encode(&self) -> String {
  245. self.to_string()
  246. }
  247. /// Decode token from string representation
  248. #[uniffi::constructor]
  249. pub fn decode(encoded_token: String) -> Result<Token, FfiError> {
  250. encoded_token.parse()
  251. }
  252. }
  253. /// FFI-compatible SendMemo
  254. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  255. pub struct SendMemo {
  256. /// Memo text
  257. pub memo: String,
  258. /// Include memo in token
  259. pub include_memo: bool,
  260. }
  261. impl From<SendMemo> for cdk::wallet::SendMemo {
  262. fn from(memo: SendMemo) -> Self {
  263. cdk::wallet::SendMemo {
  264. memo: memo.memo,
  265. include_memo: memo.include_memo,
  266. }
  267. }
  268. }
  269. impl From<cdk::wallet::SendMemo> for SendMemo {
  270. fn from(memo: cdk::wallet::SendMemo) -> Self {
  271. Self {
  272. memo: memo.memo,
  273. include_memo: memo.include_memo,
  274. }
  275. }
  276. }
  277. impl SendMemo {
  278. /// Convert SendMemo to JSON string
  279. pub fn to_json(&self) -> Result<String, FfiError> {
  280. Ok(serde_json::to_string(self)?)
  281. }
  282. }
  283. /// Decode SendMemo from JSON string
  284. #[uniffi::export]
  285. pub fn decode_send_memo(json: String) -> Result<SendMemo, FfiError> {
  286. Ok(serde_json::from_str(&json)?)
  287. }
  288. /// Encode SendMemo to JSON string
  289. #[uniffi::export]
  290. pub fn encode_send_memo(memo: SendMemo) -> Result<String, FfiError> {
  291. Ok(serde_json::to_string(&memo)?)
  292. }
  293. /// FFI-compatible SplitTarget
  294. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
  295. pub enum SplitTarget {
  296. /// Default target; least amount of proofs
  297. None,
  298. /// Target amount for wallet to have most proofs that add up to value
  299. Value { amount: Amount },
  300. /// Specific amounts to split into (must equal amount being split)
  301. Values { amounts: Vec<Amount> },
  302. }
  303. impl From<SplitTarget> for cdk::amount::SplitTarget {
  304. fn from(target: SplitTarget) -> Self {
  305. match target {
  306. SplitTarget::None => cdk::amount::SplitTarget::None,
  307. SplitTarget::Value { amount } => cdk::amount::SplitTarget::Value(amount.into()),
  308. SplitTarget::Values { amounts } => {
  309. cdk::amount::SplitTarget::Values(amounts.into_iter().map(Into::into).collect())
  310. }
  311. }
  312. }
  313. }
  314. impl From<cdk::amount::SplitTarget> for SplitTarget {
  315. fn from(target: cdk::amount::SplitTarget) -> Self {
  316. match target {
  317. cdk::amount::SplitTarget::None => SplitTarget::None,
  318. cdk::amount::SplitTarget::Value(amount) => SplitTarget::Value {
  319. amount: amount.into(),
  320. },
  321. cdk::amount::SplitTarget::Values(amounts) => SplitTarget::Values {
  322. amounts: amounts.into_iter().map(Into::into).collect(),
  323. },
  324. }
  325. }
  326. }
  327. /// FFI-compatible SendKind
  328. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
  329. pub enum SendKind {
  330. /// Allow online swap before send if wallet does not have exact amount
  331. OnlineExact,
  332. /// Prefer offline send if difference is less than tolerance
  333. OnlineTolerance { tolerance: Amount },
  334. /// Wallet cannot do an online swap and selected proof must be exactly send amount
  335. OfflineExact,
  336. /// Wallet must remain offline but can over pay if below tolerance
  337. OfflineTolerance { tolerance: Amount },
  338. }
  339. impl From<SendKind> for cdk::wallet::SendKind {
  340. fn from(kind: SendKind) -> Self {
  341. match kind {
  342. SendKind::OnlineExact => cdk::wallet::SendKind::OnlineExact,
  343. SendKind::OnlineTolerance { tolerance } => {
  344. cdk::wallet::SendKind::OnlineTolerance(tolerance.into())
  345. }
  346. SendKind::OfflineExact => cdk::wallet::SendKind::OfflineExact,
  347. SendKind::OfflineTolerance { tolerance } => {
  348. cdk::wallet::SendKind::OfflineTolerance(tolerance.into())
  349. }
  350. }
  351. }
  352. }
  353. impl From<cdk::wallet::SendKind> for SendKind {
  354. fn from(kind: cdk::wallet::SendKind) -> Self {
  355. match kind {
  356. cdk::wallet::SendKind::OnlineExact => SendKind::OnlineExact,
  357. cdk::wallet::SendKind::OnlineTolerance(tolerance) => SendKind::OnlineTolerance {
  358. tolerance: tolerance.into(),
  359. },
  360. cdk::wallet::SendKind::OfflineExact => SendKind::OfflineExact,
  361. cdk::wallet::SendKind::OfflineTolerance(tolerance) => SendKind::OfflineTolerance {
  362. tolerance: tolerance.into(),
  363. },
  364. }
  365. }
  366. }
  367. /// FFI-compatible Send options
  368. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  369. pub struct SendOptions {
  370. /// Memo
  371. pub memo: Option<SendMemo>,
  372. /// Spending conditions
  373. pub conditions: Option<SpendingConditions>,
  374. /// Amount split target
  375. pub amount_split_target: SplitTarget,
  376. /// Send kind
  377. pub send_kind: SendKind,
  378. /// Include fee
  379. pub include_fee: bool,
  380. /// Maximum number of proofs to include in the token
  381. pub max_proofs: Option<u32>,
  382. /// Metadata
  383. pub metadata: HashMap<String, String>,
  384. }
  385. impl Default for SendOptions {
  386. fn default() -> Self {
  387. Self {
  388. memo: None,
  389. conditions: None,
  390. amount_split_target: SplitTarget::None,
  391. send_kind: SendKind::OnlineExact,
  392. include_fee: false,
  393. max_proofs: None,
  394. metadata: HashMap::new(),
  395. }
  396. }
  397. }
  398. impl From<SendOptions> for cdk::wallet::SendOptions {
  399. fn from(opts: SendOptions) -> Self {
  400. cdk::wallet::SendOptions {
  401. memo: opts.memo.map(Into::into),
  402. conditions: opts.conditions.and_then(|c| c.try_into().ok()),
  403. amount_split_target: opts.amount_split_target.into(),
  404. send_kind: opts.send_kind.into(),
  405. include_fee: opts.include_fee,
  406. max_proofs: opts.max_proofs.map(|p| p as usize),
  407. metadata: opts.metadata,
  408. }
  409. }
  410. }
  411. impl From<cdk::wallet::SendOptions> for SendOptions {
  412. fn from(opts: cdk::wallet::SendOptions) -> Self {
  413. Self {
  414. memo: opts.memo.map(Into::into),
  415. conditions: opts.conditions.map(Into::into),
  416. amount_split_target: opts.amount_split_target.into(),
  417. send_kind: opts.send_kind.into(),
  418. include_fee: opts.include_fee,
  419. max_proofs: opts.max_proofs.map(|p| p as u32),
  420. metadata: opts.metadata,
  421. }
  422. }
  423. }
  424. impl SendOptions {
  425. /// Convert SendOptions to JSON string
  426. pub fn to_json(&self) -> Result<String, FfiError> {
  427. Ok(serde_json::to_string(self)?)
  428. }
  429. }
  430. /// Decode SendOptions from JSON string
  431. #[uniffi::export]
  432. pub fn decode_send_options(json: String) -> Result<SendOptions, FfiError> {
  433. Ok(serde_json::from_str(&json)?)
  434. }
  435. /// Encode SendOptions to JSON string
  436. #[uniffi::export]
  437. pub fn encode_send_options(options: SendOptions) -> Result<String, FfiError> {
  438. Ok(serde_json::to_string(&options)?)
  439. }
  440. /// FFI-compatible SecretKey
  441. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  442. #[serde(transparent)]
  443. pub struct SecretKey {
  444. /// Hex-encoded secret key (64 characters)
  445. pub hex: String,
  446. }
  447. impl SecretKey {
  448. /// Create a new SecretKey from hex string
  449. pub fn from_hex(hex: String) -> Result<Self, FfiError> {
  450. // Validate hex string length (should be 64 characters for 32 bytes)
  451. if hex.len() != 64 {
  452. return Err(FfiError::InvalidHex {
  453. msg: "Secret key hex must be exactly 64 characters (32 bytes)".to_string(),
  454. });
  455. }
  456. // Validate hex format
  457. if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
  458. return Err(FfiError::InvalidHex {
  459. msg: "Secret key hex contains invalid characters".to_string(),
  460. });
  461. }
  462. Ok(Self { hex })
  463. }
  464. /// Generate a random secret key
  465. pub fn random() -> Self {
  466. use cdk::nuts::SecretKey as CdkSecretKey;
  467. let secret_key = CdkSecretKey::generate();
  468. Self {
  469. hex: secret_key.to_secret_hex(),
  470. }
  471. }
  472. }
  473. impl From<SecretKey> for cdk::nuts::SecretKey {
  474. fn from(key: SecretKey) -> Self {
  475. // This will panic if hex is invalid, but we validate in from_hex()
  476. cdk::nuts::SecretKey::from_hex(&key.hex).expect("Invalid secret key hex")
  477. }
  478. }
  479. impl From<cdk::nuts::SecretKey> for SecretKey {
  480. fn from(key: cdk::nuts::SecretKey) -> Self {
  481. Self {
  482. hex: key.to_secret_hex(),
  483. }
  484. }
  485. }
  486. /// FFI-compatible Receive options
  487. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  488. pub struct ReceiveOptions {
  489. /// Amount split target
  490. pub amount_split_target: SplitTarget,
  491. /// P2PK signing keys
  492. pub p2pk_signing_keys: Vec<SecretKey>,
  493. /// Preimages for HTLC conditions
  494. pub preimages: Vec<String>,
  495. /// Metadata
  496. pub metadata: HashMap<String, String>,
  497. }
  498. impl Default for ReceiveOptions {
  499. fn default() -> Self {
  500. Self {
  501. amount_split_target: SplitTarget::None,
  502. p2pk_signing_keys: Vec::new(),
  503. preimages: Vec::new(),
  504. metadata: HashMap::new(),
  505. }
  506. }
  507. }
  508. impl From<ReceiveOptions> for cdk::wallet::ReceiveOptions {
  509. fn from(opts: ReceiveOptions) -> Self {
  510. cdk::wallet::ReceiveOptions {
  511. amount_split_target: opts.amount_split_target.into(),
  512. p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
  513. preimages: opts.preimages,
  514. metadata: opts.metadata,
  515. }
  516. }
  517. }
  518. impl From<cdk::wallet::ReceiveOptions> for ReceiveOptions {
  519. fn from(opts: cdk::wallet::ReceiveOptions) -> Self {
  520. Self {
  521. amount_split_target: opts.amount_split_target.into(),
  522. p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
  523. preimages: opts.preimages,
  524. metadata: opts.metadata,
  525. }
  526. }
  527. }
  528. impl ReceiveOptions {
  529. /// Convert ReceiveOptions to JSON string
  530. pub fn to_json(&self) -> Result<String, FfiError> {
  531. Ok(serde_json::to_string(self)?)
  532. }
  533. }
  534. /// Decode ReceiveOptions from JSON string
  535. #[uniffi::export]
  536. pub fn decode_receive_options(json: String) -> Result<ReceiveOptions, FfiError> {
  537. Ok(serde_json::from_str(&json)?)
  538. }
  539. /// Encode ReceiveOptions to JSON string
  540. #[uniffi::export]
  541. pub fn encode_receive_options(options: ReceiveOptions) -> Result<String, FfiError> {
  542. Ok(serde_json::to_string(&options)?)
  543. }
  544. /// FFI-compatible Proof
  545. #[derive(Debug, uniffi::Object)]
  546. pub struct Proof {
  547. pub(crate) inner: cdk::nuts::Proof,
  548. }
  549. impl From<cdk::nuts::Proof> for Proof {
  550. fn from(proof: cdk::nuts::Proof) -> Self {
  551. Self { inner: proof }
  552. }
  553. }
  554. impl From<Proof> for cdk::nuts::Proof {
  555. fn from(proof: Proof) -> Self {
  556. proof.inner
  557. }
  558. }
  559. #[uniffi::export]
  560. impl Proof {
  561. /// Get the amount
  562. pub fn amount(&self) -> Amount {
  563. self.inner.amount.into()
  564. }
  565. /// Get the secret as string
  566. pub fn secret(&self) -> String {
  567. self.inner.secret.to_string()
  568. }
  569. /// Get the unblinded signature (C) as string
  570. pub fn c(&self) -> String {
  571. self.inner.c.to_string()
  572. }
  573. /// Get the keyset ID as string
  574. pub fn keyset_id(&self) -> String {
  575. self.inner.keyset_id.to_string()
  576. }
  577. /// Get the witness
  578. pub fn witness(&self) -> Option<Witness> {
  579. self.inner.witness.as_ref().map(|w| w.clone().into())
  580. }
  581. /// Check if proof is active with given keyset IDs
  582. pub fn is_active(&self, active_keyset_ids: Vec<String>) -> bool {
  583. use cdk::nuts::Id;
  584. let ids: Vec<Id> = active_keyset_ids
  585. .into_iter()
  586. .filter_map(|id| Id::from_str(&id).ok())
  587. .collect();
  588. self.inner.is_active(&ids)
  589. }
  590. /// Get the Y value (hash_to_curve of secret)
  591. pub fn y(&self) -> Result<String, FfiError> {
  592. Ok(self.inner.y()?.to_string())
  593. }
  594. /// Get the DLEQ proof if present
  595. pub fn dleq(&self) -> Option<ProofDleq> {
  596. self.inner.dleq.as_ref().map(|d| d.clone().into())
  597. }
  598. /// Check if proof has DLEQ proof
  599. pub fn has_dleq(&self) -> bool {
  600. self.inner.dleq.is_some()
  601. }
  602. }
  603. /// FFI-compatible Proofs (vector of Proof)
  604. pub type Proofs = Vec<std::sync::Arc<Proof>>;
  605. /// FFI-compatible DLEQ proof for proofs
  606. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  607. pub struct ProofDleq {
  608. /// e value (hex-encoded SecretKey)
  609. pub e: String,
  610. /// s value (hex-encoded SecretKey)
  611. pub s: String,
  612. /// r value - blinding factor (hex-encoded SecretKey)
  613. pub r: String,
  614. }
  615. /// FFI-compatible DLEQ proof for blind signatures
  616. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  617. pub struct BlindSignatureDleq {
  618. /// e value (hex-encoded SecretKey)
  619. pub e: String,
  620. /// s value (hex-encoded SecretKey)
  621. pub s: String,
  622. }
  623. impl From<cdk::nuts::ProofDleq> for ProofDleq {
  624. fn from(dleq: cdk::nuts::ProofDleq) -> Self {
  625. Self {
  626. e: dleq.e.to_secret_hex(),
  627. s: dleq.s.to_secret_hex(),
  628. r: dleq.r.to_secret_hex(),
  629. }
  630. }
  631. }
  632. impl From<ProofDleq> for cdk::nuts::ProofDleq {
  633. fn from(dleq: ProofDleq) -> Self {
  634. Self {
  635. e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
  636. s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
  637. r: cdk::nuts::SecretKey::from_hex(&dleq.r).expect("Invalid r hex"),
  638. }
  639. }
  640. }
  641. impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
  642. fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
  643. Self {
  644. e: dleq.e.to_secret_hex(),
  645. s: dleq.s.to_secret_hex(),
  646. }
  647. }
  648. }
  649. impl From<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
  650. fn from(dleq: BlindSignatureDleq) -> Self {
  651. Self {
  652. e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
  653. s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
  654. }
  655. }
  656. }
  657. /// Helper functions for Proofs
  658. pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
  659. let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
  660. use cdk::nuts::ProofsMethods;
  661. Ok(cdk_proofs.total_amount()?.into())
  662. }
  663. /// FFI-compatible MintQuote
  664. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  665. pub struct MintQuote {
  666. /// Quote ID
  667. pub id: String,
  668. /// Quote amount
  669. pub amount: Option<Amount>,
  670. /// Currency unit
  671. pub unit: CurrencyUnit,
  672. /// Payment request
  673. pub request: String,
  674. /// Quote state
  675. pub state: QuoteState,
  676. /// Expiry timestamp
  677. pub expiry: u64,
  678. /// Mint URL
  679. pub mint_url: MintUrl,
  680. /// Amount issued
  681. pub amount_issued: Amount,
  682. /// Amount paid
  683. pub amount_paid: Amount,
  684. /// Payment method
  685. pub payment_method: PaymentMethod,
  686. /// Secret key (optional, hex-encoded)
  687. pub secret_key: Option<String>,
  688. }
  689. impl From<cdk::wallet::MintQuote> for MintQuote {
  690. fn from(quote: cdk::wallet::MintQuote) -> Self {
  691. Self {
  692. id: quote.id.clone(),
  693. amount: quote.amount.map(Into::into),
  694. unit: quote.unit.clone().into(),
  695. request: quote.request.clone(),
  696. state: quote.state.into(),
  697. expiry: quote.expiry,
  698. mint_url: quote.mint_url.clone().into(),
  699. amount_issued: quote.amount_issued.into(),
  700. amount_paid: quote.amount_paid.into(),
  701. payment_method: quote.payment_method.into(),
  702. secret_key: quote.secret_key.map(|sk| sk.to_secret_hex()),
  703. }
  704. }
  705. }
  706. impl TryFrom<MintQuote> for cdk::wallet::MintQuote {
  707. type Error = FfiError;
  708. fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
  709. let secret_key = quote
  710. .secret_key
  711. .map(|hex| cdk::nuts::SecretKey::from_hex(&hex))
  712. .transpose()
  713. .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
  714. Ok(Self {
  715. id: quote.id,
  716. amount: quote.amount.map(Into::into),
  717. unit: quote.unit.into(),
  718. request: quote.request,
  719. state: quote.state.into(),
  720. expiry: quote.expiry,
  721. mint_url: quote.mint_url.try_into()?,
  722. amount_issued: quote.amount_issued.into(),
  723. amount_paid: quote.amount_paid.into(),
  724. payment_method: quote.payment_method.into(),
  725. secret_key,
  726. })
  727. }
  728. }
  729. impl MintQuote {
  730. /// Get total amount (amount + fees)
  731. pub fn total_amount(&self) -> Amount {
  732. if let Some(amount) = self.amount {
  733. Amount::new(amount.value + self.amount_paid.value - self.amount_issued.value)
  734. } else {
  735. Amount::zero()
  736. }
  737. }
  738. /// Check if quote is expired
  739. pub fn is_expired(&self, current_time: u64) -> bool {
  740. current_time > self.expiry
  741. }
  742. /// Get amount that can be minted
  743. pub fn amount_mintable(&self) -> Amount {
  744. Amount::new(self.amount_paid.value - self.amount_issued.value)
  745. }
  746. /// Convert MintQuote to JSON string
  747. pub fn to_json(&self) -> Result<String, FfiError> {
  748. Ok(serde_json::to_string(self)?)
  749. }
  750. }
  751. /// Decode MintQuote from JSON string
  752. #[uniffi::export]
  753. pub fn decode_mint_quote(json: String) -> Result<MintQuote, FfiError> {
  754. let quote: cdk::wallet::MintQuote = serde_json::from_str(&json)?;
  755. Ok(quote.into())
  756. }
  757. /// Encode MintQuote to JSON string
  758. #[uniffi::export]
  759. pub fn encode_mint_quote(quote: MintQuote) -> Result<String, FfiError> {
  760. Ok(serde_json::to_string(&quote)?)
  761. }
  762. /// FFI-compatible MintQuoteBolt11Response
  763. #[derive(Debug, uniffi::Object)]
  764. pub struct MintQuoteBolt11Response {
  765. /// Quote ID
  766. pub quote: String,
  767. /// Request string
  768. pub request: String,
  769. /// State of the quote
  770. pub state: QuoteState,
  771. /// Expiry timestamp (optional)
  772. pub expiry: Option<u64>,
  773. /// Amount (optional)
  774. pub amount: Option<Amount>,
  775. /// Unit (optional)
  776. pub unit: Option<CurrencyUnit>,
  777. /// Pubkey (optional)
  778. pub pubkey: Option<String>,
  779. }
  780. impl From<cdk::nuts::MintQuoteBolt11Response<String>> for MintQuoteBolt11Response {
  781. fn from(response: cdk::nuts::MintQuoteBolt11Response<String>) -> Self {
  782. Self {
  783. quote: response.quote,
  784. request: response.request,
  785. state: response.state.into(),
  786. expiry: response.expiry,
  787. amount: response.amount.map(Into::into),
  788. unit: response.unit.map(Into::into),
  789. pubkey: response.pubkey.map(|p| p.to_string()),
  790. }
  791. }
  792. }
  793. #[uniffi::export]
  794. impl MintQuoteBolt11Response {
  795. /// Get quote ID
  796. pub fn quote(&self) -> String {
  797. self.quote.clone()
  798. }
  799. /// Get request string
  800. pub fn request(&self) -> String {
  801. self.request.clone()
  802. }
  803. /// Get state
  804. pub fn state(&self) -> QuoteState {
  805. self.state.clone()
  806. }
  807. /// Get expiry
  808. pub fn expiry(&self) -> Option<u64> {
  809. self.expiry
  810. }
  811. /// Get amount
  812. pub fn amount(&self) -> Option<Amount> {
  813. self.amount
  814. }
  815. /// Get unit
  816. pub fn unit(&self) -> Option<CurrencyUnit> {
  817. self.unit.clone()
  818. }
  819. /// Get pubkey
  820. pub fn pubkey(&self) -> Option<String> {
  821. self.pubkey.clone()
  822. }
  823. }
  824. /// FFI-compatible MeltQuoteBolt11Response
  825. #[derive(Debug, uniffi::Object)]
  826. pub struct MeltQuoteBolt11Response {
  827. /// Quote ID
  828. pub quote: String,
  829. /// Amount
  830. pub amount: Amount,
  831. /// Fee reserve
  832. pub fee_reserve: Amount,
  833. /// State of the quote
  834. pub state: QuoteState,
  835. /// Expiry timestamp
  836. pub expiry: u64,
  837. /// Payment preimage (optional)
  838. pub payment_preimage: Option<String>,
  839. /// Request string (optional)
  840. pub request: Option<String>,
  841. /// Unit (optional)
  842. pub unit: Option<CurrencyUnit>,
  843. }
  844. impl From<cdk::nuts::MeltQuoteBolt11Response<String>> for MeltQuoteBolt11Response {
  845. fn from(response: cdk::nuts::MeltQuoteBolt11Response<String>) -> Self {
  846. Self {
  847. quote: response.quote,
  848. amount: response.amount.into(),
  849. fee_reserve: response.fee_reserve.into(),
  850. state: response.state.into(),
  851. expiry: response.expiry,
  852. payment_preimage: response.payment_preimage,
  853. request: response.request,
  854. unit: response.unit.map(Into::into),
  855. }
  856. }
  857. }
  858. #[uniffi::export]
  859. impl MeltQuoteBolt11Response {
  860. /// Get quote ID
  861. pub fn quote(&self) -> String {
  862. self.quote.clone()
  863. }
  864. /// Get amount
  865. pub fn amount(&self) -> Amount {
  866. self.amount
  867. }
  868. /// Get fee reserve
  869. pub fn fee_reserve(&self) -> Amount {
  870. self.fee_reserve
  871. }
  872. /// Get state
  873. pub fn state(&self) -> QuoteState {
  874. self.state.clone()
  875. }
  876. /// Get expiry
  877. pub fn expiry(&self) -> u64 {
  878. self.expiry
  879. }
  880. /// Get payment preimage
  881. pub fn payment_preimage(&self) -> Option<String> {
  882. self.payment_preimage.clone()
  883. }
  884. /// Get request
  885. pub fn request(&self) -> Option<String> {
  886. self.request.clone()
  887. }
  888. /// Get unit
  889. pub fn unit(&self) -> Option<CurrencyUnit> {
  890. self.unit.clone()
  891. }
  892. }
  893. /// FFI-compatible PaymentMethod
  894. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
  895. pub enum PaymentMethod {
  896. /// Bolt11 payment type
  897. Bolt11,
  898. /// Bolt12 payment type
  899. Bolt12,
  900. /// Custom payment type
  901. Custom { method: String },
  902. }
  903. impl From<cdk::nuts::PaymentMethod> for PaymentMethod {
  904. fn from(method: cdk::nuts::PaymentMethod) -> Self {
  905. match method {
  906. cdk::nuts::PaymentMethod::Bolt11 => Self::Bolt11,
  907. cdk::nuts::PaymentMethod::Bolt12 => Self::Bolt12,
  908. cdk::nuts::PaymentMethod::Custom(s) => Self::Custom { method: s },
  909. }
  910. }
  911. }
  912. impl From<PaymentMethod> for cdk::nuts::PaymentMethod {
  913. fn from(method: PaymentMethod) -> Self {
  914. match method {
  915. PaymentMethod::Bolt11 => Self::Bolt11,
  916. PaymentMethod::Bolt12 => Self::Bolt12,
  917. PaymentMethod::Custom { method } => Self::Custom(method),
  918. }
  919. }
  920. }
  921. /// FFI-compatible MeltQuote
  922. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  923. pub struct MeltQuote {
  924. /// Quote ID
  925. pub id: String,
  926. /// Quote amount
  927. pub amount: Amount,
  928. /// Currency unit
  929. pub unit: CurrencyUnit,
  930. /// Payment request
  931. pub request: String,
  932. /// Fee reserve
  933. pub fee_reserve: Amount,
  934. /// Quote state
  935. pub state: QuoteState,
  936. /// Expiry timestamp
  937. pub expiry: u64,
  938. /// Payment preimage
  939. pub payment_preimage: Option<String>,
  940. /// Payment method
  941. pub payment_method: PaymentMethod,
  942. }
  943. impl From<cdk::wallet::MeltQuote> for MeltQuote {
  944. fn from(quote: cdk::wallet::MeltQuote) -> Self {
  945. Self {
  946. id: quote.id.clone(),
  947. amount: quote.amount.into(),
  948. unit: quote.unit.clone().into(),
  949. request: quote.request.clone(),
  950. fee_reserve: quote.fee_reserve.into(),
  951. state: quote.state.into(),
  952. expiry: quote.expiry,
  953. payment_preimage: quote.payment_preimage.clone(),
  954. payment_method: quote.payment_method.into(),
  955. }
  956. }
  957. }
  958. impl TryFrom<MeltQuote> for cdk::wallet::MeltQuote {
  959. type Error = FfiError;
  960. fn try_from(quote: MeltQuote) -> Result<Self, Self::Error> {
  961. Ok(Self {
  962. id: quote.id,
  963. amount: quote.amount.into(),
  964. unit: quote.unit.into(),
  965. request: quote.request,
  966. fee_reserve: quote.fee_reserve.into(),
  967. state: quote.state.into(),
  968. expiry: quote.expiry,
  969. payment_preimage: quote.payment_preimage,
  970. payment_method: quote.payment_method.into(),
  971. })
  972. }
  973. }
  974. impl MeltQuote {
  975. /// Convert MeltQuote to JSON string
  976. pub fn to_json(&self) -> Result<String, FfiError> {
  977. Ok(serde_json::to_string(self)?)
  978. }
  979. }
  980. /// Decode MeltQuote from JSON string
  981. #[uniffi::export]
  982. pub fn decode_melt_quote(json: String) -> Result<MeltQuote, FfiError> {
  983. let quote: cdk::wallet::MeltQuote = serde_json::from_str(&json)?;
  984. Ok(quote.into())
  985. }
  986. /// Encode MeltQuote to JSON string
  987. #[uniffi::export]
  988. pub fn encode_melt_quote(quote: MeltQuote) -> Result<String, FfiError> {
  989. Ok(serde_json::to_string(&quote)?)
  990. }
  991. /// FFI-compatible QuoteState
  992. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
  993. pub enum QuoteState {
  994. Unpaid,
  995. Paid,
  996. Pending,
  997. Issued,
  998. }
  999. impl From<cdk::nuts::nut05::QuoteState> for QuoteState {
  1000. fn from(state: cdk::nuts::nut05::QuoteState) -> Self {
  1001. match state {
  1002. cdk::nuts::nut05::QuoteState::Unpaid => QuoteState::Unpaid,
  1003. cdk::nuts::nut05::QuoteState::Paid => QuoteState::Paid,
  1004. cdk::nuts::nut05::QuoteState::Pending => QuoteState::Pending,
  1005. cdk::nuts::nut05::QuoteState::Unknown => QuoteState::Unpaid,
  1006. cdk::nuts::nut05::QuoteState::Failed => QuoteState::Unpaid,
  1007. }
  1008. }
  1009. }
  1010. impl From<QuoteState> for cdk::nuts::nut05::QuoteState {
  1011. fn from(state: QuoteState) -> Self {
  1012. match state {
  1013. QuoteState::Unpaid => cdk::nuts::nut05::QuoteState::Unpaid,
  1014. QuoteState::Paid => cdk::nuts::nut05::QuoteState::Paid,
  1015. QuoteState::Pending => cdk::nuts::nut05::QuoteState::Pending,
  1016. QuoteState::Issued => cdk::nuts::nut05::QuoteState::Paid, // Map issued to paid for melt quotes
  1017. }
  1018. }
  1019. }
  1020. impl From<cdk::nuts::MintQuoteState> for QuoteState {
  1021. fn from(state: cdk::nuts::MintQuoteState) -> Self {
  1022. match state {
  1023. cdk::nuts::MintQuoteState::Unpaid => QuoteState::Unpaid,
  1024. cdk::nuts::MintQuoteState::Paid => QuoteState::Paid,
  1025. cdk::nuts::MintQuoteState::Issued => QuoteState::Issued,
  1026. }
  1027. }
  1028. }
  1029. impl From<QuoteState> for cdk::nuts::MintQuoteState {
  1030. fn from(state: QuoteState) -> Self {
  1031. match state {
  1032. QuoteState::Unpaid => cdk::nuts::MintQuoteState::Unpaid,
  1033. QuoteState::Paid => cdk::nuts::MintQuoteState::Paid,
  1034. QuoteState::Issued => cdk::nuts::MintQuoteState::Issued,
  1035. QuoteState::Pending => cdk::nuts::MintQuoteState::Paid, // Map pending to paid
  1036. }
  1037. }
  1038. }
  1039. // Note: MeltQuoteState is the same as nut05::QuoteState, so we don't need a separate impl
  1040. /// FFI-compatible PreparedSend
  1041. #[derive(Debug, uniffi::Object)]
  1042. pub struct PreparedSend {
  1043. inner: Mutex<Option<cdk::wallet::PreparedSend>>,
  1044. id: String,
  1045. amount: Amount,
  1046. proofs: Proofs,
  1047. }
  1048. impl From<cdk::wallet::PreparedSend> for PreparedSend {
  1049. fn from(prepared: cdk::wallet::PreparedSend) -> Self {
  1050. let id = format!("{:?}", prepared); // Use debug format as ID
  1051. let amount = prepared.amount().into();
  1052. let proofs = prepared
  1053. .proofs()
  1054. .iter()
  1055. .cloned()
  1056. .map(|p| std::sync::Arc::new(p.into()))
  1057. .collect();
  1058. Self {
  1059. inner: Mutex::new(Some(prepared)),
  1060. id,
  1061. amount,
  1062. proofs,
  1063. }
  1064. }
  1065. }
  1066. #[uniffi::export(async_runtime = "tokio")]
  1067. impl PreparedSend {
  1068. /// Get the prepared send ID
  1069. pub fn id(&self) -> String {
  1070. self.id.clone()
  1071. }
  1072. /// Get the amount to send
  1073. pub fn amount(&self) -> Amount {
  1074. self.amount
  1075. }
  1076. /// Get the proofs that will be used
  1077. pub fn proofs(&self) -> Proofs {
  1078. self.proofs.clone()
  1079. }
  1080. /// Get the total fee for this send operation
  1081. pub fn fee(&self) -> Amount {
  1082. if let Ok(guard) = self.inner.lock() {
  1083. if let Some(ref inner) = *guard {
  1084. inner.fee().into()
  1085. } else {
  1086. Amount::new(0)
  1087. }
  1088. } else {
  1089. Amount::new(0)
  1090. }
  1091. }
  1092. /// Confirm the prepared send and create a token
  1093. pub async fn confirm(
  1094. self: std::sync::Arc<Self>,
  1095. memo: Option<String>,
  1096. ) -> Result<Token, FfiError> {
  1097. let inner = {
  1098. if let Ok(mut guard) = self.inner.lock() {
  1099. guard.take()
  1100. } else {
  1101. return Err(FfiError::Generic {
  1102. msg: "Failed to acquire lock on PreparedSend".to_string(),
  1103. });
  1104. }
  1105. };
  1106. if let Some(inner) = inner {
  1107. let send_memo = memo.map(|m| cdk::wallet::SendMemo::for_token(&m));
  1108. let token = inner.confirm(send_memo).await?;
  1109. Ok(token.into())
  1110. } else {
  1111. Err(FfiError::Generic {
  1112. msg: "PreparedSend has already been consumed or cancelled".to_string(),
  1113. })
  1114. }
  1115. }
  1116. /// Cancel the prepared send operation
  1117. pub async fn cancel(self: std::sync::Arc<Self>) -> Result<(), FfiError> {
  1118. let inner = {
  1119. if let Ok(mut guard) = self.inner.lock() {
  1120. guard.take()
  1121. } else {
  1122. return Err(FfiError::Generic {
  1123. msg: "Failed to acquire lock on PreparedSend".to_string(),
  1124. });
  1125. }
  1126. };
  1127. if let Some(inner) = inner {
  1128. inner.cancel().await?;
  1129. Ok(())
  1130. } else {
  1131. Err(FfiError::Generic {
  1132. msg: "PreparedSend has already been consumed or cancelled".to_string(),
  1133. })
  1134. }
  1135. }
  1136. }
  1137. /// FFI-compatible Melted result
  1138. #[derive(Debug, Clone, uniffi::Record)]
  1139. pub struct Melted {
  1140. pub state: QuoteState,
  1141. pub preimage: Option<String>,
  1142. pub change: Option<Proofs>,
  1143. pub amount: Amount,
  1144. pub fee_paid: Amount,
  1145. }
  1146. // MeltQuoteState is just an alias for nut05::QuoteState, so we don't need a separate implementation
  1147. impl From<cdk::types::Melted> for Melted {
  1148. fn from(melted: cdk::types::Melted) -> Self {
  1149. Self {
  1150. state: melted.state.into(),
  1151. preimage: melted.preimage,
  1152. change: melted.change.map(|proofs| {
  1153. proofs
  1154. .into_iter()
  1155. .map(|p| std::sync::Arc::new(p.into()))
  1156. .collect()
  1157. }),
  1158. amount: melted.amount.into(),
  1159. fee_paid: melted.fee_paid.into(),
  1160. }
  1161. }
  1162. }
  1163. /// FFI-compatible MeltOptions
  1164. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
  1165. pub enum MeltOptions {
  1166. /// MPP (Multi-Part Payments) options
  1167. Mpp { amount: Amount },
  1168. /// Amountless options
  1169. Amountless { amount_msat: Amount },
  1170. }
  1171. impl From<MeltOptions> for cdk::nuts::MeltOptions {
  1172. fn from(opts: MeltOptions) -> Self {
  1173. match opts {
  1174. MeltOptions::Mpp { amount } => {
  1175. let cdk_amount: cdk::Amount = amount.into();
  1176. cdk::nuts::MeltOptions::new_mpp(cdk_amount)
  1177. }
  1178. MeltOptions::Amountless { amount_msat } => {
  1179. let cdk_amount: cdk::Amount = amount_msat.into();
  1180. cdk::nuts::MeltOptions::new_amountless(cdk_amount)
  1181. }
  1182. }
  1183. }
  1184. }
  1185. impl From<cdk::nuts::MeltOptions> for MeltOptions {
  1186. fn from(opts: cdk::nuts::MeltOptions) -> Self {
  1187. match opts {
  1188. cdk::nuts::MeltOptions::Mpp { mpp } => MeltOptions::Mpp {
  1189. amount: mpp.amount.into(),
  1190. },
  1191. cdk::nuts::MeltOptions::Amountless { amountless } => MeltOptions::Amountless {
  1192. amount_msat: amountless.amount_msat.into(),
  1193. },
  1194. }
  1195. }
  1196. }
  1197. /// FFI-compatible MintVersion
  1198. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1199. pub struct MintVersion {
  1200. /// Mint Software name
  1201. pub name: String,
  1202. /// Mint Version
  1203. pub version: String,
  1204. }
  1205. impl From<cdk::nuts::MintVersion> for MintVersion {
  1206. fn from(version: cdk::nuts::MintVersion) -> Self {
  1207. Self {
  1208. name: version.name,
  1209. version: version.version,
  1210. }
  1211. }
  1212. }
  1213. impl From<MintVersion> for cdk::nuts::MintVersion {
  1214. fn from(version: MintVersion) -> Self {
  1215. Self {
  1216. name: version.name,
  1217. version: version.version,
  1218. }
  1219. }
  1220. }
  1221. impl MintVersion {
  1222. /// Convert MintVersion to JSON string
  1223. pub fn to_json(&self) -> Result<String, FfiError> {
  1224. Ok(serde_json::to_string(self)?)
  1225. }
  1226. }
  1227. /// Decode MintVersion from JSON string
  1228. #[uniffi::export]
  1229. pub fn decode_mint_version(json: String) -> Result<MintVersion, FfiError> {
  1230. Ok(serde_json::from_str(&json)?)
  1231. }
  1232. /// Encode MintVersion to JSON string
  1233. #[uniffi::export]
  1234. pub fn encode_mint_version(version: MintVersion) -> Result<String, FfiError> {
  1235. Ok(serde_json::to_string(&version)?)
  1236. }
  1237. /// FFI-compatible ContactInfo
  1238. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1239. pub struct ContactInfo {
  1240. /// Contact Method i.e. nostr
  1241. pub method: String,
  1242. /// Contact info i.e. npub...
  1243. pub info: String,
  1244. }
  1245. impl From<cdk::nuts::ContactInfo> for ContactInfo {
  1246. fn from(contact: cdk::nuts::ContactInfo) -> Self {
  1247. Self {
  1248. method: contact.method,
  1249. info: contact.info,
  1250. }
  1251. }
  1252. }
  1253. impl From<ContactInfo> for cdk::nuts::ContactInfo {
  1254. fn from(contact: ContactInfo) -> Self {
  1255. Self {
  1256. method: contact.method,
  1257. info: contact.info,
  1258. }
  1259. }
  1260. }
  1261. impl ContactInfo {
  1262. /// Convert ContactInfo to JSON string
  1263. pub fn to_json(&self) -> Result<String, FfiError> {
  1264. Ok(serde_json::to_string(self)?)
  1265. }
  1266. }
  1267. /// Decode ContactInfo from JSON string
  1268. #[uniffi::export]
  1269. pub fn decode_contact_info(json: String) -> Result<ContactInfo, FfiError> {
  1270. Ok(serde_json::from_str(&json)?)
  1271. }
  1272. /// Encode ContactInfo to JSON string
  1273. #[uniffi::export]
  1274. pub fn encode_contact_info(info: ContactInfo) -> Result<String, FfiError> {
  1275. Ok(serde_json::to_string(&info)?)
  1276. }
  1277. /// FFI-compatible SupportedSettings
  1278. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1279. #[serde(transparent)]
  1280. pub struct SupportedSettings {
  1281. /// Setting supported
  1282. pub supported: bool,
  1283. }
  1284. impl From<cdk::nuts::nut06::SupportedSettings> for SupportedSettings {
  1285. fn from(settings: cdk::nuts::nut06::SupportedSettings) -> Self {
  1286. Self {
  1287. supported: settings.supported,
  1288. }
  1289. }
  1290. }
  1291. impl From<SupportedSettings> for cdk::nuts::nut06::SupportedSettings {
  1292. fn from(settings: SupportedSettings) -> Self {
  1293. Self {
  1294. supported: settings.supported,
  1295. }
  1296. }
  1297. }
  1298. // -----------------------------
  1299. // NUT-04/05 FFI Types
  1300. // -----------------------------
  1301. /// FFI-compatible MintMethodSettings (NUT-04)
  1302. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1303. pub struct MintMethodSettings {
  1304. pub method: PaymentMethod,
  1305. pub unit: CurrencyUnit,
  1306. pub min_amount: Option<Amount>,
  1307. pub max_amount: Option<Amount>,
  1308. /// For bolt11, whether mint supports setting invoice description
  1309. pub description: Option<bool>,
  1310. }
  1311. impl From<cdk::nuts::nut04::MintMethodSettings> for MintMethodSettings {
  1312. fn from(s: cdk::nuts::nut04::MintMethodSettings) -> Self {
  1313. let description = match s.options {
  1314. Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description }) => Some(description),
  1315. _ => None,
  1316. };
  1317. Self {
  1318. method: s.method.into(),
  1319. unit: s.unit.into(),
  1320. min_amount: s.min_amount.map(Into::into),
  1321. max_amount: s.max_amount.map(Into::into),
  1322. description,
  1323. }
  1324. }
  1325. }
  1326. impl TryFrom<MintMethodSettings> for cdk::nuts::nut04::MintMethodSettings {
  1327. type Error = FfiError;
  1328. fn try_from(s: MintMethodSettings) -> Result<Self, Self::Error> {
  1329. let options = match (s.method.clone(), s.description) {
  1330. (PaymentMethod::Bolt11, Some(description)) => {
  1331. Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description })
  1332. }
  1333. _ => None,
  1334. };
  1335. Ok(Self {
  1336. method: s.method.into(),
  1337. unit: s.unit.into(),
  1338. min_amount: s.min_amount.map(Into::into),
  1339. max_amount: s.max_amount.map(Into::into),
  1340. options,
  1341. })
  1342. }
  1343. }
  1344. /// FFI-compatible Nut04 Settings
  1345. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1346. pub struct Nut04Settings {
  1347. pub methods: Vec<MintMethodSettings>,
  1348. pub disabled: bool,
  1349. }
  1350. impl From<cdk::nuts::nut04::Settings> for Nut04Settings {
  1351. fn from(s: cdk::nuts::nut04::Settings) -> Self {
  1352. Self {
  1353. methods: s.methods.into_iter().map(Into::into).collect(),
  1354. disabled: s.disabled,
  1355. }
  1356. }
  1357. }
  1358. impl TryFrom<Nut04Settings> for cdk::nuts::nut04::Settings {
  1359. type Error = FfiError;
  1360. fn try_from(s: Nut04Settings) -> Result<Self, Self::Error> {
  1361. Ok(Self {
  1362. methods: s
  1363. .methods
  1364. .into_iter()
  1365. .map(TryInto::try_into)
  1366. .collect::<Result<_, _>>()?,
  1367. disabled: s.disabled,
  1368. })
  1369. }
  1370. }
  1371. /// FFI-compatible MeltMethodSettings (NUT-05)
  1372. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1373. pub struct MeltMethodSettings {
  1374. pub method: PaymentMethod,
  1375. pub unit: CurrencyUnit,
  1376. pub min_amount: Option<Amount>,
  1377. pub max_amount: Option<Amount>,
  1378. /// For bolt11, whether mint supports amountless invoices
  1379. pub amountless: Option<bool>,
  1380. }
  1381. impl From<cdk::nuts::nut05::MeltMethodSettings> for MeltMethodSettings {
  1382. fn from(s: cdk::nuts::nut05::MeltMethodSettings) -> Self {
  1383. let amountless = match s.options {
  1384. Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless }) => Some(amountless),
  1385. _ => None,
  1386. };
  1387. Self {
  1388. method: s.method.into(),
  1389. unit: s.unit.into(),
  1390. min_amount: s.min_amount.map(Into::into),
  1391. max_amount: s.max_amount.map(Into::into),
  1392. amountless,
  1393. }
  1394. }
  1395. }
  1396. impl TryFrom<MeltMethodSettings> for cdk::nuts::nut05::MeltMethodSettings {
  1397. type Error = FfiError;
  1398. fn try_from(s: MeltMethodSettings) -> Result<Self, Self::Error> {
  1399. let options = match (s.method.clone(), s.amountless) {
  1400. (PaymentMethod::Bolt11, Some(amountless)) => {
  1401. Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless })
  1402. }
  1403. _ => None,
  1404. };
  1405. Ok(Self {
  1406. method: s.method.into(),
  1407. unit: s.unit.into(),
  1408. min_amount: s.min_amount.map(Into::into),
  1409. max_amount: s.max_amount.map(Into::into),
  1410. options,
  1411. })
  1412. }
  1413. }
  1414. /// FFI-compatible Nut05 Settings
  1415. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1416. pub struct Nut05Settings {
  1417. pub methods: Vec<MeltMethodSettings>,
  1418. pub disabled: bool,
  1419. }
  1420. impl From<cdk::nuts::nut05::Settings> for Nut05Settings {
  1421. fn from(s: cdk::nuts::nut05::Settings) -> Self {
  1422. Self {
  1423. methods: s.methods.into_iter().map(Into::into).collect(),
  1424. disabled: s.disabled,
  1425. }
  1426. }
  1427. }
  1428. impl TryFrom<Nut05Settings> for cdk::nuts::nut05::Settings {
  1429. type Error = FfiError;
  1430. fn try_from(s: Nut05Settings) -> Result<Self, Self::Error> {
  1431. Ok(Self {
  1432. methods: s
  1433. .methods
  1434. .into_iter()
  1435. .map(TryInto::try_into)
  1436. .collect::<Result<_, _>>()?,
  1437. disabled: s.disabled,
  1438. })
  1439. }
  1440. }
  1441. /// FFI-compatible ProtectedEndpoint (for auth nuts)
  1442. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1443. pub struct ProtectedEndpoint {
  1444. /// HTTP method (GET, POST, etc.)
  1445. pub method: String,
  1446. /// Endpoint path
  1447. pub path: String,
  1448. }
  1449. /// FFI-compatible ClearAuthSettings (NUT-21)
  1450. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1451. pub struct ClearAuthSettings {
  1452. /// OpenID Connect discovery URL
  1453. pub openid_discovery: String,
  1454. /// OAuth 2.0 client ID
  1455. pub client_id: String,
  1456. /// Protected endpoints requiring clear authentication
  1457. pub protected_endpoints: Vec<ProtectedEndpoint>,
  1458. }
  1459. /// FFI-compatible BlindAuthSettings (NUT-22)
  1460. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1461. pub struct BlindAuthSettings {
  1462. /// Maximum number of blind auth tokens that can be minted per request
  1463. pub bat_max_mint: u64,
  1464. /// Protected endpoints requiring blind authentication
  1465. pub protected_endpoints: Vec<ProtectedEndpoint>,
  1466. }
  1467. impl From<cdk::nuts::ClearAuthSettings> for ClearAuthSettings {
  1468. fn from(settings: cdk::nuts::ClearAuthSettings) -> Self {
  1469. Self {
  1470. openid_discovery: settings.openid_discovery,
  1471. client_id: settings.client_id,
  1472. protected_endpoints: settings
  1473. .protected_endpoints
  1474. .into_iter()
  1475. .map(Into::into)
  1476. .collect(),
  1477. }
  1478. }
  1479. }
  1480. impl TryFrom<ClearAuthSettings> for cdk::nuts::ClearAuthSettings {
  1481. type Error = FfiError;
  1482. fn try_from(settings: ClearAuthSettings) -> Result<Self, Self::Error> {
  1483. Ok(Self {
  1484. openid_discovery: settings.openid_discovery,
  1485. client_id: settings.client_id,
  1486. protected_endpoints: settings
  1487. .protected_endpoints
  1488. .into_iter()
  1489. .map(|e| e.try_into())
  1490. .collect::<Result<Vec<_>, _>>()?,
  1491. })
  1492. }
  1493. }
  1494. impl From<cdk::nuts::BlindAuthSettings> for BlindAuthSettings {
  1495. fn from(settings: cdk::nuts::BlindAuthSettings) -> Self {
  1496. Self {
  1497. bat_max_mint: settings.bat_max_mint,
  1498. protected_endpoints: settings
  1499. .protected_endpoints
  1500. .into_iter()
  1501. .map(Into::into)
  1502. .collect(),
  1503. }
  1504. }
  1505. }
  1506. impl TryFrom<BlindAuthSettings> for cdk::nuts::BlindAuthSettings {
  1507. type Error = FfiError;
  1508. fn try_from(settings: BlindAuthSettings) -> Result<Self, Self::Error> {
  1509. Ok(Self {
  1510. bat_max_mint: settings.bat_max_mint,
  1511. protected_endpoints: settings
  1512. .protected_endpoints
  1513. .into_iter()
  1514. .map(|e| e.try_into())
  1515. .collect::<Result<Vec<_>, _>>()?,
  1516. })
  1517. }
  1518. }
  1519. impl From<cdk::nuts::ProtectedEndpoint> for ProtectedEndpoint {
  1520. fn from(endpoint: cdk::nuts::ProtectedEndpoint) -> Self {
  1521. Self {
  1522. method: match endpoint.method {
  1523. cdk::nuts::Method::Get => "GET".to_string(),
  1524. cdk::nuts::Method::Post => "POST".to_string(),
  1525. },
  1526. path: endpoint.path.to_string(),
  1527. }
  1528. }
  1529. }
  1530. impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
  1531. type Error = FfiError;
  1532. fn try_from(endpoint: ProtectedEndpoint) -> Result<Self, Self::Error> {
  1533. let method = match endpoint.method.as_str() {
  1534. "GET" => cdk::nuts::Method::Get,
  1535. "POST" => cdk::nuts::Method::Post,
  1536. _ => {
  1537. return Err(FfiError::Generic {
  1538. msg: format!(
  1539. "Invalid HTTP method: {}. Only GET and POST are supported",
  1540. endpoint.method
  1541. ),
  1542. })
  1543. }
  1544. };
  1545. // Convert path string to RoutePath by matching against known paths
  1546. let route_path = match endpoint.path.as_str() {
  1547. "/v1/mint/quote/bolt11" => cdk::nuts::RoutePath::MintQuoteBolt11,
  1548. "/v1/mint/bolt11" => cdk::nuts::RoutePath::MintBolt11,
  1549. "/v1/melt/quote/bolt11" => cdk::nuts::RoutePath::MeltQuoteBolt11,
  1550. "/v1/melt/bolt11" => cdk::nuts::RoutePath::MeltBolt11,
  1551. "/v1/swap" => cdk::nuts::RoutePath::Swap,
  1552. "/v1/checkstate" => cdk::nuts::RoutePath::Checkstate,
  1553. "/v1/restore" => cdk::nuts::RoutePath::Restore,
  1554. "/v1/auth/blind/mint" => cdk::nuts::RoutePath::MintBlindAuth,
  1555. "/v1/mint/quote/bolt12" => cdk::nuts::RoutePath::MintQuoteBolt12,
  1556. "/v1/mint/bolt12" => cdk::nuts::RoutePath::MintBolt12,
  1557. "/v1/melt/quote/bolt12" => cdk::nuts::RoutePath::MeltQuoteBolt12,
  1558. "/v1/melt/bolt12" => cdk::nuts::RoutePath::MeltBolt12,
  1559. _ => {
  1560. return Err(FfiError::Generic {
  1561. msg: format!("Unknown route path: {}", endpoint.path),
  1562. })
  1563. }
  1564. };
  1565. Ok(cdk::nuts::ProtectedEndpoint::new(method, route_path))
  1566. }
  1567. }
  1568. /// FFI-compatible Nuts settings (extended to include NUT-04 and NUT-05 settings)
  1569. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1570. pub struct Nuts {
  1571. /// NUT04 Settings
  1572. pub nut04: Nut04Settings,
  1573. /// NUT05 Settings
  1574. pub nut05: Nut05Settings,
  1575. /// NUT07 Settings - Token state check
  1576. pub nut07_supported: bool,
  1577. /// NUT08 Settings - Lightning fee return
  1578. pub nut08_supported: bool,
  1579. /// NUT09 Settings - Restore signature
  1580. pub nut09_supported: bool,
  1581. /// NUT10 Settings - Spending conditions
  1582. pub nut10_supported: bool,
  1583. /// NUT11 Settings - Pay to Public Key Hash
  1584. pub nut11_supported: bool,
  1585. /// NUT12 Settings - DLEQ proofs
  1586. pub nut12_supported: bool,
  1587. /// NUT14 Settings - Hashed Time Locked Contracts
  1588. pub nut14_supported: bool,
  1589. /// NUT20 Settings - Web sockets
  1590. pub nut20_supported: bool,
  1591. /// NUT21 Settings - Clear authentication
  1592. pub nut21: Option<ClearAuthSettings>,
  1593. /// NUT22 Settings - Blind authentication
  1594. pub nut22: Option<BlindAuthSettings>,
  1595. /// Supported currency units for minting
  1596. pub mint_units: Vec<CurrencyUnit>,
  1597. /// Supported currency units for melting
  1598. pub melt_units: Vec<CurrencyUnit>,
  1599. }
  1600. impl From<cdk::nuts::Nuts> for Nuts {
  1601. fn from(nuts: cdk::nuts::Nuts) -> Self {
  1602. let mint_units = nuts
  1603. .supported_mint_units()
  1604. .into_iter()
  1605. .map(|u| u.clone().into())
  1606. .collect();
  1607. let melt_units = nuts
  1608. .supported_melt_units()
  1609. .into_iter()
  1610. .map(|u| u.clone().into())
  1611. .collect();
  1612. Self {
  1613. nut04: nuts.nut04.clone().into(),
  1614. nut05: nuts.nut05.clone().into(),
  1615. nut07_supported: nuts.nut07.supported,
  1616. nut08_supported: nuts.nut08.supported,
  1617. nut09_supported: nuts.nut09.supported,
  1618. nut10_supported: nuts.nut10.supported,
  1619. nut11_supported: nuts.nut11.supported,
  1620. nut12_supported: nuts.nut12.supported,
  1621. nut14_supported: nuts.nut14.supported,
  1622. nut20_supported: nuts.nut20.supported,
  1623. nut21: nuts.nut21.map(Into::into),
  1624. nut22: nuts.nut22.map(Into::into),
  1625. mint_units,
  1626. melt_units,
  1627. }
  1628. }
  1629. }
  1630. impl TryFrom<Nuts> for cdk::nuts::Nuts {
  1631. type Error = FfiError;
  1632. fn try_from(n: Nuts) -> Result<Self, Self::Error> {
  1633. Ok(Self {
  1634. nut04: n.nut04.try_into()?,
  1635. nut05: n.nut05.try_into()?,
  1636. nut07: cdk::nuts::nut06::SupportedSettings {
  1637. supported: n.nut07_supported,
  1638. },
  1639. nut08: cdk::nuts::nut06::SupportedSettings {
  1640. supported: n.nut08_supported,
  1641. },
  1642. nut09: cdk::nuts::nut06::SupportedSettings {
  1643. supported: n.nut09_supported,
  1644. },
  1645. nut10: cdk::nuts::nut06::SupportedSettings {
  1646. supported: n.nut10_supported,
  1647. },
  1648. nut11: cdk::nuts::nut06::SupportedSettings {
  1649. supported: n.nut11_supported,
  1650. },
  1651. nut12: cdk::nuts::nut06::SupportedSettings {
  1652. supported: n.nut12_supported,
  1653. },
  1654. nut14: cdk::nuts::nut06::SupportedSettings {
  1655. supported: n.nut14_supported,
  1656. },
  1657. nut15: Default::default(),
  1658. nut17: Default::default(),
  1659. nut19: Default::default(),
  1660. nut20: cdk::nuts::nut06::SupportedSettings {
  1661. supported: n.nut20_supported,
  1662. },
  1663. nut21: n.nut21.map(|s| s.try_into()).transpose()?,
  1664. nut22: n.nut22.map(|s| s.try_into()).transpose()?,
  1665. })
  1666. }
  1667. }
  1668. impl Nuts {
  1669. /// Convert Nuts to JSON string
  1670. pub fn to_json(&self) -> Result<String, FfiError> {
  1671. Ok(serde_json::to_string(self)?)
  1672. }
  1673. }
  1674. /// Decode Nuts from JSON string
  1675. #[uniffi::export]
  1676. pub fn decode_nuts(json: String) -> Result<Nuts, FfiError> {
  1677. Ok(serde_json::from_str(&json)?)
  1678. }
  1679. /// Encode Nuts to JSON string
  1680. #[uniffi::export]
  1681. pub fn encode_nuts(nuts: Nuts) -> Result<String, FfiError> {
  1682. Ok(serde_json::to_string(&nuts)?)
  1683. }
  1684. /// FFI-compatible MintInfo
  1685. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1686. pub struct MintInfo {
  1687. /// name of the mint and should be recognizable
  1688. pub name: Option<String>,
  1689. /// hex pubkey of the mint
  1690. pub pubkey: Option<String>,
  1691. /// implementation name and the version running
  1692. pub version: Option<MintVersion>,
  1693. /// short description of the mint
  1694. pub description: Option<String>,
  1695. /// long description
  1696. pub description_long: Option<String>,
  1697. /// Contact info
  1698. pub contact: Option<Vec<ContactInfo>>,
  1699. /// shows which NUTs the mint supports
  1700. pub nuts: Nuts,
  1701. /// Mint's icon URL
  1702. pub icon_url: Option<String>,
  1703. /// Mint's endpoint URLs
  1704. pub urls: Option<Vec<String>>,
  1705. /// message of the day that the wallet must display to the user
  1706. pub motd: Option<String>,
  1707. /// server unix timestamp
  1708. pub time: Option<u64>,
  1709. /// terms of url service of the mint
  1710. pub tos_url: Option<String>,
  1711. }
  1712. impl From<cdk::nuts::MintInfo> for MintInfo {
  1713. fn from(info: cdk::nuts::MintInfo) -> Self {
  1714. Self {
  1715. name: info.name,
  1716. pubkey: info.pubkey.map(|p| p.to_string()),
  1717. version: info.version.map(Into::into),
  1718. description: info.description,
  1719. description_long: info.description_long,
  1720. contact: info
  1721. .contact
  1722. .map(|contacts| contacts.into_iter().map(Into::into).collect()),
  1723. nuts: info.nuts.into(),
  1724. icon_url: info.icon_url,
  1725. urls: info.urls,
  1726. motd: info.motd,
  1727. time: info.time,
  1728. tos_url: info.tos_url,
  1729. }
  1730. }
  1731. }
  1732. impl From<MintInfo> for cdk::nuts::MintInfo {
  1733. fn from(info: MintInfo) -> Self {
  1734. // Convert FFI Nuts back to cdk::nuts::Nuts (best-effort)
  1735. let nuts_cdk: cdk::nuts::Nuts = info.nuts.clone().try_into().unwrap_or_default();
  1736. Self {
  1737. name: info.name,
  1738. pubkey: info.pubkey.and_then(|p| p.parse().ok()),
  1739. version: info.version.map(Into::into),
  1740. description: info.description,
  1741. description_long: info.description_long,
  1742. contact: info
  1743. .contact
  1744. .map(|contacts| contacts.into_iter().map(Into::into).collect()),
  1745. nuts: nuts_cdk,
  1746. icon_url: info.icon_url,
  1747. urls: info.urls,
  1748. motd: info.motd,
  1749. time: info.time,
  1750. tos_url: info.tos_url,
  1751. }
  1752. }
  1753. }
  1754. impl MintInfo {
  1755. /// Convert MintInfo to JSON string
  1756. pub fn to_json(&self) -> Result<String, FfiError> {
  1757. Ok(serde_json::to_string(self)?)
  1758. }
  1759. }
  1760. /// Decode MintInfo from JSON string
  1761. #[uniffi::export]
  1762. pub fn decode_mint_info(json: String) -> Result<MintInfo, FfiError> {
  1763. Ok(serde_json::from_str(&json)?)
  1764. }
  1765. /// Encode MintInfo to JSON string
  1766. #[uniffi::export]
  1767. pub fn encode_mint_info(info: MintInfo) -> Result<String, FfiError> {
  1768. Ok(serde_json::to_string(&info)?)
  1769. }
  1770. /// FFI-compatible Conditions (for spending conditions)
  1771. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1772. pub struct Conditions {
  1773. /// Unix locktime after which refund keys can be used
  1774. pub locktime: Option<u64>,
  1775. /// Additional Public keys (as hex strings)
  1776. pub pubkeys: Vec<String>,
  1777. /// Refund keys (as hex strings)
  1778. pub refund_keys: Vec<String>,
  1779. /// Number of signatures required (default 1)
  1780. pub num_sigs: Option<u64>,
  1781. /// Signature flag (0 = SigInputs, 1 = SigAll)
  1782. pub sig_flag: u8,
  1783. /// Number of refund signatures required (default 1)
  1784. pub num_sigs_refund: Option<u64>,
  1785. }
  1786. impl From<cdk::nuts::nut11::Conditions> for Conditions {
  1787. fn from(conditions: cdk::nuts::nut11::Conditions) -> Self {
  1788. Self {
  1789. locktime: conditions.locktime,
  1790. pubkeys: conditions
  1791. .pubkeys
  1792. .unwrap_or_default()
  1793. .into_iter()
  1794. .map(|p| p.to_string())
  1795. .collect(),
  1796. refund_keys: conditions
  1797. .refund_keys
  1798. .unwrap_or_default()
  1799. .into_iter()
  1800. .map(|p| p.to_string())
  1801. .collect(),
  1802. num_sigs: conditions.num_sigs,
  1803. sig_flag: match conditions.sig_flag {
  1804. cdk::nuts::nut11::SigFlag::SigInputs => 0,
  1805. cdk::nuts::nut11::SigFlag::SigAll => 1,
  1806. },
  1807. num_sigs_refund: conditions.num_sigs_refund,
  1808. }
  1809. }
  1810. }
  1811. impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
  1812. type Error = FfiError;
  1813. fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
  1814. let pubkeys = if conditions.pubkeys.is_empty() {
  1815. None
  1816. } else {
  1817. Some(
  1818. conditions
  1819. .pubkeys
  1820. .into_iter()
  1821. .map(|s| {
  1822. s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
  1823. msg: format!("Invalid pubkey: {}", e),
  1824. })
  1825. })
  1826. .collect::<Result<Vec<_>, _>>()?,
  1827. )
  1828. };
  1829. let refund_keys = if conditions.refund_keys.is_empty() {
  1830. None
  1831. } else {
  1832. Some(
  1833. conditions
  1834. .refund_keys
  1835. .into_iter()
  1836. .map(|s| {
  1837. s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
  1838. msg: format!("Invalid refund key: {}", e),
  1839. })
  1840. })
  1841. .collect::<Result<Vec<_>, _>>()?,
  1842. )
  1843. };
  1844. let sig_flag = match conditions.sig_flag {
  1845. 0 => cdk::nuts::nut11::SigFlag::SigInputs,
  1846. 1 => cdk::nuts::nut11::SigFlag::SigAll,
  1847. _ => {
  1848. return Err(FfiError::Generic {
  1849. msg: "Invalid sig_flag value".to_string(),
  1850. })
  1851. }
  1852. };
  1853. Ok(Self {
  1854. locktime: conditions.locktime,
  1855. pubkeys,
  1856. refund_keys,
  1857. num_sigs: conditions.num_sigs,
  1858. sig_flag,
  1859. num_sigs_refund: conditions.num_sigs_refund,
  1860. })
  1861. }
  1862. }
  1863. impl Conditions {
  1864. /// Convert Conditions to JSON string
  1865. pub fn to_json(&self) -> Result<String, FfiError> {
  1866. Ok(serde_json::to_string(self)?)
  1867. }
  1868. }
  1869. /// Decode Conditions from JSON string
  1870. #[uniffi::export]
  1871. pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
  1872. Ok(serde_json::from_str(&json)?)
  1873. }
  1874. /// Encode Conditions to JSON string
  1875. #[uniffi::export]
  1876. pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
  1877. Ok(serde_json::to_string(&conditions)?)
  1878. }
  1879. /// FFI-compatible Witness
  1880. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
  1881. pub enum Witness {
  1882. /// P2PK Witness
  1883. P2PK {
  1884. /// Signatures
  1885. signatures: Vec<String>,
  1886. },
  1887. /// HTLC Witness
  1888. HTLC {
  1889. /// Preimage
  1890. preimage: String,
  1891. /// Optional signatures
  1892. signatures: Option<Vec<String>>,
  1893. },
  1894. }
  1895. impl From<cdk::nuts::Witness> for Witness {
  1896. fn from(witness: cdk::nuts::Witness) -> Self {
  1897. match witness {
  1898. cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
  1899. signatures: p2pk.signatures,
  1900. },
  1901. cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
  1902. preimage: htlc.preimage,
  1903. signatures: htlc.signatures,
  1904. },
  1905. }
  1906. }
  1907. }
  1908. impl From<Witness> for cdk::nuts::Witness {
  1909. fn from(witness: Witness) -> Self {
  1910. match witness {
  1911. Witness::P2PK { signatures } => {
  1912. Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
  1913. }
  1914. Witness::HTLC {
  1915. preimage,
  1916. signatures,
  1917. } => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
  1918. preimage,
  1919. signatures,
  1920. }),
  1921. }
  1922. }
  1923. }
  1924. /// FFI-compatible SpendingConditions
  1925. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
  1926. pub enum SpendingConditions {
  1927. /// P2PK (Pay to Public Key) conditions
  1928. P2PK {
  1929. /// The public key (as hex string)
  1930. pubkey: String,
  1931. /// Additional conditions
  1932. conditions: Option<Conditions>,
  1933. },
  1934. /// HTLC (Hash Time Locked Contract) conditions
  1935. HTLC {
  1936. /// Hash of the preimage (as hex string)
  1937. hash: String,
  1938. /// Additional conditions
  1939. conditions: Option<Conditions>,
  1940. },
  1941. }
  1942. impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
  1943. fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
  1944. match spending_conditions {
  1945. cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
  1946. pubkey: data.to_string(),
  1947. conditions: conditions.map(Into::into),
  1948. },
  1949. cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
  1950. hash: data.to_string(),
  1951. conditions: conditions.map(Into::into),
  1952. },
  1953. }
  1954. }
  1955. }
  1956. /// FFI-compatible Transaction
  1957. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  1958. pub struct Transaction {
  1959. /// Transaction ID
  1960. pub id: TransactionId,
  1961. /// Mint URL
  1962. pub mint_url: MintUrl,
  1963. /// Transaction direction
  1964. pub direction: TransactionDirection,
  1965. /// Amount
  1966. pub amount: Amount,
  1967. /// Fee
  1968. pub fee: Amount,
  1969. /// Currency Unit
  1970. pub unit: CurrencyUnit,
  1971. /// Proof Ys (Y values from proofs)
  1972. pub ys: Vec<PublicKey>,
  1973. /// Unix timestamp
  1974. pub timestamp: u64,
  1975. /// Memo
  1976. pub memo: Option<String>,
  1977. /// User-defined metadata
  1978. pub metadata: HashMap<String, String>,
  1979. /// Quote ID if this is a mint or melt transaction
  1980. pub quote_id: Option<String>,
  1981. }
  1982. impl From<cdk::wallet::types::Transaction> for Transaction {
  1983. fn from(tx: cdk::wallet::types::Transaction) -> Self {
  1984. Self {
  1985. id: tx.id().into(),
  1986. mint_url: tx.mint_url.into(),
  1987. direction: tx.direction.into(),
  1988. amount: tx.amount.into(),
  1989. fee: tx.fee.into(),
  1990. unit: tx.unit.into(),
  1991. ys: tx.ys.into_iter().map(Into::into).collect(),
  1992. timestamp: tx.timestamp,
  1993. memo: tx.memo,
  1994. metadata: tx.metadata,
  1995. quote_id: tx.quote_id,
  1996. }
  1997. }
  1998. }
  1999. /// Convert FFI Transaction to CDK Transaction
  2000. impl TryFrom<Transaction> for cdk::wallet::types::Transaction {
  2001. type Error = FfiError;
  2002. fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
  2003. let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, _> =
  2004. tx.ys.into_iter().map(|pk| pk.try_into()).collect();
  2005. let cdk_ys = cdk_ys?;
  2006. Ok(Self {
  2007. mint_url: tx.mint_url.try_into()?,
  2008. direction: tx.direction.into(),
  2009. amount: tx.amount.into(),
  2010. fee: tx.fee.into(),
  2011. unit: tx.unit.into(),
  2012. ys: cdk_ys,
  2013. timestamp: tx.timestamp,
  2014. memo: tx.memo,
  2015. metadata: tx.metadata,
  2016. quote_id: tx.quote_id,
  2017. })
  2018. }
  2019. }
  2020. impl Transaction {
  2021. /// Convert Transaction to JSON string
  2022. pub fn to_json(&self) -> Result<String, FfiError> {
  2023. Ok(serde_json::to_string(self)?)
  2024. }
  2025. }
  2026. /// Decode Transaction from JSON string
  2027. #[uniffi::export]
  2028. pub fn decode_transaction(json: String) -> Result<Transaction, FfiError> {
  2029. Ok(serde_json::from_str(&json)?)
  2030. }
  2031. /// Encode Transaction to JSON string
  2032. #[uniffi::export]
  2033. pub fn encode_transaction(transaction: Transaction) -> Result<String, FfiError> {
  2034. Ok(serde_json::to_string(&transaction)?)
  2035. }
  2036. /// FFI-compatible TransactionDirection
  2037. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
  2038. pub enum TransactionDirection {
  2039. /// Incoming transaction (i.e., receive or mint)
  2040. Incoming,
  2041. /// Outgoing transaction (i.e., send or melt)
  2042. Outgoing,
  2043. }
  2044. impl From<cdk::wallet::types::TransactionDirection> for TransactionDirection {
  2045. fn from(direction: cdk::wallet::types::TransactionDirection) -> Self {
  2046. match direction {
  2047. cdk::wallet::types::TransactionDirection::Incoming => TransactionDirection::Incoming,
  2048. cdk::wallet::types::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
  2049. }
  2050. }
  2051. }
  2052. impl From<TransactionDirection> for cdk::wallet::types::TransactionDirection {
  2053. fn from(direction: TransactionDirection) -> Self {
  2054. match direction {
  2055. TransactionDirection::Incoming => cdk::wallet::types::TransactionDirection::Incoming,
  2056. TransactionDirection::Outgoing => cdk::wallet::types::TransactionDirection::Outgoing,
  2057. }
  2058. }
  2059. }
  2060. /// FFI-compatible TransactionId
  2061. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2062. #[serde(transparent)]
  2063. pub struct TransactionId {
  2064. /// Hex-encoded transaction ID (64 characters)
  2065. pub hex: String,
  2066. }
  2067. impl TransactionId {
  2068. /// Create a new TransactionId from hex string
  2069. pub fn from_hex(hex: String) -> Result<Self, FfiError> {
  2070. // Validate hex string length (should be 64 characters for 32 bytes)
  2071. if hex.len() != 64 {
  2072. return Err(FfiError::InvalidHex {
  2073. msg: "Transaction ID hex must be exactly 64 characters (32 bytes)".to_string(),
  2074. });
  2075. }
  2076. // Validate hex format
  2077. if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
  2078. return Err(FfiError::InvalidHex {
  2079. msg: "Transaction ID hex contains invalid characters".to_string(),
  2080. });
  2081. }
  2082. Ok(Self { hex })
  2083. }
  2084. /// Create from proofs
  2085. pub fn from_proofs(proofs: &Proofs) -> Result<Self, FfiError> {
  2086. let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
  2087. let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?;
  2088. Ok(Self {
  2089. hex: id.to_string(),
  2090. })
  2091. }
  2092. }
  2093. impl From<cdk::wallet::types::TransactionId> for TransactionId {
  2094. fn from(id: cdk::wallet::types::TransactionId) -> Self {
  2095. Self {
  2096. hex: id.to_string(),
  2097. }
  2098. }
  2099. }
  2100. impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
  2101. type Error = FfiError;
  2102. fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
  2103. cdk::wallet::types::TransactionId::from_hex(&id.hex)
  2104. .map_err(|e| FfiError::InvalidHex { msg: e.to_string() })
  2105. }
  2106. }
  2107. /// FFI-compatible AuthProof
  2108. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2109. pub struct AuthProof {
  2110. /// Keyset ID
  2111. pub keyset_id: String,
  2112. /// Secret message
  2113. pub secret: String,
  2114. /// Unblinded signature (C)
  2115. pub c: String,
  2116. /// Y value (hash_to_curve of secret)
  2117. pub y: String,
  2118. }
  2119. impl From<cdk::nuts::AuthProof> for AuthProof {
  2120. fn from(auth_proof: cdk::nuts::AuthProof) -> Self {
  2121. Self {
  2122. keyset_id: auth_proof.keyset_id.to_string(),
  2123. secret: auth_proof.secret.to_string(),
  2124. c: auth_proof.c.to_string(),
  2125. y: auth_proof
  2126. .y()
  2127. .map(|y| y.to_string())
  2128. .unwrap_or_else(|_| "".to_string()),
  2129. }
  2130. }
  2131. }
  2132. impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
  2133. type Error = FfiError;
  2134. fn try_from(auth_proof: AuthProof) -> Result<Self, Self::Error> {
  2135. use std::str::FromStr;
  2136. Ok(Self {
  2137. keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
  2138. .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
  2139. secret: {
  2140. use std::str::FromStr;
  2141. cdk::secret::Secret::from_str(&auth_proof.secret)
  2142. .map_err(|e| FfiError::Serialization { msg: e.to_string() })?
  2143. },
  2144. c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
  2145. .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
  2146. dleq: None, // FFI doesn't expose DLEQ proofs for simplicity
  2147. })
  2148. }
  2149. }
  2150. impl AuthProof {
  2151. /// Convert AuthProof to JSON string
  2152. pub fn to_json(&self) -> Result<String, FfiError> {
  2153. Ok(serde_json::to_string(self)?)
  2154. }
  2155. }
  2156. /// Decode AuthProof from JSON string
  2157. #[uniffi::export]
  2158. pub fn decode_auth_proof(json: String) -> Result<AuthProof, FfiError> {
  2159. Ok(serde_json::from_str(&json)?)
  2160. }
  2161. /// Encode AuthProof to JSON string
  2162. #[uniffi::export]
  2163. pub fn encode_auth_proof(proof: AuthProof) -> Result<String, FfiError> {
  2164. Ok(serde_json::to_string(&proof)?)
  2165. }
  2166. impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
  2167. type Error = FfiError;
  2168. fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
  2169. match spending_conditions {
  2170. SpendingConditions::P2PK { pubkey, conditions } => {
  2171. let pubkey = pubkey
  2172. .parse()
  2173. .map_err(|e| FfiError::InvalidCryptographicKey {
  2174. msg: format!("Invalid pubkey: {}", e),
  2175. })?;
  2176. let conditions = conditions.map(|c| c.try_into()).transpose()?;
  2177. Ok(Self::P2PKConditions {
  2178. data: pubkey,
  2179. conditions,
  2180. })
  2181. }
  2182. SpendingConditions::HTLC { hash, conditions } => {
  2183. let hash = hash
  2184. .parse()
  2185. .map_err(|e| FfiError::InvalidCryptographicKey {
  2186. msg: format!("Invalid hash: {}", e),
  2187. })?;
  2188. let conditions = conditions.map(|c| c.try_into()).transpose()?;
  2189. Ok(Self::HTLCConditions {
  2190. data: hash,
  2191. conditions,
  2192. })
  2193. }
  2194. }
  2195. }
  2196. }
  2197. /// FFI-compatible SubscriptionKind
  2198. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
  2199. pub enum SubscriptionKind {
  2200. /// Bolt 11 Melt Quote
  2201. Bolt11MeltQuote,
  2202. /// Bolt 11 Mint Quote
  2203. Bolt11MintQuote,
  2204. /// Bolt 12 Mint Quote
  2205. Bolt12MintQuote,
  2206. /// Proof State
  2207. ProofState,
  2208. }
  2209. impl From<SubscriptionKind> for cdk::nuts::nut17::Kind {
  2210. fn from(kind: SubscriptionKind) -> Self {
  2211. match kind {
  2212. SubscriptionKind::Bolt11MeltQuote => cdk::nuts::nut17::Kind::Bolt11MeltQuote,
  2213. SubscriptionKind::Bolt11MintQuote => cdk::nuts::nut17::Kind::Bolt11MintQuote,
  2214. SubscriptionKind::Bolt12MintQuote => cdk::nuts::nut17::Kind::Bolt12MintQuote,
  2215. SubscriptionKind::ProofState => cdk::nuts::nut17::Kind::ProofState,
  2216. }
  2217. }
  2218. }
  2219. impl From<cdk::nuts::nut17::Kind> for SubscriptionKind {
  2220. fn from(kind: cdk::nuts::nut17::Kind) -> Self {
  2221. match kind {
  2222. cdk::nuts::nut17::Kind::Bolt11MeltQuote => SubscriptionKind::Bolt11MeltQuote,
  2223. cdk::nuts::nut17::Kind::Bolt11MintQuote => SubscriptionKind::Bolt11MintQuote,
  2224. cdk::nuts::nut17::Kind::Bolt12MintQuote => SubscriptionKind::Bolt12MintQuote,
  2225. cdk::nuts::nut17::Kind::ProofState => SubscriptionKind::ProofState,
  2226. }
  2227. }
  2228. }
  2229. /// FFI-compatible SubscribeParams
  2230. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2231. pub struct SubscribeParams {
  2232. /// Subscription kind
  2233. pub kind: SubscriptionKind,
  2234. /// Filters
  2235. pub filters: Vec<String>,
  2236. /// Subscription ID (optional, will be generated if not provided)
  2237. pub id: Option<String>,
  2238. }
  2239. impl From<SubscribeParams> for cdk::nuts::nut17::Params<cdk::pub_sub::SubId> {
  2240. fn from(params: SubscribeParams) -> Self {
  2241. let sub_id = params
  2242. .id
  2243. .map(|id| SubId::from(id.as_str()))
  2244. .unwrap_or_else(|| {
  2245. // Generate a random ID
  2246. let uuid = uuid::Uuid::new_v4();
  2247. SubId::from(uuid.to_string().as_str())
  2248. });
  2249. cdk::nuts::nut17::Params {
  2250. kind: params.kind.into(),
  2251. filters: params.filters,
  2252. id: sub_id,
  2253. }
  2254. }
  2255. }
  2256. impl SubscribeParams {
  2257. /// Convert SubscribeParams to JSON string
  2258. pub fn to_json(&self) -> Result<String, FfiError> {
  2259. Ok(serde_json::to_string(self)?)
  2260. }
  2261. }
  2262. /// Decode SubscribeParams from JSON string
  2263. #[uniffi::export]
  2264. pub fn decode_subscribe_params(json: String) -> Result<SubscribeParams, FfiError> {
  2265. Ok(serde_json::from_str(&json)?)
  2266. }
  2267. /// Encode SubscribeParams to JSON string
  2268. #[uniffi::export]
  2269. pub fn encode_subscribe_params(params: SubscribeParams) -> Result<String, FfiError> {
  2270. Ok(serde_json::to_string(&params)?)
  2271. }
  2272. /// FFI-compatible ActiveSubscription
  2273. #[derive(uniffi::Object)]
  2274. pub struct ActiveSubscription {
  2275. inner: std::sync::Arc<tokio::sync::Mutex<cdk::wallet::subscription::ActiveSubscription>>,
  2276. pub sub_id: String,
  2277. }
  2278. impl ActiveSubscription {
  2279. pub(crate) fn new(
  2280. inner: cdk::wallet::subscription::ActiveSubscription,
  2281. sub_id: String,
  2282. ) -> Self {
  2283. Self {
  2284. inner: std::sync::Arc::new(tokio::sync::Mutex::new(inner)),
  2285. sub_id,
  2286. }
  2287. }
  2288. }
  2289. #[uniffi::export(async_runtime = "tokio")]
  2290. impl ActiveSubscription {
  2291. /// Get the subscription ID
  2292. pub fn id(&self) -> String {
  2293. self.sub_id.clone()
  2294. }
  2295. /// Receive the next notification
  2296. pub async fn recv(&self) -> Result<NotificationPayload, FfiError> {
  2297. let mut guard = self.inner.lock().await;
  2298. guard
  2299. .recv()
  2300. .await
  2301. .ok_or(FfiError::Generic {
  2302. msg: "Subscription closed".to_string(),
  2303. })
  2304. .map(Into::into)
  2305. }
  2306. /// Try to receive a notification without blocking
  2307. pub async fn try_recv(&self) -> Result<Option<NotificationPayload>, FfiError> {
  2308. let mut guard = self.inner.lock().await;
  2309. guard
  2310. .try_recv()
  2311. .map(|opt| opt.map(Into::into))
  2312. .map_err(|e| FfiError::Generic {
  2313. msg: format!("Failed to receive notification: {}", e),
  2314. })
  2315. }
  2316. }
  2317. /// FFI-compatible NotificationPayload
  2318. #[derive(Debug, Clone, uniffi::Enum)]
  2319. pub enum NotificationPayload {
  2320. /// Proof state update
  2321. ProofState { proof_states: Vec<ProofStateUpdate> },
  2322. /// Mint quote update
  2323. MintQuoteUpdate {
  2324. quote: std::sync::Arc<MintQuoteBolt11Response>,
  2325. },
  2326. /// Melt quote update
  2327. MeltQuoteUpdate {
  2328. quote: std::sync::Arc<MeltQuoteBolt11Response>,
  2329. },
  2330. }
  2331. impl From<cdk::nuts::NotificationPayload<String>> for NotificationPayload {
  2332. fn from(payload: cdk::nuts::NotificationPayload<String>) -> Self {
  2333. match payload {
  2334. cdk::nuts::NotificationPayload::ProofState(states) => NotificationPayload::ProofState {
  2335. proof_states: vec![states.into()],
  2336. },
  2337. cdk::nuts::NotificationPayload::MintQuoteBolt11Response(quote_resp) => {
  2338. NotificationPayload::MintQuoteUpdate {
  2339. quote: std::sync::Arc::new(quote_resp.into()),
  2340. }
  2341. }
  2342. cdk::nuts::NotificationPayload::MeltQuoteBolt11Response(quote_resp) => {
  2343. NotificationPayload::MeltQuoteUpdate {
  2344. quote: std::sync::Arc::new(quote_resp.into()),
  2345. }
  2346. }
  2347. _ => {
  2348. // For now, handle other notification types as empty ProofState
  2349. NotificationPayload::ProofState {
  2350. proof_states: vec![],
  2351. }
  2352. }
  2353. }
  2354. }
  2355. }
  2356. /// FFI-compatible ProofStateUpdate
  2357. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2358. pub struct ProofStateUpdate {
  2359. /// Y value (hash_to_curve of secret)
  2360. pub y: String,
  2361. /// Current state
  2362. pub state: ProofState,
  2363. /// Optional witness data
  2364. pub witness: Option<String>,
  2365. }
  2366. impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
  2367. fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
  2368. Self {
  2369. y: proof_state.y.to_string(),
  2370. state: proof_state.state.into(),
  2371. witness: proof_state.witness.map(|w| format!("{:?}", w)),
  2372. }
  2373. }
  2374. }
  2375. impl ProofStateUpdate {
  2376. /// Convert ProofStateUpdate to JSON string
  2377. pub fn to_json(&self) -> Result<String, FfiError> {
  2378. Ok(serde_json::to_string(self)?)
  2379. }
  2380. }
  2381. /// Decode ProofStateUpdate from JSON string
  2382. #[uniffi::export]
  2383. pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
  2384. Ok(serde_json::from_str(&json)?)
  2385. }
  2386. /// Encode ProofStateUpdate to JSON string
  2387. #[uniffi::export]
  2388. pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
  2389. Ok(serde_json::to_string(&update)?)
  2390. }
  2391. /// FFI-compatible KeySetInfo
  2392. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2393. pub struct KeySetInfo {
  2394. pub id: String,
  2395. pub unit: CurrencyUnit,
  2396. pub active: bool,
  2397. /// Input fee per thousand (ppk)
  2398. pub input_fee_ppk: u64,
  2399. }
  2400. impl From<cdk::nuts::KeySetInfo> for KeySetInfo {
  2401. fn from(keyset: cdk::nuts::KeySetInfo) -> Self {
  2402. Self {
  2403. id: keyset.id.to_string(),
  2404. unit: keyset.unit.into(),
  2405. active: keyset.active,
  2406. input_fee_ppk: keyset.input_fee_ppk,
  2407. }
  2408. }
  2409. }
  2410. impl From<KeySetInfo> for cdk::nuts::KeySetInfo {
  2411. fn from(keyset: KeySetInfo) -> Self {
  2412. use std::str::FromStr;
  2413. Self {
  2414. id: cdk::nuts::Id::from_str(&keyset.id).unwrap(),
  2415. unit: keyset.unit.into(),
  2416. active: keyset.active,
  2417. final_expiry: None,
  2418. input_fee_ppk: keyset.input_fee_ppk,
  2419. }
  2420. }
  2421. }
  2422. impl KeySetInfo {
  2423. /// Convert KeySetInfo to JSON string
  2424. pub fn to_json(&self) -> Result<String, FfiError> {
  2425. Ok(serde_json::to_string(self)?)
  2426. }
  2427. }
  2428. /// Decode KeySetInfo from JSON string
  2429. #[uniffi::export]
  2430. pub fn decode_key_set_info(json: String) -> Result<KeySetInfo, FfiError> {
  2431. Ok(serde_json::from_str(&json)?)
  2432. }
  2433. /// Encode KeySetInfo to JSON string
  2434. #[uniffi::export]
  2435. pub fn encode_key_set_info(info: KeySetInfo) -> Result<String, FfiError> {
  2436. Ok(serde_json::to_string(&info)?)
  2437. }
  2438. /// FFI-compatible PublicKey
  2439. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2440. #[serde(transparent)]
  2441. pub struct PublicKey {
  2442. /// Hex-encoded public key
  2443. pub hex: String,
  2444. }
  2445. impl From<cdk::nuts::PublicKey> for PublicKey {
  2446. fn from(key: cdk::nuts::PublicKey) -> Self {
  2447. Self {
  2448. hex: key.to_string(),
  2449. }
  2450. }
  2451. }
  2452. impl TryFrom<PublicKey> for cdk::nuts::PublicKey {
  2453. type Error = FfiError;
  2454. fn try_from(key: PublicKey) -> Result<Self, Self::Error> {
  2455. key.hex
  2456. .parse()
  2457. .map_err(|e| FfiError::InvalidCryptographicKey {
  2458. msg: format!("Invalid public key: {}", e),
  2459. })
  2460. }
  2461. }
  2462. /// FFI-compatible Keys (simplified - contains only essential info)
  2463. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2464. pub struct Keys {
  2465. /// Keyset ID
  2466. pub id: String,
  2467. /// Currency unit
  2468. pub unit: CurrencyUnit,
  2469. /// Map of amount to public key hex (simplified from BTreeMap)
  2470. pub keys: HashMap<u64, String>,
  2471. }
  2472. impl From<cdk::nuts::Keys> for Keys {
  2473. fn from(keys: cdk::nuts::Keys) -> Self {
  2474. // Keys doesn't have id and unit - we'll need to get these from context
  2475. // For now, use placeholder values
  2476. Self {
  2477. id: "unknown".to_string(), // This should come from KeySet
  2478. unit: CurrencyUnit::Sat, // This should come from KeySet
  2479. keys: keys
  2480. .keys()
  2481. .iter()
  2482. .map(|(amount, pubkey)| (u64::from(*amount), pubkey.to_string()))
  2483. .collect(),
  2484. }
  2485. }
  2486. }
  2487. impl TryFrom<Keys> for cdk::nuts::Keys {
  2488. type Error = FfiError;
  2489. fn try_from(keys: Keys) -> Result<Self, Self::Error> {
  2490. use std::collections::BTreeMap;
  2491. use std::str::FromStr;
  2492. // Convert the HashMap to BTreeMap with proper types
  2493. let mut keys_map = BTreeMap::new();
  2494. for (amount_u64, pubkey_hex) in keys.keys {
  2495. let amount = cdk::Amount::from(amount_u64);
  2496. let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
  2497. .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
  2498. keys_map.insert(amount, pubkey);
  2499. }
  2500. Ok(cdk::nuts::Keys::new(keys_map))
  2501. }
  2502. }
  2503. impl Keys {
  2504. /// Convert Keys to JSON string
  2505. pub fn to_json(&self) -> Result<String, FfiError> {
  2506. Ok(serde_json::to_string(self)?)
  2507. }
  2508. }
  2509. /// Decode Keys from JSON string
  2510. #[uniffi::export]
  2511. pub fn decode_keys(json: String) -> Result<Keys, FfiError> {
  2512. Ok(serde_json::from_str(&json)?)
  2513. }
  2514. /// Encode Keys to JSON string
  2515. #[uniffi::export]
  2516. pub fn encode_keys(keys: Keys) -> Result<String, FfiError> {
  2517. Ok(serde_json::to_string(&keys)?)
  2518. }
  2519. /// FFI-compatible KeySet
  2520. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2521. pub struct KeySet {
  2522. /// Keyset ID
  2523. pub id: String,
  2524. /// Currency unit
  2525. pub unit: CurrencyUnit,
  2526. /// The keys (map of amount to public key hex)
  2527. pub keys: HashMap<u64, String>,
  2528. /// Optional expiry timestamp
  2529. pub final_expiry: Option<u64>,
  2530. }
  2531. impl From<cdk::nuts::KeySet> for KeySet {
  2532. fn from(keyset: cdk::nuts::KeySet) -> Self {
  2533. Self {
  2534. id: keyset.id.to_string(),
  2535. unit: keyset.unit.into(),
  2536. keys: keyset
  2537. .keys
  2538. .keys()
  2539. .iter()
  2540. .map(|(amount, pubkey)| (u64::from(*amount), pubkey.to_string()))
  2541. .collect(),
  2542. final_expiry: keyset.final_expiry,
  2543. }
  2544. }
  2545. }
  2546. impl TryFrom<KeySet> for cdk::nuts::KeySet {
  2547. type Error = FfiError;
  2548. fn try_from(keyset: KeySet) -> Result<Self, Self::Error> {
  2549. use std::collections::BTreeMap;
  2550. use std::str::FromStr;
  2551. // Convert id
  2552. let id = cdk::nuts::Id::from_str(&keyset.id)
  2553. .map_err(|e| FfiError::Serialization { msg: e.to_string() })?;
  2554. // Convert unit
  2555. let unit: cdk::nuts::CurrencyUnit = keyset.unit.into();
  2556. // Convert keys
  2557. let mut keys_map = BTreeMap::new();
  2558. for (amount_u64, pubkey_hex) in keyset.keys {
  2559. let amount = cdk::Amount::from(amount_u64);
  2560. let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
  2561. .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
  2562. keys_map.insert(amount, pubkey);
  2563. }
  2564. let keys = cdk::nuts::Keys::new(keys_map);
  2565. Ok(cdk::nuts::KeySet {
  2566. id,
  2567. unit,
  2568. keys,
  2569. final_expiry: keyset.final_expiry,
  2570. })
  2571. }
  2572. }
  2573. impl KeySet {
  2574. /// Convert KeySet to JSON string
  2575. pub fn to_json(&self) -> Result<String, FfiError> {
  2576. Ok(serde_json::to_string(self)?)
  2577. }
  2578. }
  2579. /// Decode KeySet from JSON string
  2580. #[uniffi::export]
  2581. pub fn decode_key_set(json: String) -> Result<KeySet, FfiError> {
  2582. Ok(serde_json::from_str(&json)?)
  2583. }
  2584. /// Encode KeySet to JSON string
  2585. #[uniffi::export]
  2586. pub fn encode_key_set(keyset: KeySet) -> Result<String, FfiError> {
  2587. Ok(serde_json::to_string(&keyset)?)
  2588. }
  2589. /// FFI-compatible ProofInfo
  2590. #[derive(Debug, Clone, uniffi::Record)]
  2591. pub struct ProofInfo {
  2592. /// Proof
  2593. pub proof: std::sync::Arc<Proof>,
  2594. /// Y value (hash_to_curve of secret)
  2595. pub y: PublicKey,
  2596. /// Mint URL
  2597. pub mint_url: MintUrl,
  2598. /// Proof state
  2599. pub state: ProofState,
  2600. /// Proof Spending Conditions
  2601. pub spending_condition: Option<SpendingConditions>,
  2602. /// Currency unit
  2603. pub unit: CurrencyUnit,
  2604. }
  2605. impl From<cdk::types::ProofInfo> for ProofInfo {
  2606. fn from(info: cdk::types::ProofInfo) -> Self {
  2607. Self {
  2608. proof: std::sync::Arc::new(info.proof.into()),
  2609. y: info.y.into(),
  2610. mint_url: info.mint_url.into(),
  2611. state: info.state.into(),
  2612. spending_condition: info.spending_condition.map(Into::into),
  2613. unit: info.unit.into(),
  2614. }
  2615. }
  2616. }
  2617. /// Decode ProofInfo from JSON string
  2618. #[uniffi::export]
  2619. pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
  2620. let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
  2621. Ok(info.into())
  2622. }
  2623. /// Encode ProofInfo to JSON string
  2624. #[uniffi::export]
  2625. pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
  2626. // Convert to cdk::types::ProofInfo for serialization
  2627. let cdk_info = cdk::types::ProofInfo {
  2628. proof: info.proof.inner.clone(),
  2629. y: info.y.try_into()?,
  2630. mint_url: info.mint_url.try_into()?,
  2631. state: info.state.into(),
  2632. spending_condition: info.spending_condition.and_then(|c| c.try_into().ok()),
  2633. unit: info.unit.into(),
  2634. };
  2635. Ok(serde_json::to_string(&cdk_info)?)
  2636. }
  2637. // State enum removed - using ProofState instead
  2638. /// FFI-compatible Id (for keyset IDs)
  2639. #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
  2640. #[serde(transparent)]
  2641. pub struct Id {
  2642. pub hex: String,
  2643. }
  2644. impl From<cdk::nuts::Id> for Id {
  2645. fn from(id: cdk::nuts::Id) -> Self {
  2646. Self {
  2647. hex: id.to_string(),
  2648. }
  2649. }
  2650. }
  2651. impl From<Id> for cdk::nuts::Id {
  2652. fn from(id: Id) -> Self {
  2653. use std::str::FromStr;
  2654. Self::from_str(&id.hex).unwrap()
  2655. }
  2656. }