use std::str::FromStr;
use cdk_common::wallet::MeltQuote;
use tracing::instrument;
use crate::lightning_address::LightningAddress;
use crate::{Amount, Error, Wallet};
impl Wallet {
#[instrument(skip(self, amount_msat), fields(lightning_address = %lightning_address))]
pub async fn melt_lightning_address_quote(
&self,
lightning_address: &str,
amount_msat: impl Into<Amount>,
) -> Result<MeltQuote, Error> {
let amount = amount_msat.into();
let ln_address = LightningAddress::from_str(lightning_address).map_err(|e| {
tracing::error!(
"Failed to parse Lightning address '{}': {}",
lightning_address,
e
);
Error::LightningAddressParse(e.to_string())
})?;
tracing::debug!("Resolving Lightning address: {}", ln_address);
let invoice = ln_address
.request_invoice(&self.client, amount)
.await
.map_err(|e| {
tracing::error!(
"Failed to get invoice from Lightning address service: {}",
e
);
Error::LightningAddressRequest(e.to_string())
})?;
tracing::debug!(
"Received invoice from Lightning address service: {}",
invoice
);
self.melt_bolt11_quote(invoice.to_string(), None).await
}
}
#[cfg(all(test, feature = "wallet", not(target_arch = "wasm32")))]
mod tests {
use std::sync::Arc;
use cdk_common::database::WalletDatabase;
use super::*;
use crate::mint_url::MintUrl;
use crate::nuts::{CurrencyUnit, MeltQuoteBolt11Response, MeltQuoteState};
use crate::wallet::test_utils::MockMintConnector;
use crate::wallet::WalletBuilder;
const INVOICE_1000_SATS: &str = "lnbc10u1p3xtswzsp5a0pjcg5t042q3lk4mvjqv3x3ea8m9w2grswlxqk6dwlj6x4spphsdqqcqzzsxqyz5vqsp5f4w7aw8g0n7v9j4hrjz8fll2gk7wgpk9v7s0x3t8xmtt4f25mh9qxpqysgqfeh44plf5n2m4gq2a4v0y5ngd8lgfz2g06kknk2pu4v772ma3xjxfugm6mh8vk0j9j2qlenhtj5w0q0td5j0g2vm0r6zv0v2fsz0u4qqr28j6g";
async fn test_wallet_with_connector(connector: Arc<MockMintConnector>) -> Wallet {
let db = Arc::new(
cdk_sqlite::wallet::memory::empty()
.await
.expect("memory db"),
) as Arc<dyn WalletDatabase<_> + Send + Sync>;
let seed = [1; 64];
WalletBuilder::new()
.mint_url(MintUrl::from_str("https://mint.example.com").expect("valid mint url"))
.unit(CurrencyUnit::Sat)
.localstore(db)
.seed(seed)
.shared_client(connector)
.build()
.expect("wallet builds")
}
#[tokio::test]
async fn test_melt_lightning_address_quote_rejects_mismatched_invoice_amount() {
let connector = Arc::new(MockMintConnector::new());
connector.set_lnurl_pay_request_response(Ok(crate::lightning_address::LnurlPayResponse {
callback: "https://example.com/callback".to_string(),
min_sendable: 1,
max_sendable: 2_000_000,
metadata: "[]".to_string(),
tag: Some("payRequest".to_string()),
reason: None,
}));
connector.set_lnurl_invoice_response(Ok(
crate::lightning_address::LnurlPayInvoiceResponse {
pr: Some(INVOICE_1000_SATS.to_string()),
success_action: None,
routes: None,
reason: None,
},
));
connector.set_melt_quote_status_response(Ok(MeltQuoteBolt11Response {
quote: "quote-should-not-be-used".to_string(),
amount: Amount::from(1_000_u64),
fee_reserve: Amount::from(0_u64),
state: MeltQuoteState::Unpaid,
expiry: 0,
payment_preimage: None,
change: None,
request: None,
unit: None,
}));
let wallet = test_wallet_with_connector(connector.clone()).await;
let error = wallet
.melt_lightning_address_quote("alice@example.com", Amount::from(100_000_u64))
.await
.expect_err("mismatched invoice amount should fail");
assert!(matches!(error, Error::LightningAddressRequest(_)));
}
}