-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathlightning.rs
137 lines (119 loc) · 4.44 KB
/
lightning.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use serde::{Deserialize, Serialize};
use bitcoin_waila::PaymentParams;
use lightning_invoice::Bolt11Invoice;
use lnurl::lightning_address::LightningAddress;
use lnurl::lnurl::LnUrl;
use lnurl::LnUrlResponse;
use log::info;
use nostr::prelude::ZapRequestData;
use nostr::{EventBuilder, Filter, JsonUtil, Kind, Metadata, UncheckedUrl};
use std::str::FromStr;
use tonic_openssl_lnd::lnrpc;
use crate::auth::AuthUser;
use crate::nostr_dms::RELAYS;
use crate::{AppState, MAX_SEND_AMOUNT};
#[derive(Clone, Deserialize)]
pub struct LightningRequest {
pub bolt11: String,
}
#[derive(Clone, Serialize)]
pub struct LightningResponse {
pub payment_hash: String,
}
pub async fn pay_lightning(
state: &AppState,
x_forwarded_for: &str,
user: Option<&AuthUser>,
bolt11: &str,
) -> anyhow::Result<String> {
let params = PaymentParams::from_str(bolt11).map_err(|_| anyhow::anyhow!("invalid bolt 11"))?;
let invoice = if let Some(invoice) = params.invoice() {
if let Some(msat_amount) = invoice.amount_milli_satoshis() {
if msat_amount / 1000 > MAX_SEND_AMOUNT {
anyhow::bail!("max amount is 1,000,000");
}
invoice
} else {
anyhow::bail!("bolt11 invoice should have an amount");
}
} else if let Some(lnurl) = params.lnurl() {
match state.lnurl.make_request(&lnurl.url).await? {
LnUrlResponse::LnUrlPayResponse(pay) => {
if pay.min_sendable > MAX_SEND_AMOUNT {
anyhow::bail!("max amount is 1,000,000");
}
let inv = state
.lnurl
.get_invoice(&pay, pay.min_sendable, None, None)
.await?;
Bolt11Invoice::from_str(inv.invoice())?
}
_ => anyhow::bail!("invalid lnurl"),
}
} else if let Some(npub) = params.nostr_pubkey() {
let client = nostr_sdk::Client::default();
client.add_relays(RELAYS).await?;
client.connect().await;
let filter = Filter::new()
.author(npub.into())
.kind(Kind::Metadata)
.limit(1);
let events = client.get_events_of(vec![filter], None).await?;
let event = events
.into_iter()
.max_by_key(|e| e.created_at)
.ok_or(anyhow::anyhow!("no event"))?;
let metadata = Metadata::from_json(&event.content)?;
let lnurl = metadata
.lud16
.and_then(|l| LightningAddress::from_str(&l).ok().map(|l| l.lnurl()))
.or(metadata.lud06.and_then(|l| LnUrl::decode(l).ok()))
.ok_or(anyhow::anyhow!("no lnurl"))?;
match state.lnurl.make_request(&lnurl.url).await? {
LnUrlResponse::LnUrlPayResponse(pay) => {
if pay.min_sendable > MAX_SEND_AMOUNT {
anyhow::bail!("max amount is 1,000,000");
}
let relays = RELAYS.iter().map(|r| UncheckedUrl::new(*r));
let zap_data = ZapRequestData::new(npub.into(), relays)
.lnurl(lnurl.encode())
.amount(pay.min_sendable);
let zap = EventBuilder::public_zap_request(zap_data).to_event(&state.keys)?;
let inv = state
.lnurl
.get_invoice(&pay, pay.min_sendable, Some(zap.as_json()), None)
.await?;
Bolt11Invoice::from_str(inv.invoice())?
}
_ => anyhow::bail!("invalid lnurl"),
}
} else {
anyhow::bail!("invalid bolt11")
};
let payment_preimage = {
let mut lightning_client = state.lightning_client.clone();
info!("Paying invoice {invoice}");
state
.payments
.add_payment(
x_forwarded_for,
None,
user,
invoice.amount_milli_satoshis().unwrap_or(0) / 1000,
)
.await;
let response = lightning_client
.send_payment_sync(lnrpc::SendRequest {
payment_request: invoice.to_string(),
allow_self_payment: true,
..Default::default()
})
.await?
.into_inner();
if !response.payment_error.is_empty() {
return Err(anyhow::anyhow!("Payment error: {}", response.payment_error));
}
response.payment_preimage
};
Ok(hex::encode(payment_preimage))
}