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}