1use crate::Error;
5
6#[derive(Clone, uniffi::Record)]
8pub struct ParsedInvoice {
9 pub bolt11: String,
11 pub payee_pubkey: Option<Vec<u8>>,
13 pub payment_hash: Vec<u8>,
15 pub description: Option<String>,
17 pub amount_msat: Option<u64>,
19 pub expiry: u64,
21 pub timestamp: u64,
23}
24
25#[derive(Clone, uniffi::Enum)]
27pub enum InputType {
28 Bolt11 { invoice: ParsedInvoice },
30 NodeId { node_id: String },
32}
33
34pub fn parse_input(input: String) -> Result<InputType, Error> {
39 let trimmed = input.trim();
40 if trimmed.is_empty() {
41 return Err(Error::Other("Empty input".to_string()));
42 }
43
44 let stripped = if let Some(rest) = trimmed.strip_prefix("lightning:") {
46 rest
47 } else if let Some(rest) = trimmed.strip_prefix("LIGHTNING:") {
48 rest
49 } else {
50 trimmed
51 };
52
53 if let Some(input_type) = try_parse_bolt11(stripped) {
55 return input_type;
56 }
57
58 if let Some(input_type) = try_parse_node_id(stripped) {
60 return Ok(input_type);
61 }
62
63 Err(Error::Other("Unrecognized input".to_string()))
64}
65
66fn try_parse_bolt11(input: &str) -> Option<Result<InputType, Error>> {
69 let lower = input.to_lowercase();
70 if !lower.starts_with("lnbc") && !lower.starts_with("lntb") && !lower.starts_with("lnbcrt") {
71 return None;
72 }
73
74 let parsed: lightning_invoice::Bolt11Invoice = match input.parse() {
75 Ok(inv) => inv,
76 Err(e) => return Some(Err(Error::Other(format!("Invalid BOLT11 invoice: {e}")))),
77 };
78
79 if parsed.check_signature().is_err() {
80 return Some(Err(Error::Other(
81 "BOLT11 invoice has invalid signature".to_string(),
82 )));
83 }
84
85 let payee_pubkey = parsed
86 .recover_payee_pub_key()
87 .serialize()
88 .to_vec();
89
90 let payment_hash = format!("{}", parsed.payment_hash());
91 let payment_hash = hex::decode(&payment_hash)
92 .unwrap_or_default();
93
94 let description = match parsed.description() {
95 lightning_invoice::Bolt11InvoiceDescriptionRef::Direct(d) => Some(d.to_string()),
96 lightning_invoice::Bolt11InvoiceDescriptionRef::Hash(_) => None,
97 };
98
99 let amount_msat = parsed.amount_milli_satoshis();
100
101 let expiry = parsed.expiry_time().as_secs();
102
103 let timestamp = parsed
104 .timestamp()
105 .duration_since(std::time::SystemTime::UNIX_EPOCH)
106 .unwrap_or_default()
107 .as_secs();
108
109 Some(Ok(InputType::Bolt11 {
110 invoice: ParsedInvoice {
111 bolt11: input.to_string(),
112 payee_pubkey: Some(payee_pubkey),
113 payment_hash,
114 description,
115 amount_msat,
116 expiry,
117 timestamp,
118 },
119 }))
120}
121
122fn try_parse_node_id(input: &str) -> Option<InputType> {
124 if input.len() != 66 {
125 return None;
126 }
127 let bytes = hex::decode(input).ok()?;
128 if bytes.len() != 33 {
129 return None;
130 }
131 if bytes[0] != 0x02 && bytes[0] != 0x03 {
133 return None;
134 }
135 Some(InputType::NodeId {
136 node_id: input.to_string(),
137 })
138}