Skip to main content

cdk_ffi/types/
subscription.rs

1//! Subscription-related FFI types
2use std::sync::Arc;
3
4use cdk::event::MintEvent;
5use serde::{Deserialize, Serialize};
6
7use super::proof::ProofStateUpdate;
8use super::quote::{
9    MeltQuoteBolt11Response, MeltQuoteOnchainResponse, MintQuoteBolt11Response,
10    MintQuoteOnchainResponse,
11};
12use crate::error::FfiError;
13
14/// FFI-compatible SubscriptionKind
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, uniffi::Enum)]
16pub enum SubscriptionKind {
17    /// Bolt 11 Melt Quote
18    Bolt11MeltQuote,
19    /// Bolt 11 Mint Quote
20    Bolt11MintQuote,
21    /// Bolt 12 Mint Quote
22    Bolt12MintQuote,
23    /// Bolt 12 Melt Quote
24    Bolt12MeltQuote,
25    /// Onchain Mint Quote
26    OnchainMintQuote,
27    /// Onchain Melt Quote
28    OnchainMeltQuote,
29    /// Proof State
30    ProofState,
31}
32
33impl From<SubscriptionKind> for cdk::nuts::nut17::Kind {
34    fn from(kind: SubscriptionKind) -> Self {
35        match kind {
36            SubscriptionKind::Bolt11MeltQuote => cdk::nuts::nut17::Kind::Bolt11MeltQuote,
37            SubscriptionKind::Bolt11MintQuote => cdk::nuts::nut17::Kind::Bolt11MintQuote,
38            SubscriptionKind::Bolt12MintQuote => cdk::nuts::nut17::Kind::Bolt12MintQuote,
39            SubscriptionKind::Bolt12MeltQuote => cdk::nuts::nut17::Kind::Bolt12MeltQuote,
40            SubscriptionKind::OnchainMintQuote => cdk::nuts::nut17::Kind::OnchainMintQuote,
41            SubscriptionKind::OnchainMeltQuote => cdk::nuts::nut17::Kind::OnchainMeltQuote,
42            SubscriptionKind::ProofState => cdk::nuts::nut17::Kind::ProofState,
43        }
44    }
45}
46
47impl From<cdk::nuts::nut17::Kind> for SubscriptionKind {
48    fn from(kind: cdk::nuts::nut17::Kind) -> Self {
49        match kind {
50            cdk::nuts::nut17::Kind::Bolt11MeltQuote => SubscriptionKind::Bolt11MeltQuote,
51            cdk::nuts::nut17::Kind::Bolt11MintQuote => SubscriptionKind::Bolt11MintQuote,
52            cdk::nuts::nut17::Kind::Bolt12MintQuote => SubscriptionKind::Bolt12MintQuote,
53            cdk::nuts::nut17::Kind::Bolt12MeltQuote => SubscriptionKind::Bolt12MeltQuote,
54            cdk::nuts::nut17::Kind::OnchainMintQuote => SubscriptionKind::OnchainMintQuote,
55            cdk::nuts::nut17::Kind::OnchainMeltQuote => SubscriptionKind::OnchainMeltQuote,
56            cdk::nuts::nut17::Kind::Custom(_) => {
57                panic!("Custom subscription kind not supported in FFI")
58            }
59            cdk::nuts::nut17::Kind::ProofState => SubscriptionKind::ProofState,
60        }
61    }
62}
63
64/// FFI-compatible SubscribeParams
65#[derive(Debug, Clone, Serialize, Deserialize, uniffi::Record)]
66pub struct SubscribeParams {
67    /// Subscription kind
68    pub kind: SubscriptionKind,
69    /// Filters
70    pub filters: Vec<String>,
71    /// Subscription ID (optional, will be generated if not provided)
72    pub id: Option<String>,
73}
74
75impl From<SubscribeParams> for cdk::nuts::nut17::Params<Arc<String>> {
76    fn from(params: SubscribeParams) -> Self {
77        let sub_id = params.id.unwrap_or_else(|| {
78            // Generate a random ID
79            uuid::Uuid::new_v4().to_string()
80        });
81
82        cdk::nuts::nut17::Params {
83            kind: params.kind.into(),
84            filters: params.filters,
85            id: Arc::new(sub_id),
86        }
87    }
88}
89
90impl SubscribeParams {
91    /// Convert SubscribeParams to JSON string
92    pub fn to_json(&self) -> Result<String, FfiError> {
93        Ok(serde_json::to_string(self)?)
94    }
95}
96
97/// Decode SubscribeParams from JSON string
98#[uniffi::export]
99pub fn decode_subscribe_params(json: String) -> Result<SubscribeParams, FfiError> {
100    Ok(serde_json::from_str(&json)?)
101}
102
103/// Encode SubscribeParams to JSON string
104#[uniffi::export]
105pub fn encode_subscribe_params(params: SubscribeParams) -> Result<String, FfiError> {
106    Ok(serde_json::to_string(&params)?)
107}
108
109/// FFI-compatible ActiveSubscription
110#[derive(uniffi::Object)]
111pub struct ActiveSubscription {
112    inner: std::sync::Arc<tokio::sync::Mutex<cdk::wallet::subscription::ActiveSubscription>>,
113    pub sub_id: String,
114}
115
116impl ActiveSubscription {
117    pub(crate) fn new(
118        inner: cdk::wallet::subscription::ActiveSubscription,
119        sub_id: String,
120    ) -> Self {
121        Self {
122            inner: std::sync::Arc::new(tokio::sync::Mutex::new(inner)),
123            sub_id,
124        }
125    }
126}
127
128#[uniffi::export(async_runtime = "tokio")]
129impl ActiveSubscription {
130    /// Get the subscription ID
131    pub fn id(&self) -> String {
132        self.sub_id.clone()
133    }
134
135    /// Receive the next notification
136    pub async fn recv(&self) -> Result<NotificationPayload, FfiError> {
137        let mut guard = self.inner.lock().await;
138        guard
139            .recv()
140            .await
141            .ok_or_else(|| FfiError::internal("Subscription closed"))
142            .map(Into::into)
143    }
144
145    /// Try to receive a notification without blocking
146    pub async fn try_recv(&self) -> Result<Option<NotificationPayload>, FfiError> {
147        let mut guard = self.inner.lock().await;
148        Ok(guard.try_recv().map(Into::into))
149    }
150}
151
152/// FFI-compatible NotificationPayload
153#[derive(Debug, Clone, uniffi::Enum)]
154pub enum NotificationPayload {
155    /// Proof state update
156    ProofState { proof_states: Vec<ProofStateUpdate> },
157    /// Mint quote update
158    MintQuoteUpdate { quote: MintQuoteBolt11Response },
159    /// Melt quote update
160    MeltQuoteUpdate { quote: MeltQuoteBolt11Response },
161    /// Onchain mint quote update
162    MintQuoteOnchainUpdate { quote: MintQuoteOnchainResponse },
163    /// Onchain melt quote update
164    MeltQuoteOnchainUpdate { quote: MeltQuoteOnchainResponse },
165}
166
167impl From<MintEvent<String>> for NotificationPayload {
168    fn from(payload: MintEvent<String>) -> Self {
169        match payload.into() {
170            cdk::nuts::NotificationPayload::ProofState(states) => NotificationPayload::ProofState {
171                proof_states: vec![states.into()],
172            },
173            cdk::nuts::NotificationPayload::MintQuoteBolt11Response(quote_resp) => {
174                NotificationPayload::MintQuoteUpdate {
175                    quote: quote_resp.into(),
176                }
177            }
178            cdk::nuts::NotificationPayload::MeltQuoteBolt11Response(quote_resp) => {
179                NotificationPayload::MeltQuoteUpdate {
180                    quote: quote_resp.into(),
181                }
182            }
183            cdk::nuts::NotificationPayload::MintQuoteOnchainResponse(quote_resp) => {
184                NotificationPayload::MintQuoteOnchainUpdate {
185                    quote: quote_resp.into(),
186                }
187            }
188            cdk::nuts::NotificationPayload::MeltQuoteOnchainResponse(quote_resp) => {
189                NotificationPayload::MeltQuoteOnchainUpdate {
190                    quote: quote_resp.into(),
191                }
192            }
193            _ => {
194                // For now, handle other notification types as empty ProofState
195                NotificationPayload::ProofState {
196                    proof_states: vec![],
197                }
198            }
199        }
200    }
201}