| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- //! FFI bindings for the NpubCash client SDK
- //!
- //! This module provides FFI-compatible bindings for interacting with the NpubCash API.
- //! The client can be used standalone without requiring a wallet.
- use std::sync::Arc;
- use cdk_npubcash::{JwtAuthProvider, NpubCashClient as CdkNpubCashClient};
- use crate::error::FfiError;
- use crate::types::MintQuote;
- /// FFI-compatible NpubCash client
- ///
- /// This client provides access to the NpubCash API for fetching quotes
- /// and managing user settings.
- #[derive(uniffi::Object)]
- pub struct NpubCashClient {
- inner: Arc<CdkNpubCashClient>,
- }
- #[uniffi::export(async_runtime = "tokio")]
- impl NpubCashClient {
- /// Create a new NpubCash client
- ///
- /// # Arguments
- ///
- /// * `base_url` - Base URL of the NpubCash service (e.g., "https://npub.cash")
- /// * `nostr_secret_key` - Nostr secret key for authentication. Accepts either:
- /// - Hex-encoded secret key (64 characters)
- /// - Bech32 `nsec` format (e.g., "nsec1...")
- ///
- /// # Errors
- ///
- /// Returns an error if the secret key is invalid or cannot be parsed
- #[uniffi::constructor]
- pub fn new(base_url: String, nostr_secret_key: String) -> Result<Self, FfiError> {
- let keys = parse_nostr_secret_key(&nostr_secret_key)?;
- let auth_provider = Arc::new(JwtAuthProvider::new(base_url.clone(), keys));
- let client = CdkNpubCashClient::new(base_url, auth_provider);
- Ok(Self {
- inner: Arc::new(client),
- })
- }
- /// Fetch quotes from NpubCash
- ///
- /// # Arguments
- ///
- /// * `since` - Optional Unix timestamp to fetch quotes from. If `None`, fetches all quotes.
- ///
- /// # Returns
- ///
- /// A list of quotes from the NpubCash service. The client automatically handles
- /// pagination to fetch all available quotes.
- ///
- /// # Errors
- ///
- /// Returns an error if the API request fails or authentication fails
- pub async fn get_quotes(&self, since: Option<u64>) -> Result<Vec<NpubCashQuote>, FfiError> {
- let quotes = self
- .inner
- .get_quotes(since)
- .await
- .map_err(|e| FfiError::internal(e.to_string()))?;
- Ok(quotes.into_iter().map(Into::into).collect())
- }
- /// Set the mint URL for the user on the NpubCash server
- ///
- /// Updates the default mint URL used by the NpubCash server when creating quotes.
- ///
- /// # Arguments
- ///
- /// * `mint_url` - URL of the Cashu mint to use (e.g., "https://mint.example.com")
- ///
- /// # Errors
- ///
- /// Returns an error if the API request fails or authentication fails
- pub async fn set_mint_url(&self, mint_url: String) -> Result<NpubCashUserResponse, FfiError> {
- let response = self
- .inner
- .set_mint_url(mint_url)
- .await
- .map_err(|e| FfiError::internal(e.to_string()))?;
- Ok(response.into())
- }
- }
- /// A quote from the NpubCash service
- #[derive(Debug, Clone, uniffi::Record)]
- pub struct NpubCashQuote {
- /// Unique identifier for the quote
- pub id: String,
- /// Amount in the specified unit
- pub amount: u64,
- /// Currency or unit for the amount (e.g., "sat")
- pub unit: String,
- /// Unix timestamp when the quote was created
- pub created_at: u64,
- /// Unix timestamp when the quote was paid (if paid)
- pub paid_at: Option<u64>,
- /// Unix timestamp when the quote expires
- pub expires_at: Option<u64>,
- /// Mint URL associated with the quote
- pub mint_url: Option<String>,
- /// Lightning invoice request
- pub request: Option<String>,
- /// Quote state (e.g., "PAID", "PENDING")
- pub state: Option<String>,
- /// Whether the quote is locked
- pub locked: Option<bool>,
- }
- impl From<cdk_npubcash::Quote> for NpubCashQuote {
- fn from(quote: cdk_npubcash::Quote) -> Self {
- Self {
- id: quote.id,
- amount: quote.amount,
- unit: quote.unit,
- created_at: quote.created_at,
- paid_at: quote.paid_at,
- expires_at: quote.expires_at,
- mint_url: quote.mint_url,
- request: quote.request,
- state: quote.state,
- locked: quote.locked,
- }
- }
- }
- /// Convert a NpubCash quote to a wallet MintQuote
- ///
- /// This allows the quote to be used with the wallet's minting functions.
- /// Note that the resulting MintQuote will not have a secret key set,
- /// which may be required for locked quotes.
- ///
- /// # Arguments
- ///
- /// * `quote` - The NpubCash quote to convert
- ///
- /// # Returns
- ///
- /// A MintQuote that can be used with wallet minting functions
- #[uniffi::export]
- pub fn npubcash_quote_to_mint_quote(quote: NpubCashQuote) -> MintQuote {
- let cdk_quote = cdk_npubcash::Quote {
- id: quote.id,
- amount: quote.amount,
- unit: quote.unit,
- created_at: quote.created_at,
- paid_at: quote.paid_at,
- expires_at: quote.expires_at,
- mint_url: quote.mint_url,
- request: quote.request,
- state: quote.state,
- locked: quote.locked,
- };
- let mint_quote: cdk::wallet::MintQuote = cdk_quote.into();
- mint_quote.into()
- }
- /// Response from updating user settings on NpubCash
- #[derive(Debug, Clone, uniffi::Record)]
- pub struct NpubCashUserResponse {
- /// Whether the request resulted in an error
- pub error: bool,
- /// User's public key
- pub pubkey: String,
- /// Configured mint URL
- pub mint_url: Option<String>,
- /// Whether quotes are locked
- pub lock_quote: bool,
- }
- impl From<cdk_npubcash::UserResponse> for NpubCashUserResponse {
- fn from(response: cdk_npubcash::UserResponse) -> Self {
- Self {
- error: response.error,
- pubkey: response.data.user.pubkey,
- mint_url: response.data.user.mint_url,
- lock_quote: response.data.user.lock_quote,
- }
- }
- }
- /// Derive Nostr keys from a wallet seed
- ///
- /// This function derives the same Nostr keys that a wallet would use for NpubCash
- /// authentication. It takes the first 32 bytes of the seed as the secret key.
- ///
- /// # Arguments
- ///
- /// * `seed` - The wallet seed bytes (must be at least 32 bytes)
- ///
- /// # Returns
- ///
- /// The hex-encoded Nostr secret key that can be used with `NpubCashClient::new()`
- ///
- /// # Errors
- ///
- /// Returns an error if the seed is too short or key derivation fails
- #[uniffi::export]
- pub fn npubcash_derive_secret_key_from_seed(seed: Vec<u8>) -> Result<String, FfiError> {
- if seed.len() < 32 {
- return Err(FfiError::internal(
- "Seed must be at least 32 bytes".to_string(),
- ));
- }
- // Use the first 32 bytes of the seed as the secret key
- let secret_key = nostr_sdk::SecretKey::from_slice(&seed[..32])
- .map_err(|e| FfiError::internal(format!("Failed to derive secret key: {}", e)))?;
- Ok(secret_key.to_secret_hex())
- }
- /// Get the public key for a given Nostr secret key
- ///
- /// # Arguments
- ///
- /// * `nostr_secret_key` - Nostr secret key. Accepts either:
- /// - Hex-encoded secret key (64 characters)
- /// - Bech32 `nsec` format (e.g., "nsec1...")
- ///
- /// # Returns
- ///
- /// The hex-encoded public key
- ///
- /// # Errors
- ///
- /// Returns an error if the secret key is invalid
- #[uniffi::export]
- pub fn npubcash_get_pubkey(nostr_secret_key: String) -> Result<String, FfiError> {
- let keys = parse_nostr_secret_key(&nostr_secret_key)?;
- Ok(keys.public_key().to_hex())
- }
- /// Parse a Nostr secret key from either hex or nsec format
- fn parse_nostr_secret_key(key: &str) -> Result<nostr_sdk::Keys, FfiError> {
- // Try parsing as nsec (bech32) first
- if key.starts_with("nsec") {
- nostr_sdk::Keys::parse(key)
- .map_err(|e| FfiError::internal(format!("Invalid nsec key: {}", e)))
- } else {
- // Try parsing as hex
- let secret_key = nostr_sdk::SecretKey::parse(key)
- .map_err(|e| FfiError::internal(format!("Invalid hex secret key: {}", e)))?;
- Ok(nostr_sdk::Keys::new(secret_key))
- }
- }
|