Skip to main content

cdk_ffi/types/
bip321.rs

1//! FFI-compatible types for BIP 321 payment instruction helpers.
2//!
3//! These types represent the result of parsing a BIP 321 `bitcoin:` URI (or a
4//! standalone payment string) via
5//! [`parse_bip321_payment_instruction`](crate::bip321::parse_bip321_payment_instruction).
6
7use std::sync::Arc;
8
9use cdk_common::bitcoin;
10
11use super::payment_request::PaymentRequest;
12
13/// Bitcoin network for on-chain address validation.
14///
15/// This determines which address prefixes are accepted when parsing a BIP 321
16/// `bitcoin:` URI that contains an on-chain component.
17///
18/// ```text
19/// val parsed = parseBip321PaymentInstruction(
20///     "bitcoin:bc1qar0s...?creq=CREQB1...",
21///     BitcoinNetwork.BITCOIN  // mainnet addresses only
22/// )
23/// ```
24#[derive(Debug, Clone, Copy, PartialEq, Eq, uniffi::Enum)]
25pub enum BitcoinNetwork {
26    /// Bitcoin mainnet (addresses start with `bc1`, `1`, or `3`).
27    Bitcoin,
28    /// Bitcoin testnet (addresses start with `tb1`, `m`, or `n`).
29    Testnet,
30    /// Bitcoin signet (addresses start with `tb1`).
31    Signet,
32    /// Bitcoin regtest (addresses start with `bcrt1`).
33    Regtest,
34}
35
36impl From<BitcoinNetwork> for bitcoin::Network {
37    fn from(network: BitcoinNetwork) -> Self {
38        match network {
39            BitcoinNetwork::Bitcoin => bitcoin::Network::Bitcoin,
40            BitcoinNetwork::Testnet => bitcoin::Network::Testnet,
41            BitcoinNetwork::Signet => bitcoin::Network::Signet,
42            BitcoinNetwork::Regtest => bitcoin::Network::Regtest,
43        }
44    }
45}
46
47impl From<bitcoin::Network> for BitcoinNetwork {
48    fn from(network: bitcoin::Network) -> Self {
49        match network {
50            bitcoin::Network::Bitcoin => BitcoinNetwork::Bitcoin,
51            bitcoin::Network::Testnet => BitcoinNetwork::Testnet,
52            bitcoin::Network::Signet => BitcoinNetwork::Signet,
53            bitcoin::Network::Regtest => BitcoinNetwork::Regtest,
54            _ => BitcoinNetwork::Bitcoin,
55        }
56    }
57}
58
59/// A parsed BIP 321 payment instruction containing all payment methods found.
60///
61/// After parsing, inspect the lists to determine which payment methods are
62/// available and choose the best one for your wallet. A single URI can contain
63/// multiple methods (e.g. cashu + BOLT11 + on-chain) to give the payer options.
64///
65/// # Examples
66///
67/// ```text
68/// // Parse a BIP 321 URI that bundles cashu, BOLT11, and an on-chain address
69/// val parsed = parseBip321PaymentInstruction(
70///     "bitcoin:bc1qar0s...?creq=CREQB1...&lightning=lnbc100n1p..."
71/// )
72///
73/// // Check which payment methods are available and pick one
74/// when {
75///     parsed.cashuRequests.isNotEmpty() -> {
76///         // Prefer ecash: instant settlement, zero fees
77///         val request = parsed.cashuRequests.first()
78///         val id = request.paymentId()         // e.g. "b7a90176"
79///         val amount = request.amount()         // e.g. Amount(10)
80///         val unit = request.unit()             // e.g. CurrencyUnit.Sat
81///         val mints = request.mints()           // acceptable mint URLs
82///         val transports = request.transports() // how to deliver proofs
83///     }
84///     parsed.bolt11Invoices.isNotEmpty() -> {
85///         // Fall back to Lightning BOLT11
86///         val invoice = parsed.bolt11Invoices.first()
87///     }
88///     parsed.bolt12Offers.isNotEmpty() -> {
89///         // Fall back to Lightning BOLT12
90///         val offer = parsed.bolt12Offers.first()
91///     }
92///     parsed.onchainAddresses.isNotEmpty() -> {
93///         // Last resort: on-chain payment
94///         val address = parsed.onchainAddresses.first()
95///     }
96/// }
97///
98/// // Amount info
99/// val msats = parsed.amountMsats           // fixed amount in msats, or null
100/// val flexible = parsed.isConfigurableAmount // true if payer chooses amount
101/// val desc = parsed.description             // URI label/message, or null
102/// ```
103#[derive(Debug, Clone, uniffi::Record)]
104pub struct ParsedPaymentInstruction {
105    /// Cashu NUT-26 payment requests.
106    pub cashu_requests: Vec<Arc<PaymentRequest>>,
107    /// BOLT11 invoice strings.
108    pub bolt11_invoices: Vec<String>,
109    /// BOLT12 offer strings.
110    pub bolt12_offers: Vec<String>,
111    /// On-chain bitcoin addresses.
112    pub onchain_addresses: Vec<String>,
113    /// Description / label / message from the URI.
114    pub description: Option<String>,
115    /// Amount in millisatoshis (if a fixed-amount instruction).
116    pub amount_msats: Option<u64>,
117    /// Whether the amount is configurable (vs fixed).
118    pub is_configurable_amount: bool,
119}
120
121impl From<cdk::wallet::bip321::ParsedPaymentInstruction> for ParsedPaymentInstruction {
122    fn from(parsed: cdk::wallet::bip321::ParsedPaymentInstruction) -> Self {
123        let cashu_requests = parsed
124            .cashu_requests
125            .into_iter()
126            .map(|req| Arc::new(PaymentRequest::from_inner(req)))
127            .collect();
128
129        Self {
130            cashu_requests,
131            bolt11_invoices: parsed.bolt11_invoices,
132            bolt12_offers: parsed.bolt12_offers,
133            onchain_addresses: parsed.onchain_addresses,
134            description: parsed.description,
135            amount_msats: parsed.amount_msats,
136            is_configurable_amount: parsed.is_configurable_amount,
137        }
138    }
139}