| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974 |
- //! FFI-compatible types
- use std::collections::HashMap;
- use std::str::FromStr;
- use std::sync::Mutex;
- use cdk::nuts::{CurrencyUnit as CdkCurrencyUnit, State as CdkState};
- use cdk::pub_sub::SubId;
- use cdk::Amount as CdkAmount;
- use serde::{Deserialize, Serialize};
- use crate::error::FfiError;
- /// FFI-compatible Amount type
- #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct Amount {
- pub value: u64,
- }
- impl Amount {
- pub fn new(value: u64) -> Self {
- Self { value }
- }
- pub fn zero() -> Self {
- Self { value: 0 }
- }
- pub fn is_zero(&self) -> bool {
- self.value == 0
- }
- pub fn convert_unit(
- &self,
- current_unit: CurrencyUnit,
- target_unit: CurrencyUnit,
- ) -> Result<Amount, FfiError> {
- Ok(CdkAmount::from(self.value)
- .convert_unit(¤t_unit.into(), &target_unit.into())
- .map(Into::into)?)
- }
- pub fn add(&self, other: Amount) -> Result<Amount, FfiError> {
- let self_amount = CdkAmount::from(self.value);
- let other_amount = CdkAmount::from(other.value);
- self_amount
- .checked_add(other_amount)
- .map(Into::into)
- .ok_or(FfiError::AmountOverflow)
- }
- pub fn subtract(&self, other: Amount) -> Result<Amount, FfiError> {
- let self_amount = CdkAmount::from(self.value);
- let other_amount = CdkAmount::from(other.value);
- self_amount
- .checked_sub(other_amount)
- .map(Into::into)
- .ok_or(FfiError::AmountOverflow)
- }
- pub fn multiply(&self, factor: u64) -> Result<Amount, FfiError> {
- let self_amount = CdkAmount::from(self.value);
- let factor_amount = CdkAmount::from(factor);
- self_amount
- .checked_mul(factor_amount)
- .map(Into::into)
- .ok_or(FfiError::AmountOverflow)
- }
- pub fn divide(&self, divisor: u64) -> Result<Amount, FfiError> {
- if divisor == 0 {
- return Err(FfiError::DivisionByZero);
- }
- let self_amount = CdkAmount::from(self.value);
- let divisor_amount = CdkAmount::from(divisor);
- self_amount
- .checked_div(divisor_amount)
- .map(Into::into)
- .ok_or(FfiError::AmountOverflow)
- }
- }
- impl From<CdkAmount> for Amount {
- fn from(amount: CdkAmount) -> Self {
- Self {
- value: u64::from(amount),
- }
- }
- }
- impl From<Amount> for CdkAmount {
- fn from(amount: Amount) -> Self {
- CdkAmount::from(amount.value)
- }
- }
- /// FFI-compatible Currency Unit
- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
- pub enum CurrencyUnit {
- Sat,
- Msat,
- Usd,
- Eur,
- Auth,
- Custom { unit: String },
- }
- impl From<CdkCurrencyUnit> for CurrencyUnit {
- fn from(unit: CdkCurrencyUnit) -> Self {
- match unit {
- CdkCurrencyUnit::Sat => CurrencyUnit::Sat,
- CdkCurrencyUnit::Msat => CurrencyUnit::Msat,
- CdkCurrencyUnit::Usd => CurrencyUnit::Usd,
- CdkCurrencyUnit::Eur => CurrencyUnit::Eur,
- CdkCurrencyUnit::Auth => CurrencyUnit::Auth,
- CdkCurrencyUnit::Custom(s) => CurrencyUnit::Custom { unit: s },
- _ => CurrencyUnit::Sat, // Default for unknown units
- }
- }
- }
- impl From<CurrencyUnit> for CdkCurrencyUnit {
- fn from(unit: CurrencyUnit) -> Self {
- match unit {
- CurrencyUnit::Sat => CdkCurrencyUnit::Sat,
- CurrencyUnit::Msat => CdkCurrencyUnit::Msat,
- CurrencyUnit::Usd => CdkCurrencyUnit::Usd,
- CurrencyUnit::Eur => CdkCurrencyUnit::Eur,
- CurrencyUnit::Auth => CdkCurrencyUnit::Auth,
- CurrencyUnit::Custom { unit } => CdkCurrencyUnit::Custom(unit),
- }
- }
- }
- /// FFI-compatible Mint URL
- #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct MintUrl {
- pub url: String,
- }
- impl MintUrl {
- pub fn new(url: String) -> Result<Self, FfiError> {
- // Validate URL format
- url::Url::parse(&url).map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })?;
- Ok(Self { url })
- }
- }
- impl From<cdk::mint_url::MintUrl> for MintUrl {
- fn from(mint_url: cdk::mint_url::MintUrl) -> Self {
- Self {
- url: mint_url.to_string(),
- }
- }
- }
- impl TryFrom<MintUrl> for cdk::mint_url::MintUrl {
- type Error = FfiError;
- fn try_from(mint_url: MintUrl) -> Result<Self, Self::Error> {
- cdk::mint_url::MintUrl::from_str(&mint_url.url)
- .map_err(|e| FfiError::InvalidUrl { msg: e.to_string() })
- }
- }
- /// FFI-compatible Proof state
- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
- pub enum ProofState {
- Unspent,
- Pending,
- Spent,
- Reserved,
- PendingSpent,
- }
- impl From<CdkState> for ProofState {
- fn from(state: CdkState) -> Self {
- match state {
- CdkState::Unspent => ProofState::Unspent,
- CdkState::Pending => ProofState::Pending,
- CdkState::Spent => ProofState::Spent,
- CdkState::Reserved => ProofState::Reserved,
- CdkState::PendingSpent => ProofState::PendingSpent,
- }
- }
- }
- impl From<ProofState> for CdkState {
- fn from(state: ProofState) -> Self {
- match state {
- ProofState::Unspent => CdkState::Unspent,
- ProofState::Pending => CdkState::Pending,
- ProofState::Spent => CdkState::Spent,
- ProofState::Reserved => CdkState::Reserved,
- ProofState::PendingSpent => CdkState::PendingSpent,
- }
- }
- }
- /// FFI-compatible Token
- #[derive(Debug, uniffi::Object)]
- pub struct Token {
- pub(crate) inner: cdk::nuts::Token,
- }
- impl std::fmt::Display for Token {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.inner)
- }
- }
- impl FromStr for Token {
- type Err = FfiError;
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let token = cdk::nuts::Token::from_str(s)
- .map_err(|e| FfiError::InvalidToken { msg: e.to_string() })?;
- Ok(Token { inner: token })
- }
- }
- impl From<cdk::nuts::Token> for Token {
- fn from(token: cdk::nuts::Token) -> Self {
- Self { inner: token }
- }
- }
- impl From<Token> for cdk::nuts::Token {
- fn from(token: Token) -> Self {
- token.inner
- }
- }
- #[uniffi::export]
- impl Token {
- /// Create a new Token from string
- #[uniffi::constructor]
- pub fn from_string(encoded_token: String) -> Result<Token, FfiError> {
- let token = cdk::nuts::Token::from_str(&encoded_token)
- .map_err(|e| FfiError::InvalidToken { msg: e.to_string() })?;
- Ok(Token { inner: token })
- }
- /// Get the total value of the token
- pub fn value(&self) -> Result<Amount, FfiError> {
- Ok(self.inner.value()?.into())
- }
- /// Get the memo from the token
- pub fn memo(&self) -> Option<String> {
- self.inner.memo().clone()
- }
- /// Get the currency unit
- pub fn unit(&self) -> Option<CurrencyUnit> {
- self.inner.unit().map(Into::into)
- }
- /// Get the mint URL
- pub fn mint_url(&self) -> Result<MintUrl, FfiError> {
- Ok(self.inner.mint_url()?.into())
- }
- /// Get proofs from the token (simplified - no keyset filtering for now)
- pub fn proofs_simple(&self) -> Result<Proofs, FfiError> {
- // For now, return empty keysets to get all proofs
- let empty_keysets = vec![];
- let proofs = self.inner.proofs(&empty_keysets)?;
- Ok(proofs
- .into_iter()
- .map(|p| std::sync::Arc::new(p.into()))
- .collect())
- }
- /// Convert token to raw bytes
- pub fn to_raw_bytes(&self) -> Result<Vec<u8>, FfiError> {
- Ok(self.inner.to_raw_bytes()?)
- }
- /// Encode token to string representation
- pub fn encode(&self) -> String {
- self.to_string()
- }
- /// Decode token from string representation
- #[uniffi::constructor]
- pub fn decode(encoded_token: String) -> Result<Token, FfiError> {
- encoded_token.parse()
- }
- }
- /// FFI-compatible SendMemo
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct SendMemo {
- /// Memo text
- pub memo: String,
- /// Include memo in token
- pub include_memo: bool,
- }
- impl From<SendMemo> for cdk::wallet::SendMemo {
- fn from(memo: SendMemo) -> Self {
- cdk::wallet::SendMemo {
- memo: memo.memo,
- include_memo: memo.include_memo,
- }
- }
- }
- impl From<cdk::wallet::SendMemo> for SendMemo {
- fn from(memo: cdk::wallet::SendMemo) -> Self {
- Self {
- memo: memo.memo,
- include_memo: memo.include_memo,
- }
- }
- }
- impl SendMemo {
- /// Convert SendMemo to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode SendMemo from JSON string
- #[uniffi::export]
- pub fn decode_send_memo(json: String) -> Result<SendMemo, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode SendMemo to JSON string
- #[uniffi::export]
- pub fn encode_send_memo(memo: SendMemo) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&memo)?)
- }
- /// FFI-compatible SplitTarget
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
- pub enum SplitTarget {
- /// Default target; least amount of proofs
- None,
- /// Target amount for wallet to have most proofs that add up to value
- Value { amount: Amount },
- /// Specific amounts to split into (must equal amount being split)
- Values { amounts: Vec<Amount> },
- }
- impl From<SplitTarget> for cdk::amount::SplitTarget {
- fn from(target: SplitTarget) -> Self {
- match target {
- SplitTarget::None => cdk::amount::SplitTarget::None,
- SplitTarget::Value { amount } => cdk::amount::SplitTarget::Value(amount.into()),
- SplitTarget::Values { amounts } => {
- cdk::amount::SplitTarget::Values(amounts.into_iter().map(Into::into).collect())
- }
- }
- }
- }
- impl From<cdk::amount::SplitTarget> for SplitTarget {
- fn from(target: cdk::amount::SplitTarget) -> Self {
- match target {
- cdk::amount::SplitTarget::None => SplitTarget::None,
- cdk::amount::SplitTarget::Value(amount) => SplitTarget::Value {
- amount: amount.into(),
- },
- cdk::amount::SplitTarget::Values(amounts) => SplitTarget::Values {
- amounts: amounts.into_iter().map(Into::into).collect(),
- },
- }
- }
- }
- /// FFI-compatible SendKind
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
- pub enum SendKind {
- /// Allow online swap before send if wallet does not have exact amount
- OnlineExact,
- /// Prefer offline send if difference is less than tolerance
- OnlineTolerance { tolerance: Amount },
- /// Wallet cannot do an online swap and selected proof must be exactly send amount
- OfflineExact,
- /// Wallet must remain offline but can over pay if below tolerance
- OfflineTolerance { tolerance: Amount },
- }
- impl From<SendKind> for cdk::wallet::SendKind {
- fn from(kind: SendKind) -> Self {
- match kind {
- SendKind::OnlineExact => cdk::wallet::SendKind::OnlineExact,
- SendKind::OnlineTolerance { tolerance } => {
- cdk::wallet::SendKind::OnlineTolerance(tolerance.into())
- }
- SendKind::OfflineExact => cdk::wallet::SendKind::OfflineExact,
- SendKind::OfflineTolerance { tolerance } => {
- cdk::wallet::SendKind::OfflineTolerance(tolerance.into())
- }
- }
- }
- }
- impl From<cdk::wallet::SendKind> for SendKind {
- fn from(kind: cdk::wallet::SendKind) -> Self {
- match kind {
- cdk::wallet::SendKind::OnlineExact => SendKind::OnlineExact,
- cdk::wallet::SendKind::OnlineTolerance(tolerance) => SendKind::OnlineTolerance {
- tolerance: tolerance.into(),
- },
- cdk::wallet::SendKind::OfflineExact => SendKind::OfflineExact,
- cdk::wallet::SendKind::OfflineTolerance(tolerance) => SendKind::OfflineTolerance {
- tolerance: tolerance.into(),
- },
- }
- }
- }
- /// FFI-compatible Send options
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct SendOptions {
- /// Memo
- pub memo: Option<SendMemo>,
- /// Spending conditions
- pub conditions: Option<SpendingConditions>,
- /// Amount split target
- pub amount_split_target: SplitTarget,
- /// Send kind
- pub send_kind: SendKind,
- /// Include fee
- pub include_fee: bool,
- /// Maximum number of proofs to include in the token
- pub max_proofs: Option<u32>,
- /// Metadata
- pub metadata: HashMap<String, String>,
- }
- impl Default for SendOptions {
- fn default() -> Self {
- Self {
- memo: None,
- conditions: None,
- amount_split_target: SplitTarget::None,
- send_kind: SendKind::OnlineExact,
- include_fee: false,
- max_proofs: None,
- metadata: HashMap::new(),
- }
- }
- }
- impl From<SendOptions> for cdk::wallet::SendOptions {
- fn from(opts: SendOptions) -> Self {
- cdk::wallet::SendOptions {
- memo: opts.memo.map(Into::into),
- conditions: opts.conditions.and_then(|c| c.try_into().ok()),
- amount_split_target: opts.amount_split_target.into(),
- send_kind: opts.send_kind.into(),
- include_fee: opts.include_fee,
- max_proofs: opts.max_proofs.map(|p| p as usize),
- metadata: opts.metadata,
- }
- }
- }
- impl From<cdk::wallet::SendOptions> for SendOptions {
- fn from(opts: cdk::wallet::SendOptions) -> Self {
- Self {
- memo: opts.memo.map(Into::into),
- conditions: opts.conditions.map(Into::into),
- amount_split_target: opts.amount_split_target.into(),
- send_kind: opts.send_kind.into(),
- include_fee: opts.include_fee,
- max_proofs: opts.max_proofs.map(|p| p as u32),
- metadata: opts.metadata,
- }
- }
- }
- impl SendOptions {
- /// Convert SendOptions to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode SendOptions from JSON string
- #[uniffi::export]
- pub fn decode_send_options(json: String) -> Result<SendOptions, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode SendOptions to JSON string
- #[uniffi::export]
- pub fn encode_send_options(options: SendOptions) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&options)?)
- }
- /// FFI-compatible SecretKey
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct SecretKey {
- /// Hex-encoded secret key (64 characters)
- pub hex: String,
- }
- impl SecretKey {
- /// Create a new SecretKey from hex string
- pub fn from_hex(hex: String) -> Result<Self, FfiError> {
- // Validate hex string length (should be 64 characters for 32 bytes)
- if hex.len() != 64 {
- return Err(FfiError::InvalidHex {
- msg: "Secret key hex must be exactly 64 characters (32 bytes)".to_string(),
- });
- }
- // Validate hex format
- if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
- return Err(FfiError::InvalidHex {
- msg: "Secret key hex contains invalid characters".to_string(),
- });
- }
- Ok(Self { hex })
- }
- /// Generate a random secret key
- pub fn random() -> Self {
- use cdk::nuts::SecretKey as CdkSecretKey;
- let secret_key = CdkSecretKey::generate();
- Self {
- hex: secret_key.to_secret_hex(),
- }
- }
- }
- impl From<SecretKey> for cdk::nuts::SecretKey {
- fn from(key: SecretKey) -> Self {
- // This will panic if hex is invalid, but we validate in from_hex()
- cdk::nuts::SecretKey::from_hex(&key.hex).expect("Invalid secret key hex")
- }
- }
- impl From<cdk::nuts::SecretKey> for SecretKey {
- fn from(key: cdk::nuts::SecretKey) -> Self {
- Self {
- hex: key.to_secret_hex(),
- }
- }
- }
- /// FFI-compatible Receive options
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct ReceiveOptions {
- /// Amount split target
- pub amount_split_target: SplitTarget,
- /// P2PK signing keys
- pub p2pk_signing_keys: Vec<SecretKey>,
- /// Preimages for HTLC conditions
- pub preimages: Vec<String>,
- /// Metadata
- pub metadata: HashMap<String, String>,
- }
- impl Default for ReceiveOptions {
- fn default() -> Self {
- Self {
- amount_split_target: SplitTarget::None,
- p2pk_signing_keys: Vec::new(),
- preimages: Vec::new(),
- metadata: HashMap::new(),
- }
- }
- }
- impl From<ReceiveOptions> for cdk::wallet::ReceiveOptions {
- fn from(opts: ReceiveOptions) -> Self {
- cdk::wallet::ReceiveOptions {
- amount_split_target: opts.amount_split_target.into(),
- p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
- preimages: opts.preimages,
- metadata: opts.metadata,
- }
- }
- }
- impl From<cdk::wallet::ReceiveOptions> for ReceiveOptions {
- fn from(opts: cdk::wallet::ReceiveOptions) -> Self {
- Self {
- amount_split_target: opts.amount_split_target.into(),
- p2pk_signing_keys: opts.p2pk_signing_keys.into_iter().map(Into::into).collect(),
- preimages: opts.preimages,
- metadata: opts.metadata,
- }
- }
- }
- impl ReceiveOptions {
- /// Convert ReceiveOptions to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode ReceiveOptions from JSON string
- #[uniffi::export]
- pub fn decode_receive_options(json: String) -> Result<ReceiveOptions, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode ReceiveOptions to JSON string
- #[uniffi::export]
- pub fn encode_receive_options(options: ReceiveOptions) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&options)?)
- }
- /// FFI-compatible Proof
- #[derive(Debug, uniffi::Object)]
- pub struct Proof {
- pub(crate) inner: cdk::nuts::Proof,
- }
- impl From<cdk::nuts::Proof> for Proof {
- fn from(proof: cdk::nuts::Proof) -> Self {
- Self { inner: proof }
- }
- }
- impl From<Proof> for cdk::nuts::Proof {
- fn from(proof: Proof) -> Self {
- proof.inner
- }
- }
- #[uniffi::export]
- impl Proof {
- /// Get the amount
- pub fn amount(&self) -> Amount {
- self.inner.amount.into()
- }
- /// Get the secret as string
- pub fn secret(&self) -> String {
- self.inner.secret.to_string()
- }
- /// Get the unblinded signature (C) as string
- pub fn c(&self) -> String {
- self.inner.c.to_string()
- }
- /// Get the keyset ID as string
- pub fn keyset_id(&self) -> String {
- self.inner.keyset_id.to_string()
- }
- /// Get the witness
- pub fn witness(&self) -> Option<Witness> {
- self.inner.witness.as_ref().map(|w| w.clone().into())
- }
- /// Check if proof is active with given keyset IDs
- pub fn is_active(&self, active_keyset_ids: Vec<String>) -> bool {
- use cdk::nuts::Id;
- let ids: Vec<Id> = active_keyset_ids
- .into_iter()
- .filter_map(|id| Id::from_str(&id).ok())
- .collect();
- self.inner.is_active(&ids)
- }
- /// Get the Y value (hash_to_curve of secret)
- pub fn y(&self) -> Result<String, FfiError> {
- Ok(self.inner.y()?.to_string())
- }
- /// Get the DLEQ proof if present
- pub fn dleq(&self) -> Option<ProofDleq> {
- self.inner.dleq.as_ref().map(|d| d.clone().into())
- }
- /// Check if proof has DLEQ proof
- pub fn has_dleq(&self) -> bool {
- self.inner.dleq.is_some()
- }
- }
- /// FFI-compatible Proofs (vector of Proof)
- pub type Proofs = Vec<std::sync::Arc<Proof>>;
- /// FFI-compatible DLEQ proof for proofs
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct ProofDleq {
- /// e value (hex-encoded SecretKey)
- pub e: String,
- /// s value (hex-encoded SecretKey)
- pub s: String,
- /// r value - blinding factor (hex-encoded SecretKey)
- pub r: String,
- }
- /// FFI-compatible DLEQ proof for blind signatures
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct BlindSignatureDleq {
- /// e value (hex-encoded SecretKey)
- pub e: String,
- /// s value (hex-encoded SecretKey)
- pub s: String,
- }
- impl From<cdk::nuts::ProofDleq> for ProofDleq {
- fn from(dleq: cdk::nuts::ProofDleq) -> Self {
- Self {
- e: dleq.e.to_secret_hex(),
- s: dleq.s.to_secret_hex(),
- r: dleq.r.to_secret_hex(),
- }
- }
- }
- impl From<ProofDleq> for cdk::nuts::ProofDleq {
- fn from(dleq: ProofDleq) -> Self {
- Self {
- e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
- s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
- r: cdk::nuts::SecretKey::from_hex(&dleq.r).expect("Invalid r hex"),
- }
- }
- }
- impl From<cdk::nuts::BlindSignatureDleq> for BlindSignatureDleq {
- fn from(dleq: cdk::nuts::BlindSignatureDleq) -> Self {
- Self {
- e: dleq.e.to_secret_hex(),
- s: dleq.s.to_secret_hex(),
- }
- }
- }
- impl From<BlindSignatureDleq> for cdk::nuts::BlindSignatureDleq {
- fn from(dleq: BlindSignatureDleq) -> Self {
- Self {
- e: cdk::nuts::SecretKey::from_hex(&dleq.e).expect("Invalid e hex"),
- s: cdk::nuts::SecretKey::from_hex(&dleq.s).expect("Invalid s hex"),
- }
- }
- }
- /// Helper functions for Proofs
- pub fn proofs_total_amount(proofs: &Proofs) -> Result<Amount, FfiError> {
- let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
- use cdk::nuts::ProofsMethods;
- Ok(cdk_proofs.total_amount()?.into())
- }
- /// FFI-compatible MintQuote
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct MintQuote {
- /// Quote ID
- pub id: String,
- /// Quote amount
- pub amount: Option<Amount>,
- /// Currency unit
- pub unit: CurrencyUnit,
- /// Payment request
- pub request: String,
- /// Quote state
- pub state: QuoteState,
- /// Expiry timestamp
- pub expiry: u64,
- /// Mint URL
- pub mint_url: MintUrl,
- /// Amount issued
- pub amount_issued: Amount,
- /// Amount paid
- pub amount_paid: Amount,
- /// Payment method
- pub payment_method: PaymentMethod,
- /// Secret key (optional, hex-encoded)
- pub secret_key: Option<String>,
- }
- impl From<cdk::wallet::MintQuote> for MintQuote {
- fn from(quote: cdk::wallet::MintQuote) -> Self {
- Self {
- id: quote.id.clone(),
- amount: quote.amount.map(Into::into),
- unit: quote.unit.clone().into(),
- request: quote.request.clone(),
- state: quote.state.into(),
- expiry: quote.expiry,
- mint_url: quote.mint_url.clone().into(),
- amount_issued: quote.amount_issued.into(),
- amount_paid: quote.amount_paid.into(),
- payment_method: quote.payment_method.into(),
- secret_key: quote.secret_key.map(|sk| sk.to_secret_hex()),
- }
- }
- }
- impl TryFrom<MintQuote> for cdk::wallet::MintQuote {
- type Error = FfiError;
- fn try_from(quote: MintQuote) -> Result<Self, Self::Error> {
- let secret_key = quote
- .secret_key
- .map(|hex| cdk::nuts::SecretKey::from_hex(&hex))
- .transpose()
- .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
- Ok(Self {
- id: quote.id,
- amount: quote.amount.map(Into::into),
- unit: quote.unit.into(),
- request: quote.request,
- state: quote.state.into(),
- expiry: quote.expiry,
- mint_url: quote.mint_url.try_into()?,
- amount_issued: quote.amount_issued.into(),
- amount_paid: quote.amount_paid.into(),
- payment_method: quote.payment_method.into(),
- secret_key,
- })
- }
- }
- impl MintQuote {
- /// Get total amount (amount + fees)
- pub fn total_amount(&self) -> Amount {
- if let Some(amount) = self.amount {
- Amount::new(amount.value + self.amount_paid.value - self.amount_issued.value)
- } else {
- Amount::zero()
- }
- }
- /// Check if quote is expired
- pub fn is_expired(&self, current_time: u64) -> bool {
- current_time > self.expiry
- }
- /// Get amount that can be minted
- pub fn amount_mintable(&self) -> Amount {
- Amount::new(self.amount_paid.value - self.amount_issued.value)
- }
- /// Convert MintQuote to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode MintQuote from JSON string
- #[uniffi::export]
- pub fn decode_mint_quote(json: String) -> Result<MintQuote, FfiError> {
- let quote: cdk::wallet::MintQuote = serde_json::from_str(&json)?;
- Ok(quote.into())
- }
- /// Encode MintQuote to JSON string
- #[uniffi::export]
- pub fn encode_mint_quote(quote: MintQuote) -> Result<String, FfiError> {
- Ok(serde_json::to_string("e)?)
- }
- /// FFI-compatible MintQuoteBolt11Response
- #[derive(Debug, uniffi::Object)]
- pub struct MintQuoteBolt11Response {
- /// Quote ID
- pub quote: String,
- /// Request string
- pub request: String,
- /// State of the quote
- pub state: QuoteState,
- /// Expiry timestamp (optional)
- pub expiry: Option<u64>,
- /// Amount (optional)
- pub amount: Option<Amount>,
- /// Unit (optional)
- pub unit: Option<CurrencyUnit>,
- /// Pubkey (optional)
- pub pubkey: Option<String>,
- }
- impl From<cdk::nuts::MintQuoteBolt11Response<String>> for MintQuoteBolt11Response {
- fn from(response: cdk::nuts::MintQuoteBolt11Response<String>) -> Self {
- Self {
- quote: response.quote,
- request: response.request,
- state: response.state.into(),
- expiry: response.expiry,
- amount: response.amount.map(Into::into),
- unit: response.unit.map(Into::into),
- pubkey: response.pubkey.map(|p| p.to_string()),
- }
- }
- }
- #[uniffi::export]
- impl MintQuoteBolt11Response {
- /// Get quote ID
- pub fn quote(&self) -> String {
- self.quote.clone()
- }
- /// Get request string
- pub fn request(&self) -> String {
- self.request.clone()
- }
- /// Get state
- pub fn state(&self) -> QuoteState {
- self.state.clone()
- }
- /// Get expiry
- pub fn expiry(&self) -> Option<u64> {
- self.expiry
- }
- /// Get amount
- pub fn amount(&self) -> Option<Amount> {
- self.amount
- }
- /// Get unit
- pub fn unit(&self) -> Option<CurrencyUnit> {
- self.unit.clone()
- }
- /// Get pubkey
- pub fn pubkey(&self) -> Option<String> {
- self.pubkey.clone()
- }
- }
- /// FFI-compatible MeltQuoteBolt11Response
- #[derive(Debug, uniffi::Object)]
- pub struct MeltQuoteBolt11Response {
- /// Quote ID
- pub quote: String,
- /// Amount
- pub amount: Amount,
- /// Fee reserve
- pub fee_reserve: Amount,
- /// State of the quote
- pub state: QuoteState,
- /// Expiry timestamp
- pub expiry: u64,
- /// Payment preimage (optional)
- pub payment_preimage: Option<String>,
- /// Request string (optional)
- pub request: Option<String>,
- /// Unit (optional)
- pub unit: Option<CurrencyUnit>,
- }
- impl From<cdk::nuts::MeltQuoteBolt11Response<String>> for MeltQuoteBolt11Response {
- fn from(response: cdk::nuts::MeltQuoteBolt11Response<String>) -> Self {
- Self {
- quote: response.quote,
- amount: response.amount.into(),
- fee_reserve: response.fee_reserve.into(),
- state: response.state.into(),
- expiry: response.expiry,
- payment_preimage: response.payment_preimage,
- request: response.request,
- unit: response.unit.map(Into::into),
- }
- }
- }
- #[uniffi::export]
- impl MeltQuoteBolt11Response {
- /// Get quote ID
- pub fn quote(&self) -> String {
- self.quote.clone()
- }
- /// Get amount
- pub fn amount(&self) -> Amount {
- self.amount
- }
- /// Get fee reserve
- pub fn fee_reserve(&self) -> Amount {
- self.fee_reserve
- }
- /// Get state
- pub fn state(&self) -> QuoteState {
- self.state.clone()
- }
- /// Get expiry
- pub fn expiry(&self) -> u64 {
- self.expiry
- }
- /// Get payment preimage
- pub fn payment_preimage(&self) -> Option<String> {
- self.payment_preimage.clone()
- }
- /// Get request
- pub fn request(&self) -> Option<String> {
- self.request.clone()
- }
- /// Get unit
- pub fn unit(&self) -> Option<CurrencyUnit> {
- self.unit.clone()
- }
- }
- /// FFI-compatible PaymentMethod
- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
- pub enum PaymentMethod {
- /// Bolt11 payment type
- Bolt11,
- /// Bolt12 payment type
- Bolt12,
- /// Custom payment type
- Custom { method: String },
- }
- impl From<cdk::nuts::PaymentMethod> for PaymentMethod {
- fn from(method: cdk::nuts::PaymentMethod) -> Self {
- match method {
- cdk::nuts::PaymentMethod::Bolt11 => Self::Bolt11,
- cdk::nuts::PaymentMethod::Bolt12 => Self::Bolt12,
- cdk::nuts::PaymentMethod::Custom(s) => Self::Custom { method: s },
- }
- }
- }
- impl From<PaymentMethod> for cdk::nuts::PaymentMethod {
- fn from(method: PaymentMethod) -> Self {
- match method {
- PaymentMethod::Bolt11 => Self::Bolt11,
- PaymentMethod::Bolt12 => Self::Bolt12,
- PaymentMethod::Custom { method } => Self::Custom(method),
- }
- }
- }
- /// FFI-compatible MeltQuote
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct MeltQuote {
- /// Quote ID
- pub id: String,
- /// Quote amount
- pub amount: Amount,
- /// Currency unit
- pub unit: CurrencyUnit,
- /// Payment request
- pub request: String,
- /// Fee reserve
- pub fee_reserve: Amount,
- /// Quote state
- pub state: QuoteState,
- /// Expiry timestamp
- pub expiry: u64,
- /// Payment preimage
- pub payment_preimage: Option<String>,
- /// Payment method
- pub payment_method: PaymentMethod,
- }
- impl From<cdk::wallet::MeltQuote> for MeltQuote {
- fn from(quote: cdk::wallet::MeltQuote) -> Self {
- Self {
- id: quote.id.clone(),
- amount: quote.amount.into(),
- unit: quote.unit.clone().into(),
- request: quote.request.clone(),
- fee_reserve: quote.fee_reserve.into(),
- state: quote.state.into(),
- expiry: quote.expiry,
- payment_preimage: quote.payment_preimage.clone(),
- payment_method: quote.payment_method.into(),
- }
- }
- }
- impl TryFrom<MeltQuote> for cdk::wallet::MeltQuote {
- type Error = FfiError;
- fn try_from(quote: MeltQuote) -> Result<Self, Self::Error> {
- Ok(Self {
- id: quote.id,
- amount: quote.amount.into(),
- unit: quote.unit.into(),
- request: quote.request,
- fee_reserve: quote.fee_reserve.into(),
- state: quote.state.into(),
- expiry: quote.expiry,
- payment_preimage: quote.payment_preimage,
- payment_method: quote.payment_method.into(),
- })
- }
- }
- impl MeltQuote {
- /// Convert MeltQuote to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode MeltQuote from JSON string
- #[uniffi::export]
- pub fn decode_melt_quote(json: String) -> Result<MeltQuote, FfiError> {
- let quote: cdk::wallet::MeltQuote = serde_json::from_str(&json)?;
- Ok(quote.into())
- }
- /// Encode MeltQuote to JSON string
- #[uniffi::export]
- pub fn encode_melt_quote(quote: MeltQuote) -> Result<String, FfiError> {
- Ok(serde_json::to_string("e)?)
- }
- /// FFI-compatible QuoteState
- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
- pub enum QuoteState {
- Unpaid,
- Paid,
- Pending,
- Issued,
- }
- impl From<cdk::nuts::nut05::QuoteState> for QuoteState {
- fn from(state: cdk::nuts::nut05::QuoteState) -> Self {
- match state {
- cdk::nuts::nut05::QuoteState::Unpaid => QuoteState::Unpaid,
- cdk::nuts::nut05::QuoteState::Paid => QuoteState::Paid,
- cdk::nuts::nut05::QuoteState::Pending => QuoteState::Pending,
- cdk::nuts::nut05::QuoteState::Unknown => QuoteState::Unpaid,
- cdk::nuts::nut05::QuoteState::Failed => QuoteState::Unpaid,
- }
- }
- }
- impl From<QuoteState> for cdk::nuts::nut05::QuoteState {
- fn from(state: QuoteState) -> Self {
- match state {
- QuoteState::Unpaid => cdk::nuts::nut05::QuoteState::Unpaid,
- QuoteState::Paid => cdk::nuts::nut05::QuoteState::Paid,
- QuoteState::Pending => cdk::nuts::nut05::QuoteState::Pending,
- QuoteState::Issued => cdk::nuts::nut05::QuoteState::Paid, // Map issued to paid for melt quotes
- }
- }
- }
- impl From<cdk::nuts::MintQuoteState> for QuoteState {
- fn from(state: cdk::nuts::MintQuoteState) -> Self {
- match state {
- cdk::nuts::MintQuoteState::Unpaid => QuoteState::Unpaid,
- cdk::nuts::MintQuoteState::Paid => QuoteState::Paid,
- cdk::nuts::MintQuoteState::Issued => QuoteState::Issued,
- }
- }
- }
- impl From<QuoteState> for cdk::nuts::MintQuoteState {
- fn from(state: QuoteState) -> Self {
- match state {
- QuoteState::Unpaid => cdk::nuts::MintQuoteState::Unpaid,
- QuoteState::Paid => cdk::nuts::MintQuoteState::Paid,
- QuoteState::Issued => cdk::nuts::MintQuoteState::Issued,
- QuoteState::Pending => cdk::nuts::MintQuoteState::Paid, // Map pending to paid
- }
- }
- }
- // Note: MeltQuoteState is the same as nut05::QuoteState, so we don't need a separate impl
- /// FFI-compatible PreparedSend
- #[derive(Debug, uniffi::Object)]
- pub struct PreparedSend {
- inner: Mutex<Option<cdk::wallet::PreparedSend>>,
- id: String,
- amount: Amount,
- proofs: Proofs,
- }
- impl From<cdk::wallet::PreparedSend> for PreparedSend {
- fn from(prepared: cdk::wallet::PreparedSend) -> Self {
- let id = format!("{:?}", prepared); // Use debug format as ID
- let amount = prepared.amount().into();
- let proofs = prepared
- .proofs()
- .iter()
- .cloned()
- .map(|p| std::sync::Arc::new(p.into()))
- .collect();
- Self {
- inner: Mutex::new(Some(prepared)),
- id,
- amount,
- proofs,
- }
- }
- }
- #[uniffi::export(async_runtime = "tokio")]
- impl PreparedSend {
- /// Get the prepared send ID
- pub fn id(&self) -> String {
- self.id.clone()
- }
- /// Get the amount to send
- pub fn amount(&self) -> Amount {
- self.amount
- }
- /// Get the proofs that will be used
- pub fn proofs(&self) -> Proofs {
- self.proofs.clone()
- }
- /// Get the total fee for this send operation
- pub fn fee(&self) -> Amount {
- if let Ok(guard) = self.inner.lock() {
- if let Some(ref inner) = *guard {
- inner.fee().into()
- } else {
- Amount::new(0)
- }
- } else {
- Amount::new(0)
- }
- }
- /// Confirm the prepared send and create a token
- pub async fn confirm(
- self: std::sync::Arc<Self>,
- memo: Option<String>,
- ) -> Result<Token, FfiError> {
- let inner = {
- if let Ok(mut guard) = self.inner.lock() {
- guard.take()
- } else {
- return Err(FfiError::Generic {
- msg: "Failed to acquire lock on PreparedSend".to_string(),
- });
- }
- };
- if let Some(inner) = inner {
- let send_memo = memo.map(|m| cdk::wallet::SendMemo::for_token(&m));
- let token = inner.confirm(send_memo).await?;
- Ok(token.into())
- } else {
- Err(FfiError::Generic {
- msg: "PreparedSend has already been consumed or cancelled".to_string(),
- })
- }
- }
- /// Cancel the prepared send operation
- pub async fn cancel(self: std::sync::Arc<Self>) -> Result<(), FfiError> {
- let inner = {
- if let Ok(mut guard) = self.inner.lock() {
- guard.take()
- } else {
- return Err(FfiError::Generic {
- msg: "Failed to acquire lock on PreparedSend".to_string(),
- });
- }
- };
- if let Some(inner) = inner {
- inner.cancel().await?;
- Ok(())
- } else {
- Err(FfiError::Generic {
- msg: "PreparedSend has already been consumed or cancelled".to_string(),
- })
- }
- }
- }
- /// FFI-compatible Melted result
- #[derive(Debug, Clone, uniffi::Record)]
- pub struct Melted {
- pub state: QuoteState,
- pub preimage: Option<String>,
- pub change: Option<Proofs>,
- pub amount: Amount,
- pub fee_paid: Amount,
- }
- // MeltQuoteState is just an alias for nut05::QuoteState, so we don't need a separate implementation
- impl From<cdk::types::Melted> for Melted {
- fn from(melted: cdk::types::Melted) -> Self {
- Self {
- state: melted.state.into(),
- preimage: melted.preimage,
- change: melted.change.map(|proofs| {
- proofs
- .into_iter()
- .map(|p| std::sync::Arc::new(p.into()))
- .collect()
- }),
- amount: melted.amount.into(),
- fee_paid: melted.fee_paid.into(),
- }
- }
- }
- /// FFI-compatible MeltOptions
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
- pub enum MeltOptions {
- /// MPP (Multi-Part Payments) options
- Mpp { amount: Amount },
- /// Amountless options
- Amountless { amount_msat: Amount },
- }
- impl From<MeltOptions> for cdk::nuts::MeltOptions {
- fn from(opts: MeltOptions) -> Self {
- match opts {
- MeltOptions::Mpp { amount } => {
- let cdk_amount: cdk::Amount = amount.into();
- cdk::nuts::MeltOptions::new_mpp(cdk_amount)
- }
- MeltOptions::Amountless { amount_msat } => {
- let cdk_amount: cdk::Amount = amount_msat.into();
- cdk::nuts::MeltOptions::new_amountless(cdk_amount)
- }
- }
- }
- }
- impl From<cdk::nuts::MeltOptions> for MeltOptions {
- fn from(opts: cdk::nuts::MeltOptions) -> Self {
- match opts {
- cdk::nuts::MeltOptions::Mpp { mpp } => MeltOptions::Mpp {
- amount: mpp.amount.into(),
- },
- cdk::nuts::MeltOptions::Amountless { amountless } => MeltOptions::Amountless {
- amount_msat: amountless.amount_msat.into(),
- },
- }
- }
- }
- /// FFI-compatible MintVersion
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct MintVersion {
- /// Mint Software name
- pub name: String,
- /// Mint Version
- pub version: String,
- }
- impl From<cdk::nuts::MintVersion> for MintVersion {
- fn from(version: cdk::nuts::MintVersion) -> Self {
- Self {
- name: version.name,
- version: version.version,
- }
- }
- }
- impl From<MintVersion> for cdk::nuts::MintVersion {
- fn from(version: MintVersion) -> Self {
- Self {
- name: version.name,
- version: version.version,
- }
- }
- }
- impl MintVersion {
- /// Convert MintVersion to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode MintVersion from JSON string
- #[uniffi::export]
- pub fn decode_mint_version(json: String) -> Result<MintVersion, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode MintVersion to JSON string
- #[uniffi::export]
- pub fn encode_mint_version(version: MintVersion) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&version)?)
- }
- /// FFI-compatible ContactInfo
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct ContactInfo {
- /// Contact Method i.e. nostr
- pub method: String,
- /// Contact info i.e. npub...
- pub info: String,
- }
- impl From<cdk::nuts::ContactInfo> for ContactInfo {
- fn from(contact: cdk::nuts::ContactInfo) -> Self {
- Self {
- method: contact.method,
- info: contact.info,
- }
- }
- }
- impl From<ContactInfo> for cdk::nuts::ContactInfo {
- fn from(contact: ContactInfo) -> Self {
- Self {
- method: contact.method,
- info: contact.info,
- }
- }
- }
- impl ContactInfo {
- /// Convert ContactInfo to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode ContactInfo from JSON string
- #[uniffi::export]
- pub fn decode_contact_info(json: String) -> Result<ContactInfo, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode ContactInfo to JSON string
- #[uniffi::export]
- pub fn encode_contact_info(info: ContactInfo) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&info)?)
- }
- /// FFI-compatible SupportedSettings
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct SupportedSettings {
- /// Setting supported
- pub supported: bool,
- }
- impl From<cdk::nuts::nut06::SupportedSettings> for SupportedSettings {
- fn from(settings: cdk::nuts::nut06::SupportedSettings) -> Self {
- Self {
- supported: settings.supported,
- }
- }
- }
- impl From<SupportedSettings> for cdk::nuts::nut06::SupportedSettings {
- fn from(settings: SupportedSettings) -> Self {
- Self {
- supported: settings.supported,
- }
- }
- }
- // -----------------------------
- // NUT-04/05 FFI Types
- // -----------------------------
- /// FFI-compatible MintMethodSettings (NUT-04)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct MintMethodSettings {
- pub method: PaymentMethod,
- pub unit: CurrencyUnit,
- pub min_amount: Option<Amount>,
- pub max_amount: Option<Amount>,
- /// For bolt11, whether mint supports setting invoice description
- pub description: Option<bool>,
- }
- impl From<cdk::nuts::nut04::MintMethodSettings> for MintMethodSettings {
- fn from(s: cdk::nuts::nut04::MintMethodSettings) -> Self {
- let description = match s.options {
- Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description }) => Some(description),
- _ => None,
- };
- Self {
- method: s.method.into(),
- unit: s.unit.into(),
- min_amount: s.min_amount.map(Into::into),
- max_amount: s.max_amount.map(Into::into),
- description,
- }
- }
- }
- impl TryFrom<MintMethodSettings> for cdk::nuts::nut04::MintMethodSettings {
- type Error = FfiError;
- fn try_from(s: MintMethodSettings) -> Result<Self, Self::Error> {
- let options = match (s.method.clone(), s.description) {
- (PaymentMethod::Bolt11, Some(description)) => {
- Some(cdk::nuts::nut04::MintMethodOptions::Bolt11 { description })
- }
- _ => None,
- };
- Ok(Self {
- method: s.method.into(),
- unit: s.unit.into(),
- min_amount: s.min_amount.map(Into::into),
- max_amount: s.max_amount.map(Into::into),
- options,
- })
- }
- }
- /// FFI-compatible Nut04 Settings
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct Nut04Settings {
- pub methods: Vec<MintMethodSettings>,
- pub disabled: bool,
- }
- impl From<cdk::nuts::nut04::Settings> for Nut04Settings {
- fn from(s: cdk::nuts::nut04::Settings) -> Self {
- Self {
- methods: s.methods.into_iter().map(Into::into).collect(),
- disabled: s.disabled,
- }
- }
- }
- impl TryFrom<Nut04Settings> for cdk::nuts::nut04::Settings {
- type Error = FfiError;
- fn try_from(s: Nut04Settings) -> Result<Self, Self::Error> {
- Ok(Self {
- methods: s
- .methods
- .into_iter()
- .map(TryInto::try_into)
- .collect::<Result<_, _>>()?,
- disabled: s.disabled,
- })
- }
- }
- /// FFI-compatible MeltMethodSettings (NUT-05)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct MeltMethodSettings {
- pub method: PaymentMethod,
- pub unit: CurrencyUnit,
- pub min_amount: Option<Amount>,
- pub max_amount: Option<Amount>,
- /// For bolt11, whether mint supports amountless invoices
- pub amountless: Option<bool>,
- }
- impl From<cdk::nuts::nut05::MeltMethodSettings> for MeltMethodSettings {
- fn from(s: cdk::nuts::nut05::MeltMethodSettings) -> Self {
- let amountless = match s.options {
- Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless }) => Some(amountless),
- _ => None,
- };
- Self {
- method: s.method.into(),
- unit: s.unit.into(),
- min_amount: s.min_amount.map(Into::into),
- max_amount: s.max_amount.map(Into::into),
- amountless,
- }
- }
- }
- impl TryFrom<MeltMethodSettings> for cdk::nuts::nut05::MeltMethodSettings {
- type Error = FfiError;
- fn try_from(s: MeltMethodSettings) -> Result<Self, Self::Error> {
- let options = match (s.method.clone(), s.amountless) {
- (PaymentMethod::Bolt11, Some(amountless)) => {
- Some(cdk::nuts::nut05::MeltMethodOptions::Bolt11 { amountless })
- }
- _ => None,
- };
- Ok(Self {
- method: s.method.into(),
- unit: s.unit.into(),
- min_amount: s.min_amount.map(Into::into),
- max_amount: s.max_amount.map(Into::into),
- options,
- })
- }
- }
- /// FFI-compatible Nut05 Settings
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct Nut05Settings {
- pub methods: Vec<MeltMethodSettings>,
- pub disabled: bool,
- }
- impl From<cdk::nuts::nut05::Settings> for Nut05Settings {
- fn from(s: cdk::nuts::nut05::Settings) -> Self {
- Self {
- methods: s.methods.into_iter().map(Into::into).collect(),
- disabled: s.disabled,
- }
- }
- }
- impl TryFrom<Nut05Settings> for cdk::nuts::nut05::Settings {
- type Error = FfiError;
- fn try_from(s: Nut05Settings) -> Result<Self, Self::Error> {
- Ok(Self {
- methods: s
- .methods
- .into_iter()
- .map(TryInto::try_into)
- .collect::<Result<_, _>>()?,
- disabled: s.disabled,
- })
- }
- }
- /// FFI-compatible ProtectedEndpoint (for auth nuts)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct ProtectedEndpoint {
- /// HTTP method (GET, POST, etc.)
- pub method: String,
- /// Endpoint path
- pub path: String,
- }
- /// FFI-compatible ClearAuthSettings (NUT-21)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct ClearAuthSettings {
- /// OpenID Connect discovery URL
- pub openid_discovery: String,
- /// OAuth 2.0 client ID
- pub client_id: String,
- /// Protected endpoints requiring clear authentication
- pub protected_endpoints: Vec<ProtectedEndpoint>,
- }
- /// FFI-compatible BlindAuthSettings (NUT-22)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct BlindAuthSettings {
- /// Maximum number of blind auth tokens that can be minted per request
- pub bat_max_mint: u64,
- /// Protected endpoints requiring blind authentication
- pub protected_endpoints: Vec<ProtectedEndpoint>,
- }
- impl From<cdk::nuts::ClearAuthSettings> for ClearAuthSettings {
- fn from(settings: cdk::nuts::ClearAuthSettings) -> Self {
- Self {
- openid_discovery: settings.openid_discovery,
- client_id: settings.client_id,
- protected_endpoints: settings
- .protected_endpoints
- .into_iter()
- .map(Into::into)
- .collect(),
- }
- }
- }
- impl TryFrom<ClearAuthSettings> for cdk::nuts::ClearAuthSettings {
- type Error = FfiError;
- fn try_from(settings: ClearAuthSettings) -> Result<Self, Self::Error> {
- Ok(Self {
- openid_discovery: settings.openid_discovery,
- client_id: settings.client_id,
- protected_endpoints: settings
- .protected_endpoints
- .into_iter()
- .map(|e| e.try_into())
- .collect::<Result<Vec<_>, _>>()?,
- })
- }
- }
- impl From<cdk::nuts::BlindAuthSettings> for BlindAuthSettings {
- fn from(settings: cdk::nuts::BlindAuthSettings) -> Self {
- Self {
- bat_max_mint: settings.bat_max_mint,
- protected_endpoints: settings
- .protected_endpoints
- .into_iter()
- .map(Into::into)
- .collect(),
- }
- }
- }
- impl TryFrom<BlindAuthSettings> for cdk::nuts::BlindAuthSettings {
- type Error = FfiError;
- fn try_from(settings: BlindAuthSettings) -> Result<Self, Self::Error> {
- Ok(Self {
- bat_max_mint: settings.bat_max_mint,
- protected_endpoints: settings
- .protected_endpoints
- .into_iter()
- .map(|e| e.try_into())
- .collect::<Result<Vec<_>, _>>()?,
- })
- }
- }
- impl From<cdk::nuts::ProtectedEndpoint> for ProtectedEndpoint {
- fn from(endpoint: cdk::nuts::ProtectedEndpoint) -> Self {
- Self {
- method: match endpoint.method {
- cdk::nuts::Method::Get => "GET".to_string(),
- cdk::nuts::Method::Post => "POST".to_string(),
- },
- path: endpoint.path.to_string(),
- }
- }
- }
- impl TryFrom<ProtectedEndpoint> for cdk::nuts::ProtectedEndpoint {
- type Error = FfiError;
- fn try_from(endpoint: ProtectedEndpoint) -> Result<Self, Self::Error> {
- let method = match endpoint.method.as_str() {
- "GET" => cdk::nuts::Method::Get,
- "POST" => cdk::nuts::Method::Post,
- _ => {
- return Err(FfiError::Generic {
- msg: format!(
- "Invalid HTTP method: {}. Only GET and POST are supported",
- endpoint.method
- ),
- })
- }
- };
- // Convert path string to RoutePath by matching against known paths
- let route_path = match endpoint.path.as_str() {
- "/v1/mint/quote/bolt11" => cdk::nuts::RoutePath::MintQuoteBolt11,
- "/v1/mint/bolt11" => cdk::nuts::RoutePath::MintBolt11,
- "/v1/melt/quote/bolt11" => cdk::nuts::RoutePath::MeltQuoteBolt11,
- "/v1/melt/bolt11" => cdk::nuts::RoutePath::MeltBolt11,
- "/v1/swap" => cdk::nuts::RoutePath::Swap,
- "/v1/checkstate" => cdk::nuts::RoutePath::Checkstate,
- "/v1/restore" => cdk::nuts::RoutePath::Restore,
- "/v1/auth/blind/mint" => cdk::nuts::RoutePath::MintBlindAuth,
- "/v1/mint/quote/bolt12" => cdk::nuts::RoutePath::MintQuoteBolt12,
- "/v1/mint/bolt12" => cdk::nuts::RoutePath::MintBolt12,
- "/v1/melt/quote/bolt12" => cdk::nuts::RoutePath::MeltQuoteBolt12,
- "/v1/melt/bolt12" => cdk::nuts::RoutePath::MeltBolt12,
- _ => {
- return Err(FfiError::Generic {
- msg: format!("Unknown route path: {}", endpoint.path),
- })
- }
- };
- Ok(cdk::nuts::ProtectedEndpoint::new(method, route_path))
- }
- }
- /// FFI-compatible Nuts settings (extended to include NUT-04 and NUT-05 settings)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct Nuts {
- /// NUT04 Settings
- pub nut04: Nut04Settings,
- /// NUT05 Settings
- pub nut05: Nut05Settings,
- /// NUT07 Settings - Token state check
- pub nut07_supported: bool,
- /// NUT08 Settings - Lightning fee return
- pub nut08_supported: bool,
- /// NUT09 Settings - Restore signature
- pub nut09_supported: bool,
- /// NUT10 Settings - Spending conditions
- pub nut10_supported: bool,
- /// NUT11 Settings - Pay to Public Key Hash
- pub nut11_supported: bool,
- /// NUT12 Settings - DLEQ proofs
- pub nut12_supported: bool,
- /// NUT14 Settings - Hashed Time Locked Contracts
- pub nut14_supported: bool,
- /// NUT20 Settings - Web sockets
- pub nut20_supported: bool,
- /// NUT21 Settings - Clear authentication
- pub nut21: Option<ClearAuthSettings>,
- /// NUT22 Settings - Blind authentication
- pub nut22: Option<BlindAuthSettings>,
- /// Supported currency units for minting
- pub mint_units: Vec<CurrencyUnit>,
- /// Supported currency units for melting
- pub melt_units: Vec<CurrencyUnit>,
- }
- impl From<cdk::nuts::Nuts> for Nuts {
- fn from(nuts: cdk::nuts::Nuts) -> Self {
- let mint_units = nuts
- .supported_mint_units()
- .into_iter()
- .map(|u| u.clone().into())
- .collect();
- let melt_units = nuts
- .supported_melt_units()
- .into_iter()
- .map(|u| u.clone().into())
- .collect();
- Self {
- nut04: nuts.nut04.clone().into(),
- nut05: nuts.nut05.clone().into(),
- nut07_supported: nuts.nut07.supported,
- nut08_supported: nuts.nut08.supported,
- nut09_supported: nuts.nut09.supported,
- nut10_supported: nuts.nut10.supported,
- nut11_supported: nuts.nut11.supported,
- nut12_supported: nuts.nut12.supported,
- nut14_supported: nuts.nut14.supported,
- nut20_supported: nuts.nut20.supported,
- nut21: nuts.nut21.map(Into::into),
- nut22: nuts.nut22.map(Into::into),
- mint_units,
- melt_units,
- }
- }
- }
- impl TryFrom<Nuts> for cdk::nuts::Nuts {
- type Error = FfiError;
- fn try_from(n: Nuts) -> Result<Self, Self::Error> {
- Ok(Self {
- nut04: n.nut04.try_into()?,
- nut05: n.nut05.try_into()?,
- nut07: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut07_supported,
- },
- nut08: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut08_supported,
- },
- nut09: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut09_supported,
- },
- nut10: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut10_supported,
- },
- nut11: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut11_supported,
- },
- nut12: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut12_supported,
- },
- nut14: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut14_supported,
- },
- nut15: Default::default(),
- nut17: Default::default(),
- nut19: Default::default(),
- nut20: cdk::nuts::nut06::SupportedSettings {
- supported: n.nut20_supported,
- },
- nut21: n.nut21.map(|s| s.try_into()).transpose()?,
- nut22: n.nut22.map(|s| s.try_into()).transpose()?,
- })
- }
- }
- impl Nuts {
- /// Convert Nuts to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode Nuts from JSON string
- #[uniffi::export]
- pub fn decode_nuts(json: String) -> Result<Nuts, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode Nuts to JSON string
- #[uniffi::export]
- pub fn encode_nuts(nuts: Nuts) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&nuts)?)
- }
- /// FFI-compatible MintInfo
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct MintInfo {
- /// name of the mint and should be recognizable
- pub name: Option<String>,
- /// hex pubkey of the mint
- pub pubkey: Option<String>,
- /// implementation name and the version running
- pub version: Option<MintVersion>,
- /// short description of the mint
- pub description: Option<String>,
- /// long description
- pub description_long: Option<String>,
- /// Contact info
- pub contact: Option<Vec<ContactInfo>>,
- /// shows which NUTs the mint supports
- pub nuts: Nuts,
- /// Mint's icon URL
- pub icon_url: Option<String>,
- /// Mint's endpoint URLs
- pub urls: Option<Vec<String>>,
- /// message of the day that the wallet must display to the user
- pub motd: Option<String>,
- /// server unix timestamp
- pub time: Option<u64>,
- /// terms of url service of the mint
- pub tos_url: Option<String>,
- }
- impl From<cdk::nuts::MintInfo> for MintInfo {
- fn from(info: cdk::nuts::MintInfo) -> Self {
- Self {
- name: info.name,
- pubkey: info.pubkey.map(|p| p.to_string()),
- version: info.version.map(Into::into),
- description: info.description,
- description_long: info.description_long,
- contact: info
- .contact
- .map(|contacts| contacts.into_iter().map(Into::into).collect()),
- nuts: info.nuts.into(),
- icon_url: info.icon_url,
- urls: info.urls,
- motd: info.motd,
- time: info.time,
- tos_url: info.tos_url,
- }
- }
- }
- impl From<MintInfo> for cdk::nuts::MintInfo {
- fn from(info: MintInfo) -> Self {
- // Convert FFI Nuts back to cdk::nuts::Nuts (best-effort)
- let nuts_cdk: cdk::nuts::Nuts = info.nuts.clone().try_into().unwrap_or_default();
- Self {
- name: info.name,
- pubkey: info.pubkey.and_then(|p| p.parse().ok()),
- version: info.version.map(Into::into),
- description: info.description,
- description_long: info.description_long,
- contact: info
- .contact
- .map(|contacts| contacts.into_iter().map(Into::into).collect()),
- nuts: nuts_cdk,
- icon_url: info.icon_url,
- urls: info.urls,
- motd: info.motd,
- time: info.time,
- tos_url: info.tos_url,
- }
- }
- }
- impl MintInfo {
- /// Convert MintInfo to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode MintInfo from JSON string
- #[uniffi::export]
- pub fn decode_mint_info(json: String) -> Result<MintInfo, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode MintInfo to JSON string
- #[uniffi::export]
- pub fn encode_mint_info(info: MintInfo) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&info)?)
- }
- /// FFI-compatible Conditions (for spending conditions)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct Conditions {
- /// Unix locktime after which refund keys can be used
- pub locktime: Option<u64>,
- /// Additional Public keys (as hex strings)
- pub pubkeys: Vec<String>,
- /// Refund keys (as hex strings)
- pub refund_keys: Vec<String>,
- /// Number of signatures required (default 1)
- pub num_sigs: Option<u64>,
- /// Signature flag (0 = SigInputs, 1 = SigAll)
- pub sig_flag: u8,
- /// Number of refund signatures required (default 1)
- pub num_sigs_refund: Option<u64>,
- }
- impl From<cdk::nuts::nut11::Conditions> for Conditions {
- fn from(conditions: cdk::nuts::nut11::Conditions) -> Self {
- Self {
- locktime: conditions.locktime,
- pubkeys: conditions
- .pubkeys
- .unwrap_or_default()
- .into_iter()
- .map(|p| p.to_string())
- .collect(),
- refund_keys: conditions
- .refund_keys
- .unwrap_or_default()
- .into_iter()
- .map(|p| p.to_string())
- .collect(),
- num_sigs: conditions.num_sigs,
- sig_flag: match conditions.sig_flag {
- cdk::nuts::nut11::SigFlag::SigInputs => 0,
- cdk::nuts::nut11::SigFlag::SigAll => 1,
- },
- num_sigs_refund: conditions.num_sigs_refund,
- }
- }
- }
- impl TryFrom<Conditions> for cdk::nuts::nut11::Conditions {
- type Error = FfiError;
- fn try_from(conditions: Conditions) -> Result<Self, Self::Error> {
- let pubkeys = if conditions.pubkeys.is_empty() {
- None
- } else {
- Some(
- conditions
- .pubkeys
- .into_iter()
- .map(|s| {
- s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
- msg: format!("Invalid pubkey: {}", e),
- })
- })
- .collect::<Result<Vec<_>, _>>()?,
- )
- };
- let refund_keys = if conditions.refund_keys.is_empty() {
- None
- } else {
- Some(
- conditions
- .refund_keys
- .into_iter()
- .map(|s| {
- s.parse().map_err(|e| FfiError::InvalidCryptographicKey {
- msg: format!("Invalid refund key: {}", e),
- })
- })
- .collect::<Result<Vec<_>, _>>()?,
- )
- };
- let sig_flag = match conditions.sig_flag {
- 0 => cdk::nuts::nut11::SigFlag::SigInputs,
- 1 => cdk::nuts::nut11::SigFlag::SigAll,
- _ => {
- return Err(FfiError::Generic {
- msg: "Invalid sig_flag value".to_string(),
- })
- }
- };
- Ok(Self {
- locktime: conditions.locktime,
- pubkeys,
- refund_keys,
- num_sigs: conditions.num_sigs,
- sig_flag,
- num_sigs_refund: conditions.num_sigs_refund,
- })
- }
- }
- impl Conditions {
- /// Convert Conditions to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode Conditions from JSON string
- #[uniffi::export]
- pub fn decode_conditions(json: String) -> Result<Conditions, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode Conditions to JSON string
- #[uniffi::export]
- pub fn encode_conditions(conditions: Conditions) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&conditions)?)
- }
- /// FFI-compatible Witness
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
- pub enum Witness {
- /// P2PK Witness
- P2PK {
- /// Signatures
- signatures: Vec<String>,
- },
- /// HTLC Witness
- HTLC {
- /// Preimage
- preimage: String,
- /// Optional signatures
- signatures: Option<Vec<String>>,
- },
- }
- impl From<cdk::nuts::Witness> for Witness {
- fn from(witness: cdk::nuts::Witness) -> Self {
- match witness {
- cdk::nuts::Witness::P2PKWitness(p2pk) => Self::P2PK {
- signatures: p2pk.signatures,
- },
- cdk::nuts::Witness::HTLCWitness(htlc) => Self::HTLC {
- preimage: htlc.preimage,
- signatures: htlc.signatures,
- },
- }
- }
- }
- impl From<Witness> for cdk::nuts::Witness {
- fn from(witness: Witness) -> Self {
- match witness {
- Witness::P2PK { signatures } => {
- Self::P2PKWitness(cdk::nuts::nut11::P2PKWitness { signatures })
- }
- Witness::HTLC {
- preimage,
- signatures,
- } => Self::HTLCWitness(cdk::nuts::nut14::HTLCWitness {
- preimage,
- signatures,
- }),
- }
- }
- }
- /// FFI-compatible SpendingConditions
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Enum)]
- pub enum SpendingConditions {
- /// P2PK (Pay to Public Key) conditions
- P2PK {
- /// The public key (as hex string)
- pubkey: String,
- /// Additional conditions
- conditions: Option<Conditions>,
- },
- /// HTLC (Hash Time Locked Contract) conditions
- HTLC {
- /// Hash of the preimage (as hex string)
- hash: String,
- /// Additional conditions
- conditions: Option<Conditions>,
- },
- }
- impl From<cdk::nuts::SpendingConditions> for SpendingConditions {
- fn from(spending_conditions: cdk::nuts::SpendingConditions) -> Self {
- match spending_conditions {
- cdk::nuts::SpendingConditions::P2PKConditions { data, conditions } => Self::P2PK {
- pubkey: data.to_string(),
- conditions: conditions.map(Into::into),
- },
- cdk::nuts::SpendingConditions::HTLCConditions { data, conditions } => Self::HTLC {
- hash: data.to_string(),
- conditions: conditions.map(Into::into),
- },
- }
- }
- }
- /// FFI-compatible Transaction
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct Transaction {
- /// Transaction ID
- pub id: TransactionId,
- /// Mint URL
- pub mint_url: MintUrl,
- /// Transaction direction
- pub direction: TransactionDirection,
- /// Amount
- pub amount: Amount,
- /// Fee
- pub fee: Amount,
- /// Currency Unit
- pub unit: CurrencyUnit,
- /// Proof Ys (Y values from proofs)
- pub ys: Vec<PublicKey>,
- /// Unix timestamp
- pub timestamp: u64,
- /// Memo
- pub memo: Option<String>,
- /// User-defined metadata
- pub metadata: HashMap<String, String>,
- /// Quote ID if this is a mint or melt transaction
- pub quote_id: Option<String>,
- }
- impl From<cdk::wallet::types::Transaction> for Transaction {
- fn from(tx: cdk::wallet::types::Transaction) -> Self {
- Self {
- id: tx.id().into(),
- mint_url: tx.mint_url.into(),
- direction: tx.direction.into(),
- amount: tx.amount.into(),
- fee: tx.fee.into(),
- unit: tx.unit.into(),
- ys: tx.ys.into_iter().map(Into::into).collect(),
- timestamp: tx.timestamp,
- memo: tx.memo,
- metadata: tx.metadata,
- quote_id: tx.quote_id,
- }
- }
- }
- /// Convert FFI Transaction to CDK Transaction
- impl TryFrom<Transaction> for cdk::wallet::types::Transaction {
- type Error = FfiError;
- fn try_from(tx: Transaction) -> Result<Self, Self::Error> {
- let cdk_ys: Result<Vec<cdk::nuts::PublicKey>, _> =
- tx.ys.into_iter().map(|pk| pk.try_into()).collect();
- let cdk_ys = cdk_ys?;
- Ok(Self {
- mint_url: tx.mint_url.try_into()?,
- direction: tx.direction.into(),
- amount: tx.amount.into(),
- fee: tx.fee.into(),
- unit: tx.unit.into(),
- ys: cdk_ys,
- timestamp: tx.timestamp,
- memo: tx.memo,
- metadata: tx.metadata,
- quote_id: tx.quote_id,
- })
- }
- }
- impl Transaction {
- /// Convert Transaction to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode Transaction from JSON string
- #[uniffi::export]
- pub fn decode_transaction(json: String) -> Result<Transaction, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode Transaction to JSON string
- #[uniffi::export]
- pub fn encode_transaction(transaction: Transaction) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&transaction)?)
- }
- /// FFI-compatible TransactionDirection
- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
- pub enum TransactionDirection {
- /// Incoming transaction (i.e., receive or mint)
- Incoming,
- /// Outgoing transaction (i.e., send or melt)
- Outgoing,
- }
- impl From<cdk::wallet::types::TransactionDirection> for TransactionDirection {
- fn from(direction: cdk::wallet::types::TransactionDirection) -> Self {
- match direction {
- cdk::wallet::types::TransactionDirection::Incoming => TransactionDirection::Incoming,
- cdk::wallet::types::TransactionDirection::Outgoing => TransactionDirection::Outgoing,
- }
- }
- }
- impl From<TransactionDirection> for cdk::wallet::types::TransactionDirection {
- fn from(direction: TransactionDirection) -> Self {
- match direction {
- TransactionDirection::Incoming => cdk::wallet::types::TransactionDirection::Incoming,
- TransactionDirection::Outgoing => cdk::wallet::types::TransactionDirection::Outgoing,
- }
- }
- }
- /// FFI-compatible TransactionId
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct TransactionId {
- /// Hex-encoded transaction ID (64 characters)
- pub hex: String,
- }
- impl TransactionId {
- /// Create a new TransactionId from hex string
- pub fn from_hex(hex: String) -> Result<Self, FfiError> {
- // Validate hex string length (should be 64 characters for 32 bytes)
- if hex.len() != 64 {
- return Err(FfiError::InvalidHex {
- msg: "Transaction ID hex must be exactly 64 characters (32 bytes)".to_string(),
- });
- }
- // Validate hex format
- if !hex.chars().all(|c| c.is_ascii_hexdigit()) {
- return Err(FfiError::InvalidHex {
- msg: "Transaction ID hex contains invalid characters".to_string(),
- });
- }
- Ok(Self { hex })
- }
- /// Create from proofs
- pub fn from_proofs(proofs: &Proofs) -> Result<Self, FfiError> {
- let cdk_proofs: Vec<cdk::nuts::Proof> = proofs.iter().map(|p| p.inner.clone()).collect();
- let id = cdk::wallet::types::TransactionId::from_proofs(cdk_proofs)?;
- Ok(Self {
- hex: id.to_string(),
- })
- }
- }
- impl From<cdk::wallet::types::TransactionId> for TransactionId {
- fn from(id: cdk::wallet::types::TransactionId) -> Self {
- Self {
- hex: id.to_string(),
- }
- }
- }
- impl TryFrom<TransactionId> for cdk::wallet::types::TransactionId {
- type Error = FfiError;
- fn try_from(id: TransactionId) -> Result<Self, Self::Error> {
- cdk::wallet::types::TransactionId::from_hex(&id.hex)
- .map_err(|e| FfiError::InvalidHex { msg: e.to_string() })
- }
- }
- /// FFI-compatible AuthProof
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct AuthProof {
- /// Keyset ID
- pub keyset_id: String,
- /// Secret message
- pub secret: String,
- /// Unblinded signature (C)
- pub c: String,
- /// Y value (hash_to_curve of secret)
- pub y: String,
- }
- impl From<cdk::nuts::AuthProof> for AuthProof {
- fn from(auth_proof: cdk::nuts::AuthProof) -> Self {
- Self {
- keyset_id: auth_proof.keyset_id.to_string(),
- secret: auth_proof.secret.to_string(),
- c: auth_proof.c.to_string(),
- y: auth_proof
- .y()
- .map(|y| y.to_string())
- .unwrap_or_else(|_| "".to_string()),
- }
- }
- }
- impl TryFrom<AuthProof> for cdk::nuts::AuthProof {
- type Error = FfiError;
- fn try_from(auth_proof: AuthProof) -> Result<Self, Self::Error> {
- use std::str::FromStr;
- Ok(Self {
- keyset_id: cdk::nuts::Id::from_str(&auth_proof.keyset_id)
- .map_err(|e| FfiError::Serialization { msg: e.to_string() })?,
- secret: {
- use std::str::FromStr;
- cdk::secret::Secret::from_str(&auth_proof.secret)
- .map_err(|e| FfiError::Serialization { msg: e.to_string() })?
- },
- c: cdk::nuts::PublicKey::from_str(&auth_proof.c)
- .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?,
- dleq: None, // FFI doesn't expose DLEQ proofs for simplicity
- })
- }
- }
- impl AuthProof {
- /// Convert AuthProof to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode AuthProof from JSON string
- #[uniffi::export]
- pub fn decode_auth_proof(json: String) -> Result<AuthProof, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode AuthProof to JSON string
- #[uniffi::export]
- pub fn encode_auth_proof(proof: AuthProof) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&proof)?)
- }
- impl TryFrom<SpendingConditions> for cdk::nuts::SpendingConditions {
- type Error = FfiError;
- fn try_from(spending_conditions: SpendingConditions) -> Result<Self, Self::Error> {
- match spending_conditions {
- SpendingConditions::P2PK { pubkey, conditions } => {
- let pubkey = pubkey
- .parse()
- .map_err(|e| FfiError::InvalidCryptographicKey {
- msg: format!("Invalid pubkey: {}", e),
- })?;
- let conditions = conditions.map(|c| c.try_into()).transpose()?;
- Ok(Self::P2PKConditions {
- data: pubkey,
- conditions,
- })
- }
- SpendingConditions::HTLC { hash, conditions } => {
- let hash = hash
- .parse()
- .map_err(|e| FfiError::InvalidCryptographicKey {
- msg: format!("Invalid hash: {}", e),
- })?;
- let conditions = conditions.map(|c| c.try_into()).transpose()?;
- Ok(Self::HTLCConditions {
- data: hash,
- conditions,
- })
- }
- }
- }
- }
- /// FFI-compatible SubscriptionKind
- #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
- pub enum SubscriptionKind {
- /// Bolt 11 Melt Quote
- Bolt11MeltQuote,
- /// Bolt 11 Mint Quote
- Bolt11MintQuote,
- /// Bolt 12 Mint Quote
- Bolt12MintQuote,
- /// Proof State
- ProofState,
- }
- impl From<SubscriptionKind> for cdk::nuts::nut17::Kind {
- fn from(kind: SubscriptionKind) -> Self {
- match kind {
- SubscriptionKind::Bolt11MeltQuote => cdk::nuts::nut17::Kind::Bolt11MeltQuote,
- SubscriptionKind::Bolt11MintQuote => cdk::nuts::nut17::Kind::Bolt11MintQuote,
- SubscriptionKind::Bolt12MintQuote => cdk::nuts::nut17::Kind::Bolt12MintQuote,
- SubscriptionKind::ProofState => cdk::nuts::nut17::Kind::ProofState,
- }
- }
- }
- impl From<cdk::nuts::nut17::Kind> for SubscriptionKind {
- fn from(kind: cdk::nuts::nut17::Kind) -> Self {
- match kind {
- cdk::nuts::nut17::Kind::Bolt11MeltQuote => SubscriptionKind::Bolt11MeltQuote,
- cdk::nuts::nut17::Kind::Bolt11MintQuote => SubscriptionKind::Bolt11MintQuote,
- cdk::nuts::nut17::Kind::Bolt12MintQuote => SubscriptionKind::Bolt12MintQuote,
- cdk::nuts::nut17::Kind::ProofState => SubscriptionKind::ProofState,
- }
- }
- }
- /// FFI-compatible SubscribeParams
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct SubscribeParams {
- /// Subscription kind
- pub kind: SubscriptionKind,
- /// Filters
- pub filters: Vec<String>,
- /// Subscription ID (optional, will be generated if not provided)
- pub id: Option<String>,
- }
- impl From<SubscribeParams> for cdk::nuts::nut17::Params<cdk::pub_sub::SubId> {
- fn from(params: SubscribeParams) -> Self {
- let sub_id = params
- .id
- .map(|id| SubId::from(id.as_str()))
- .unwrap_or_else(|| {
- // Generate a random ID
- let uuid = uuid::Uuid::new_v4();
- SubId::from(uuid.to_string().as_str())
- });
- cdk::nuts::nut17::Params {
- kind: params.kind.into(),
- filters: params.filters,
- id: sub_id,
- }
- }
- }
- impl SubscribeParams {
- /// Convert SubscribeParams to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode SubscribeParams from JSON string
- #[uniffi::export]
- pub fn decode_subscribe_params(json: String) -> Result<SubscribeParams, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode SubscribeParams to JSON string
- #[uniffi::export]
- pub fn encode_subscribe_params(params: SubscribeParams) -> Result<String, FfiError> {
- Ok(serde_json::to_string(¶ms)?)
- }
- /// FFI-compatible ActiveSubscription
- #[derive(uniffi::Object)]
- pub struct ActiveSubscription {
- inner: std::sync::Arc<tokio::sync::Mutex<cdk::wallet::subscription::ActiveSubscription>>,
- pub sub_id: String,
- }
- impl ActiveSubscription {
- pub(crate) fn new(
- inner: cdk::wallet::subscription::ActiveSubscription,
- sub_id: String,
- ) -> Self {
- Self {
- inner: std::sync::Arc::new(tokio::sync::Mutex::new(inner)),
- sub_id,
- }
- }
- }
- #[uniffi::export(async_runtime = "tokio")]
- impl ActiveSubscription {
- /// Get the subscription ID
- pub fn id(&self) -> String {
- self.sub_id.clone()
- }
- /// Receive the next notification
- pub async fn recv(&self) -> Result<NotificationPayload, FfiError> {
- let mut guard = self.inner.lock().await;
- guard
- .recv()
- .await
- .ok_or(FfiError::Generic {
- msg: "Subscription closed".to_string(),
- })
- .map(Into::into)
- }
- /// Try to receive a notification without blocking
- pub async fn try_recv(&self) -> Result<Option<NotificationPayload>, FfiError> {
- let mut guard = self.inner.lock().await;
- guard
- .try_recv()
- .map(|opt| opt.map(Into::into))
- .map_err(|e| FfiError::Generic {
- msg: format!("Failed to receive notification: {}", e),
- })
- }
- }
- /// FFI-compatible NotificationPayload
- #[derive(Debug, Clone, uniffi::Enum)]
- pub enum NotificationPayload {
- /// Proof state update
- ProofState { proof_states: Vec<ProofStateUpdate> },
- /// Mint quote update
- MintQuoteUpdate {
- quote: std::sync::Arc<MintQuoteBolt11Response>,
- },
- /// Melt quote update
- MeltQuoteUpdate {
- quote: std::sync::Arc<MeltQuoteBolt11Response>,
- },
- }
- impl From<cdk::nuts::NotificationPayload<String>> for NotificationPayload {
- fn from(payload: cdk::nuts::NotificationPayload<String>) -> Self {
- match payload {
- cdk::nuts::NotificationPayload::ProofState(states) => NotificationPayload::ProofState {
- proof_states: vec![states.into()],
- },
- cdk::nuts::NotificationPayload::MintQuoteBolt11Response(quote_resp) => {
- NotificationPayload::MintQuoteUpdate {
- quote: std::sync::Arc::new(quote_resp.into()),
- }
- }
- cdk::nuts::NotificationPayload::MeltQuoteBolt11Response(quote_resp) => {
- NotificationPayload::MeltQuoteUpdate {
- quote: std::sync::Arc::new(quote_resp.into()),
- }
- }
- _ => {
- // For now, handle other notification types as empty ProofState
- NotificationPayload::ProofState {
- proof_states: vec![],
- }
- }
- }
- }
- }
- /// FFI-compatible ProofStateUpdate
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct ProofStateUpdate {
- /// Y value (hash_to_curve of secret)
- pub y: String,
- /// Current state
- pub state: ProofState,
- /// Optional witness data
- pub witness: Option<String>,
- }
- impl From<cdk::nuts::nut07::ProofState> for ProofStateUpdate {
- fn from(proof_state: cdk::nuts::nut07::ProofState) -> Self {
- Self {
- y: proof_state.y.to_string(),
- state: proof_state.state.into(),
- witness: proof_state.witness.map(|w| format!("{:?}", w)),
- }
- }
- }
- impl ProofStateUpdate {
- /// Convert ProofStateUpdate to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode ProofStateUpdate from JSON string
- #[uniffi::export]
- pub fn decode_proof_state_update(json: String) -> Result<ProofStateUpdate, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode ProofStateUpdate to JSON string
- #[uniffi::export]
- pub fn encode_proof_state_update(update: ProofStateUpdate) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&update)?)
- }
- /// FFI-compatible KeySetInfo
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct KeySetInfo {
- pub id: String,
- pub unit: CurrencyUnit,
- pub active: bool,
- /// Input fee per thousand (ppk)
- pub input_fee_ppk: u64,
- }
- impl From<cdk::nuts::KeySetInfo> for KeySetInfo {
- fn from(keyset: cdk::nuts::KeySetInfo) -> Self {
- Self {
- id: keyset.id.to_string(),
- unit: keyset.unit.into(),
- active: keyset.active,
- input_fee_ppk: keyset.input_fee_ppk,
- }
- }
- }
- impl From<KeySetInfo> for cdk::nuts::KeySetInfo {
- fn from(keyset: KeySetInfo) -> Self {
- use std::str::FromStr;
- Self {
- id: cdk::nuts::Id::from_str(&keyset.id).unwrap(),
- unit: keyset.unit.into(),
- active: keyset.active,
- final_expiry: None,
- input_fee_ppk: keyset.input_fee_ppk,
- }
- }
- }
- impl KeySetInfo {
- /// Convert KeySetInfo to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode KeySetInfo from JSON string
- #[uniffi::export]
- pub fn decode_key_set_info(json: String) -> Result<KeySetInfo, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode KeySetInfo to JSON string
- #[uniffi::export]
- pub fn encode_key_set_info(info: KeySetInfo) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&info)?)
- }
- /// FFI-compatible PublicKey
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct PublicKey {
- /// Hex-encoded public key
- pub hex: String,
- }
- impl From<cdk::nuts::PublicKey> for PublicKey {
- fn from(key: cdk::nuts::PublicKey) -> Self {
- Self {
- hex: key.to_string(),
- }
- }
- }
- impl TryFrom<PublicKey> for cdk::nuts::PublicKey {
- type Error = FfiError;
- fn try_from(key: PublicKey) -> Result<Self, Self::Error> {
- key.hex
- .parse()
- .map_err(|e| FfiError::InvalidCryptographicKey {
- msg: format!("Invalid public key: {}", e),
- })
- }
- }
- /// FFI-compatible Keys (simplified - contains only essential info)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct Keys {
- /// Keyset ID
- pub id: String,
- /// Currency unit
- pub unit: CurrencyUnit,
- /// Map of amount to public key hex (simplified from BTreeMap)
- pub keys: HashMap<u64, String>,
- }
- impl From<cdk::nuts::Keys> for Keys {
- fn from(keys: cdk::nuts::Keys) -> Self {
- // Keys doesn't have id and unit - we'll need to get these from context
- // For now, use placeholder values
- Self {
- id: "unknown".to_string(), // This should come from KeySet
- unit: CurrencyUnit::Sat, // This should come from KeySet
- keys: keys
- .keys()
- .iter()
- .map(|(amount, pubkey)| (u64::from(*amount), pubkey.to_string()))
- .collect(),
- }
- }
- }
- impl TryFrom<Keys> for cdk::nuts::Keys {
- type Error = FfiError;
- fn try_from(keys: Keys) -> Result<Self, Self::Error> {
- use std::collections::BTreeMap;
- use std::str::FromStr;
- // Convert the HashMap to BTreeMap with proper types
- let mut keys_map = BTreeMap::new();
- for (amount_u64, pubkey_hex) in keys.keys {
- let amount = cdk::Amount::from(amount_u64);
- let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
- .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
- keys_map.insert(amount, pubkey);
- }
- Ok(cdk::nuts::Keys::new(keys_map))
- }
- }
- impl Keys {
- /// Convert Keys to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode Keys from JSON string
- #[uniffi::export]
- pub fn decode_keys(json: String) -> Result<Keys, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode Keys to JSON string
- #[uniffi::export]
- pub fn encode_keys(keys: Keys) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&keys)?)
- }
- /// FFI-compatible KeySet
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- pub struct KeySet {
- /// Keyset ID
- pub id: String,
- /// Currency unit
- pub unit: CurrencyUnit,
- /// The keys (map of amount to public key hex)
- pub keys: HashMap<u64, String>,
- /// Optional expiry timestamp
- pub final_expiry: Option<u64>,
- }
- impl From<cdk::nuts::KeySet> for KeySet {
- fn from(keyset: cdk::nuts::KeySet) -> Self {
- Self {
- id: keyset.id.to_string(),
- unit: keyset.unit.into(),
- keys: keyset
- .keys
- .keys()
- .iter()
- .map(|(amount, pubkey)| (u64::from(*amount), pubkey.to_string()))
- .collect(),
- final_expiry: keyset.final_expiry,
- }
- }
- }
- impl TryFrom<KeySet> for cdk::nuts::KeySet {
- type Error = FfiError;
- fn try_from(keyset: KeySet) -> Result<Self, Self::Error> {
- use std::collections::BTreeMap;
- use std::str::FromStr;
- // Convert id
- let id = cdk::nuts::Id::from_str(&keyset.id)
- .map_err(|e| FfiError::Serialization { msg: e.to_string() })?;
- // Convert unit
- let unit: cdk::nuts::CurrencyUnit = keyset.unit.into();
- // Convert keys
- let mut keys_map = BTreeMap::new();
- for (amount_u64, pubkey_hex) in keyset.keys {
- let amount = cdk::Amount::from(amount_u64);
- let pubkey = cdk::nuts::PublicKey::from_str(&pubkey_hex)
- .map_err(|e| FfiError::InvalidCryptographicKey { msg: e.to_string() })?;
- keys_map.insert(amount, pubkey);
- }
- let keys = cdk::nuts::Keys::new(keys_map);
- Ok(cdk::nuts::KeySet {
- id,
- unit,
- keys,
- final_expiry: keyset.final_expiry,
- })
- }
- }
- impl KeySet {
- /// Convert KeySet to JSON string
- pub fn to_json(&self) -> Result<String, FfiError> {
- Ok(serde_json::to_string(self)?)
- }
- }
- /// Decode KeySet from JSON string
- #[uniffi::export]
- pub fn decode_key_set(json: String) -> Result<KeySet, FfiError> {
- Ok(serde_json::from_str(&json)?)
- }
- /// Encode KeySet to JSON string
- #[uniffi::export]
- pub fn encode_key_set(keyset: KeySet) -> Result<String, FfiError> {
- Ok(serde_json::to_string(&keyset)?)
- }
- /// FFI-compatible ProofInfo
- #[derive(Debug, Clone, uniffi::Record)]
- pub struct ProofInfo {
- /// Proof
- pub proof: std::sync::Arc<Proof>,
- /// Y value (hash_to_curve of secret)
- pub y: PublicKey,
- /// Mint URL
- pub mint_url: MintUrl,
- /// Proof state
- pub state: ProofState,
- /// Proof Spending Conditions
- pub spending_condition: Option<SpendingConditions>,
- /// Currency unit
- pub unit: CurrencyUnit,
- }
- impl From<cdk::types::ProofInfo> for ProofInfo {
- fn from(info: cdk::types::ProofInfo) -> Self {
- Self {
- proof: std::sync::Arc::new(info.proof.into()),
- y: info.y.into(),
- mint_url: info.mint_url.into(),
- state: info.state.into(),
- spending_condition: info.spending_condition.map(Into::into),
- unit: info.unit.into(),
- }
- }
- }
- /// Decode ProofInfo from JSON string
- #[uniffi::export]
- pub fn decode_proof_info(json: String) -> Result<ProofInfo, FfiError> {
- let info: cdk::types::ProofInfo = serde_json::from_str(&json)?;
- Ok(info.into())
- }
- /// Encode ProofInfo to JSON string
- #[uniffi::export]
- pub fn encode_proof_info(info: ProofInfo) -> Result<String, FfiError> {
- // Convert to cdk::types::ProofInfo for serialization
- let cdk_info = cdk::types::ProofInfo {
- proof: info.proof.inner.clone(),
- y: info.y.try_into()?,
- mint_url: info.mint_url.try_into()?,
- state: info.state.into(),
- spending_condition: info.spending_condition.and_then(|c| c.try_into().ok()),
- unit: info.unit.into(),
- };
- Ok(serde_json::to_string(&cdk_info)?)
- }
- // State enum removed - using ProofState instead
- /// FFI-compatible Id (for keyset IDs)
- #[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
- #[serde(transparent)]
- pub struct Id {
- pub hex: String,
- }
- impl From<cdk::nuts::Id> for Id {
- fn from(id: cdk::nuts::Id) -> Self {
- Self {
- hex: id.to_string(),
- }
- }
- }
- impl From<Id> for cdk::nuts::Id {
- fn from(id: Id) -> Self {
- use std::str::FromStr;
- Self::from_str(&id.hex).unwrap()
- }
- }
|