-
Notifications
You must be signed in to change notification settings - Fork 29
Implementation of bip-0353 #246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 4 commits
f129b38
1a56d82
c746a64
07693ea
3623a11
9848db5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| use crate::OfferError; | ||
| use bitcoin_payment_instructions::hrn_resolution::{HrnResolution, HrnResolver, HumanReadableName}; | ||
| use bitcoin_payment_instructions::http_resolver::HTTPHrnResolver; | ||
| use std::sync::Arc; | ||
|
|
||
| #[cfg(itest)] | ||
| use std::sync::RwLock; | ||
|
|
||
| #[cfg(itest)] | ||
| pub static TEST_RESOLVER: RwLock<Option<Arc<dyn HrnResolver + Send + Sync>>> = RwLock::new(None); | ||
|
|
||
| pub struct LndkDNSResolverMessageHandler { | ||
| resolver: Arc<dyn HrnResolver + Send + Sync>, | ||
| } | ||
|
|
||
| impl Default for LndkDNSResolverMessageHandler { | ||
| fn default() -> Self { | ||
| Self::new() | ||
| } | ||
| } | ||
|
|
||
| impl LndkDNSResolverMessageHandler { | ||
| pub fn new() -> Self { | ||
| #[cfg(itest)] | ||
| { | ||
| if let Ok(guard) = TEST_RESOLVER.read() { | ||
| if let Some(test_resolver) = guard.as_ref() { | ||
| return Self { | ||
| resolver: Arc::clone(test_resolver), | ||
| }; | ||
| } | ||
| } | ||
| } | ||
| Self::with_resolver(HTTPHrnResolver::new()) | ||
| } | ||
|
|
||
| pub fn with_resolver<R: HrnResolver + Send + Sync + 'static>(resolver: R) -> Self { | ||
| Self { | ||
| resolver: Arc::new(resolver), | ||
| } | ||
| } | ||
|
|
||
| pub async fn resolver_hrn_to_offer(&self, name_str: &str) -> Result<String, OfferError> { | ||
| let resolved_uri = self.resolve_locally(name_str.to_string()).await?; | ||
| self.extract_offer_from_uri(&resolved_uri) | ||
| } | ||
|
|
||
| pub fn extract_offer_from_uri(&self, uri: &str) -> Result<String, OfferError> { | ||
| if let Some((_scheme, params)) = uri.split_once("?") { | ||
IgnacioPorte marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| for param in params.split("&") { | ||
| if let Some((key, value)) = param.split_once("=") { | ||
| if key.eq_ignore_ascii_case("lno") { | ||
| return Ok(value.to_string()); | ||
| } | ||
| } | ||
| } | ||
| Err(OfferError::ResolveUriError( | ||
| "URI does not contain 'lno' parameter with BOLT12 offer".to_string(), | ||
| )) | ||
| } else { | ||
| Err(OfferError::ResolveUriError(format!( | ||
| "Invalid URI format - expected bitcoin:?lno=<offer>, got: {}", | ||
| uri | ||
| ))) | ||
| } | ||
| } | ||
|
|
||
| pub async fn resolve_locally(&self, name: String) -> Result<String, OfferError> { | ||
| let hrn_parsed = HumanReadableName::from_encoded(&name) | ||
| .map_err(|_| OfferError::ParseHrnFailure(name.clone()))?; | ||
|
|
||
| let resolution = self | ||
| .resolver | ||
| .resolve_hrn(&hrn_parsed) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it have a time out?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, now I added |
||
| .await | ||
| .map_err(|e| OfferError::HrnResolutionFailure(format!("{}: {}", name, e)))?; | ||
|
|
||
| let uri = match resolution { | ||
| HrnResolution::DNSSEC { result, .. } => result, | ||
| HrnResolution::LNURLPay { .. } => { | ||
| return Err(OfferError::ResolveUriError( | ||
| "LNURL resolution not supported in this flow".to_string(), | ||
| )) | ||
| } | ||
| }; | ||
|
|
||
| Ok(uri) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| mod clock; | ||
| pub mod dns_resolver; | ||
| mod grpc; | ||
| #[allow(dead_code)] | ||
| pub mod lnd; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,9 @@ use super::lnd_requests::{ | |
| send_invoice_request, LndkBolt12InvoiceInfo, | ||
| }; | ||
| use super::OfferError; | ||
| use crate::dns_resolver::LndkDNSResolverMessageHandler; | ||
| use crate::offers::lnd_requests::{send_payment, track_payment, CreateOfferArgs}; | ||
| use crate::offers::{get_destination, parse::decode}; | ||
| use crate::onion_messenger::MessengerUtilities; | ||
|
|
||
| pub const DEFAULT_RESPONSE_INVOICE_TIMEOUT: u32 = 15; | ||
|
|
@@ -77,6 +79,18 @@ pub struct PayOfferParams { | |
| pub fee_limit: Option<FeeLimit>, | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct PayHumanReadableAddressParams { | ||
| pub name: String, | ||
| pub amount: Option<u64>, | ||
| pub payer_note: Option<String>, | ||
| pub network: Network, | ||
| pub client: Client, | ||
| pub reply_path: Option<BlindedMessagePath>, | ||
| pub response_invoice_timeout: Option<u32>, | ||
| pub fee_limit: Option<FeeLimit>, | ||
| } | ||
|
|
||
| #[derive(Clone)] | ||
| pub struct SendPaymentParams { | ||
| pub path: BlindedPaymentPath, | ||
|
|
@@ -150,6 +164,30 @@ impl OfferHandler { | |
| .await | ||
| } | ||
|
|
||
| /// Resolves a human-readable name (BIP-353) to an offer and pays it. | ||
| pub async fn pay_hrn(&self, cfg: PayHumanReadableAddressParams) -> Result<Payment, OfferError> { | ||
| let offer_str = LndkDNSResolverMessageHandler::new() | ||
|
||
| .resolver_hrn_to_offer(&cfg.name) | ||
| .await?; | ||
|
|
||
| let offer = decode(offer_str)?; | ||
| let destination = get_destination(&offer).await?; | ||
|
|
||
| let pay_offer_cfg = PayOfferParams { | ||
| offer, | ||
| amount: cfg.amount, | ||
| payer_note: cfg.payer_note, | ||
| network: cfg.network, | ||
| client: cfg.client, | ||
| destination, | ||
| reply_path: cfg.reply_path, | ||
| response_invoice_timeout: cfg.response_invoice_timeout, | ||
| fee_limit: cfg.fee_limit, | ||
| }; | ||
|
|
||
| self.pay_offer(pay_offer_cfg).await | ||
| } | ||
|
|
||
| /// Sends an invoice request and waits for an invoice to be sent back to us. | ||
| /// Reminder that if this method returns an error after create_invoice_request is called, we | ||
| /// *must* remove the payment_id from self.active_payments. | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.