use crate::split_once;
use bitcoin::secp256k1::SecretKey;
use bitcoin::Network;
use lightning::offers::parse::Bolt12ParseError;
use lightning::offers::refund::Refund;
use alloc::str::FromStr;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use core::time::Duration;
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum ReceiveMethod {
PrivateKey(SecretKey),
Bolt12Refund(Refund),
}
#[derive(Debug)]
pub enum ParseError {
InvalidBolt12(Bolt12ParseError),
WrongNetwork,
InvalidInstructions(&'static str),
UnknownReceiveInstructions,
UnknownRequiredParameter,
InstructionsExpired,
}
pub(crate) fn check_expiry(_expiry: Duration) -> Result<(), ParseError> {
#[cfg(feature = "std")]
{
use std::time::SystemTime;
if let Ok(now) = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) {
if now > _expiry {
return Err(ParseError::InstructionsExpired);
}
}
}
Ok(())
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct ReceiveInstructions {
description: Option<String>,
methods: Vec<ReceiveMethod>,
}
impl ReceiveInstructions {
pub fn methods(&self) -> &[ReceiveMethod] {
&self.methods
}
pub fn sender_description(&self) -> Option<&str> {
self.description.as_deref()
}
pub fn parse_receive_instructions(
instructions: &str, network: Network,
) -> Result<ReceiveInstructions, ParseError> {
if instructions.is_empty() {
return Err(ParseError::InvalidInstructions("Empty string"));
}
const BTC_URI_PFX_LEN: usize = "bitcoin:".len();
if instructions.len() >= BTC_URI_PFX_LEN
&& instructions[..BTC_URI_PFX_LEN].eq_ignore_ascii_case("bitcoin:")
{
let (_, params) = split_once(&instructions[BTC_URI_PFX_LEN..], '?');
let mut methods = Vec::new();
let mut description = None;
if let Some(params) = params {
for param in params.split('&') {
let (k, v) = split_once(param, '=');
if k.eq_ignore_ascii_case("lnr") || k.eq_ignore_ascii_case("req-lnr") {
if let Some(v) = v {
match Refund::from_str(v) {
Ok(refund) => {
if refund.chain() != network.chain_hash() {
return Err(ParseError::WrongNetwork);
}
description = Some(refund.description().0.to_string());
methods.push(ReceiveMethod::Bolt12Refund(refund));
},
Err(err) => return Err(ParseError::InvalidBolt12(err)),
}
} else {
let err = "Missing value for a BOLT 12 refund parameter in a BIP 321 bitcoin: URI";
return Err(ParseError::InvalidInstructions(err));
}
} else if k.len() >= 4 && k[..4].eq_ignore_ascii_case("req-") {
return Err(ParseError::UnknownRequiredParameter);
}
}
}
if methods.is_empty() {
return Err(ParseError::UnknownReceiveInstructions);
}
return Ok(ReceiveInstructions { description, methods });
}
if let Ok(pk) = bitcoin::key::PrivateKey::from_wif(instructions) {
if pk.network != network.into() {
return Err(ParseError::WrongNetwork);
}
return Ok(ReceiveInstructions {
description: None,
methods: vec![ReceiveMethod::PrivateKey(pk.inner)],
});
}
if let Ok(refund) = Refund::from_str(instructions) {
if refund.chain() != network.chain_hash() {
return Err(ParseError::WrongNetwork);
}
if let Some(expiry) = refund.absolute_expiry() {
check_expiry(expiry)?;
}
return Ok(ReceiveInstructions {
description: Some(refund.description().to_string()),
methods: vec![ReceiveMethod::Bolt12Refund(refund)],
});
}
Err(ParseError::UnknownReceiveInstructions)
}
}