use crate::Error;
#[derive(Clone, uniffi::Record)]
pub struct ParsedInvoice {
pub bolt11: String,
pub payee_pubkey: Option<Vec<u8>>,
pub payment_hash: Vec<u8>,
pub description: Option<String>,
pub amount_msat: Option<u64>,
pub expiry: u64,
pub timestamp: u64,
}
#[derive(Clone, uniffi::Enum)]
pub enum InputType {
Bolt11 { invoice: ParsedInvoice },
NodeId { node_id: String },
}
pub fn parse_input(input: String) -> Result<InputType, Error> {
let trimmed = input.trim();
if trimmed.is_empty() {
return Err(Error::Other("Empty input".to_string()));
}
let stripped = if let Some(rest) = trimmed.strip_prefix("lightning:") {
rest
} else if let Some(rest) = trimmed.strip_prefix("LIGHTNING:") {
rest
} else {
trimmed
};
if let Some(input_type) = try_parse_bolt11(stripped) {
return input_type;
}
if let Some(input_type) = try_parse_node_id(stripped) {
return Ok(input_type);
}
Err(Error::Other("Unrecognized input".to_string()))
}
fn try_parse_bolt11(input: &str) -> Option<Result<InputType, Error>> {
let lower = input.to_lowercase();
if !lower.starts_with("lnbc") && !lower.starts_with("lntb") && !lower.starts_with("lnbcrt") {
return None;
}
let parsed: lightning_invoice::Bolt11Invoice = match input.parse() {
Ok(inv) => inv,
Err(e) => return Some(Err(Error::Other(format!("Invalid BOLT11 invoice: {e}")))),
};
if parsed.check_signature().is_err() {
return Some(Err(Error::Other(
"BOLT11 invoice has invalid signature".to_string(),
)));
}
let payee_pubkey = parsed
.recover_payee_pub_key()
.serialize()
.to_vec();
let payment_hash = format!("{}", parsed.payment_hash());
let payment_hash = hex::decode(&payment_hash)
.unwrap_or_default();
let description = match parsed.description() {
lightning_invoice::Bolt11InvoiceDescriptionRef::Direct(d) => Some(d.to_string()),
lightning_invoice::Bolt11InvoiceDescriptionRef::Hash(_) => None,
};
let amount_msat = parsed.amount_milli_satoshis();
let expiry = parsed.expiry_time().as_secs();
let timestamp = parsed
.timestamp()
.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
Some(Ok(InputType::Bolt11 {
invoice: ParsedInvoice {
bolt11: input.to_string(),
payee_pubkey: Some(payee_pubkey),
payment_hash,
description,
amount_msat,
expiry,
timestamp,
},
}))
}
fn try_parse_node_id(input: &str) -> Option<InputType> {
if input.len() != 66 {
return None;
}
let bytes = hex::decode(input).ok()?;
if bytes.len() != 33 {
return None;
}
if bytes[0] != 0x02 && bytes[0] != 0x03 {
return None;
}
Some(InputType::NodeId {
node_id: input.to_string(),
})
}