mirror of
https://github.com/daywalker90/cln-nip47.git
synced 2026-08-13 12:33:43 +02:00
refactor, clippy and ruff fixes
This commit is contained in:
parent
e7cb3ecbcb
commit
cab38c0c9a
10 changed files with 1134 additions and 934 deletions
|
|
@ -165,8 +165,8 @@ async fn shutdown_handler(
|
|||
_args: serde_json::Value,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let mut locked_handles = plugin.state().handles.lock().await;
|
||||
for (_x, (client, _client_pubkey, _wallet_keys)) in locked_handles.drain() {
|
||||
client.shutdown().await;
|
||||
for (_x, wallet_service) in locked_handles.drain() {
|
||||
wallet_service.client.shutdown().await;
|
||||
}
|
||||
std::process::exit(0)
|
||||
}
|
||||
|
|
@ -299,6 +299,6 @@ fn do_certificates_exist(cert_dir: &Path) -> bool {
|
|||
|
||||
required_files.iter().all(|file| {
|
||||
let path = cert_dir.join(file);
|
||||
path.exists() && path.metadata().map(|m| m.len() > 0).unwrap_or(false)
|
||||
path.exists() && path.metadata().is_ok_and(|m| m.len() > 0)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
17
src/nwc.rs
17
src/nwc.rs
|
|
@ -38,11 +38,12 @@ use crate::{
|
|||
nwc_keysend::pay_keysend_response,
|
||||
nwc_lookups::{list_transactions_response, lookup_invoice_response},
|
||||
nwc_pay::pay_invoice_response,
|
||||
structs::{NwcStore, PluginState},
|
||||
structs::{NwcStore, PluginState, WalletService},
|
||||
tasks::budget_task,
|
||||
util::{build_capabilities, build_notifications_vec, is_read_only_nwc},
|
||||
};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn run_nwc(
|
||||
plugin: Plugin<PluginState>,
|
||||
label: String,
|
||||
|
|
@ -154,10 +155,12 @@ pub async fn run_nwc(
|
|||
});
|
||||
|
||||
let mut locked_handles = plugin.state().handles.lock().await;
|
||||
locked_handles.insert(
|
||||
label.clone(),
|
||||
(nostr_client, client_keys.public_key(), wallet_keys),
|
||||
);
|
||||
let wallet_service = WalletService {
|
||||
client: nostr_client,
|
||||
client_pubkey: client_keys.public_key(),
|
||||
wallet_secret: wallet_keys,
|
||||
};
|
||||
locked_handles.insert(label.clone(), wallet_service);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -208,8 +211,8 @@ pub async fn send_nwc_info_event(
|
|||
|
||||
pub async fn stop_nwc(plugin: Plugin<PluginState>, label: &String) {
|
||||
let mut locked_handles = plugin.state().handles.lock().await;
|
||||
if let Some((client, _client_pubkey, _wallet_secret)) = locked_handles.remove(label) {
|
||||
client.shutdown().await;
|
||||
if let Some(wallet_service) = locked_handles.remove(label) {
|
||||
wallet_service.client.shutdown().await;
|
||||
}
|
||||
|
||||
stop_nwc_budget_job(&plugin, label);
|
||||
|
|
|
|||
|
|
@ -2,13 +2,14 @@ use std::{collections::HashMap, str::FromStr};
|
|||
|
||||
use cln_plugin::Plugin;
|
||||
use cln_rpc::{
|
||||
ClnRpc,
|
||||
model::requests::{KeysendRequest, XkeysendRequest},
|
||||
primitives::{Amount, PublicKey, TlvEntry, TlvStream},
|
||||
};
|
||||
use nostr::nips::nip47;
|
||||
use nostr::nips::nip47::{self};
|
||||
|
||||
use crate::{
|
||||
structs::PluginState,
|
||||
structs::{NwcStore, PluginState},
|
||||
util::{at_or_above_version, budget_amount_check, load_nwc_store, update_nwc_store},
|
||||
};
|
||||
|
||||
|
|
@ -57,7 +58,7 @@ async fn pay_keysend(
|
|||
});
|
||||
}
|
||||
|
||||
let mut nwc_store = load_nwc_store(&mut rpc, label)
|
||||
let nwc_store = load_nwc_store(&mut rpc, label)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
|
|
@ -82,141 +83,161 @@ async fn pay_keysend(
|
|||
code: nip47::ErrorCode::Other,
|
||||
message: e.to_string(),
|
||||
})? {
|
||||
let mut extratlvs = HashMap::with_capacity(params.tlv_records.len());
|
||||
for tlv in params.tlv_records {
|
||||
extratlvs.insert(tlv.tlv_type.to_string(), tlv.value);
|
||||
}
|
||||
let extratlvs = if extratlvs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(extratlvs)
|
||||
};
|
||||
|
||||
match rpc
|
||||
.call_typed(&XkeysendRequest {
|
||||
extratlvs,
|
||||
label: None,
|
||||
maxdelay: None,
|
||||
maxfee: None,
|
||||
retry_for: None,
|
||||
layers: None,
|
||||
amount_msat: Amount::from_msat(params.amount),
|
||||
destination: pubkey,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(o) => {
|
||||
if let Some(ref mut bdg) = nwc_store.budget_msat {
|
||||
*bdg = bdg.saturating_sub(o.amount_sent_msat.msat());
|
||||
update_nwc_store(&mut rpc, label, nwc_store)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
}
|
||||
|
||||
let preimage = hex::encode(o.payment_preimage.to_vec());
|
||||
|
||||
let fees_paid = o.amount_sent_msat.msat() - o.amount_msat.msat();
|
||||
|
||||
Ok(nip47::PayKeysendResponse {
|
||||
preimage,
|
||||
fees_paid: Some(fees_paid),
|
||||
})
|
||||
}
|
||||
Err(e) => match e.code {
|
||||
Some(c) => match c {
|
||||
203 | 205 | 207 | 219 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::PaymentFailed,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
209 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
_ => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
None => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
xkeysend(&mut rpc, params, pubkey, nwc_store, label).await
|
||||
} else {
|
||||
let mut extratlvs = TlvStream {
|
||||
entries: Vec::new(),
|
||||
};
|
||||
for tlv in params.tlv_records {
|
||||
extratlvs.entries.push(TlvEntry {
|
||||
typ: tlv.tlv_type,
|
||||
value: tlv.value.as_bytes().to_owned(),
|
||||
});
|
||||
}
|
||||
let extratlvs = if extratlvs.entries.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(extratlvs)
|
||||
};
|
||||
|
||||
match rpc
|
||||
.call_typed(&KeysendRequest {
|
||||
exemptfee: None,
|
||||
extratlvs,
|
||||
label: None,
|
||||
maxdelay: None,
|
||||
maxfee: None,
|
||||
maxfeepercent: None,
|
||||
retry_for: None,
|
||||
routehints: None,
|
||||
amount_msat: Amount::from_msat(params.amount),
|
||||
destination: pubkey,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(o) => {
|
||||
if let Some(ref mut bdg) = nwc_store.budget_msat {
|
||||
*bdg = bdg.saturating_sub(o.amount_sent_msat.msat());
|
||||
update_nwc_store(&mut rpc, label, nwc_store)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
}
|
||||
|
||||
let preimage = hex::encode(o.payment_preimage.to_vec());
|
||||
|
||||
let fees_paid = o.amount_sent_msat.msat() - o.amount_msat.msat();
|
||||
|
||||
Ok(nip47::PayKeysendResponse {
|
||||
preimage,
|
||||
fees_paid: Some(fees_paid),
|
||||
})
|
||||
}
|
||||
Err(e) => match e.code {
|
||||
Some(c) => match c {
|
||||
203 | 205 | 210 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::PaymentFailed,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
206 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::InsufficientBalance,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
_ => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
None => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
keysend(&mut rpc, params, pubkey, nwc_store, label).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn xkeysend(
|
||||
rpc: &mut ClnRpc,
|
||||
params: nip47::PayKeysendRequest,
|
||||
pubkey: PublicKey,
|
||||
mut nwc_store: NwcStore,
|
||||
label: &str,
|
||||
) -> Result<nip47::PayKeysendResponse, nip47::NIP47Error> {
|
||||
let mut extratlvs = HashMap::with_capacity(params.tlv_records.len());
|
||||
for tlv in params.tlv_records {
|
||||
extratlvs.insert(tlv.tlv_type.to_string(), tlv.value);
|
||||
}
|
||||
let extratlvs = if extratlvs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(extratlvs)
|
||||
};
|
||||
|
||||
match rpc
|
||||
.call_typed(&XkeysendRequest {
|
||||
extratlvs,
|
||||
label: None,
|
||||
maxdelay: None,
|
||||
maxfee: None,
|
||||
retry_for: None,
|
||||
layers: None,
|
||||
amount_msat: Amount::from_msat(params.amount),
|
||||
destination: pubkey,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(o) => {
|
||||
if let Some(ref mut bdg) = nwc_store.budget_msat {
|
||||
*bdg = bdg.saturating_sub(o.amount_sent_msat.msat());
|
||||
update_nwc_store(rpc, label, nwc_store)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
}
|
||||
|
||||
let preimage = hex::encode(o.payment_preimage.to_vec());
|
||||
|
||||
let fees_paid = o.amount_sent_msat.msat() - o.amount_msat.msat();
|
||||
|
||||
Ok(nip47::PayKeysendResponse {
|
||||
preimage,
|
||||
fees_paid: Some(fees_paid),
|
||||
})
|
||||
}
|
||||
Err(e) => match e.code {
|
||||
Some(c) => match c {
|
||||
203 | 205 | 207 | 219 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::PaymentFailed,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
209 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
_ => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
None => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
async fn keysend(
|
||||
rpc: &mut ClnRpc,
|
||||
params: nip47::PayKeysendRequest,
|
||||
pubkey: PublicKey,
|
||||
mut nwc_store: NwcStore,
|
||||
label: &str,
|
||||
) -> Result<nip47::PayKeysendResponse, nip47::NIP47Error> {
|
||||
let mut extratlvs = TlvStream {
|
||||
entries: Vec::new(),
|
||||
};
|
||||
for tlv in params.tlv_records {
|
||||
extratlvs.entries.push(TlvEntry {
|
||||
typ: tlv.tlv_type,
|
||||
value: tlv.value.as_bytes().to_owned(),
|
||||
});
|
||||
}
|
||||
let extratlvs = if extratlvs.entries.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(extratlvs)
|
||||
};
|
||||
|
||||
match rpc
|
||||
.call_typed(&KeysendRequest {
|
||||
exemptfee: None,
|
||||
extratlvs,
|
||||
label: None,
|
||||
maxdelay: None,
|
||||
maxfee: None,
|
||||
maxfeepercent: None,
|
||||
retry_for: None,
|
||||
routehints: None,
|
||||
amount_msat: Amount::from_msat(params.amount),
|
||||
destination: pubkey,
|
||||
})
|
||||
.await
|
||||
{
|
||||
Ok(o) => {
|
||||
if let Some(ref mut bdg) = nwc_store.budget_msat {
|
||||
*bdg = bdg.saturating_sub(o.amount_sent_msat.msat());
|
||||
update_nwc_store(rpc, label, nwc_store)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
}
|
||||
|
||||
let preimage = hex::encode(o.payment_preimage.to_vec());
|
||||
|
||||
let fees_paid = o.amount_sent_msat.msat() - o.amount_msat.msat();
|
||||
|
||||
Ok(nip47::PayKeysendResponse {
|
||||
preimage,
|
||||
fees_paid: Some(fees_paid),
|
||||
})
|
||||
}
|
||||
Err(e) => match e.code {
|
||||
Some(c) => match c {
|
||||
203 | 205 | 210 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::PaymentFailed,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
206 => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::InsufficientBalance,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
_ => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
None => Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ use std::{cmp::Reverse, str::FromStr};
|
|||
|
||||
use cln_plugin::Plugin;
|
||||
use cln_rpc::{
|
||||
ClnRpc,
|
||||
model::{
|
||||
requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest},
|
||||
responses::{
|
||||
DecodeResponse,
|
||||
DecodeType,
|
||||
ListinvoicesInvoices,
|
||||
ListinvoicesInvoicesStatus,
|
||||
|
|
@ -13,13 +15,13 @@ use cln_rpc::{
|
|||
},
|
||||
},
|
||||
primitives::Sha256,
|
||||
ClnRpc,
|
||||
};
|
||||
use nostr::{nips::nip47, Timestamp};
|
||||
use nostr::{Timestamp, nips::nip47};
|
||||
use tonic::transport::Channel;
|
||||
|
||||
use crate::{
|
||||
hold::{self, list_request::Constraint, ListRequest},
|
||||
structs::{PluginState, NOT_INV_ERR},
|
||||
hold::{self, ListRequest, hold_client::HoldClient, list_request::Constraint},
|
||||
structs::{NOT_INV_ERR, PluginState},
|
||||
};
|
||||
|
||||
pub async fn lookup_invoice_response(
|
||||
|
|
@ -127,186 +129,7 @@ async fn lookup_invoice(
|
|||
|
||||
if holdinvoice_support {
|
||||
let mut hold_client = plugin.state().hold_client.lock().clone().unwrap();
|
||||
|
||||
let (hold_invoice, invoice_decoded) = if let Some(ph) = ¶ms.payment_hash {
|
||||
let payment_hash_hash = match hex::decode(ph) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash_hash)),
|
||||
};
|
||||
let hold_lookup = hold_client
|
||||
.list(list_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap();
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
(hold_invoice, invoice_decoded)
|
||||
} else {
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: params.invoice.unwrap(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
let ph = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_payment_hash.unwrap(),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.payment_hash.unwrap().to_string(),
|
||||
_ => todo!(),
|
||||
};
|
||||
let payment_hash_hash = match hex::decode(&ph) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash in invoice".to_owned(),
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash_hash)),
|
||||
};
|
||||
|
||||
let hold_lookup = hold_client
|
||||
.list(list_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap();
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
(hold_invoice, invoice_decoded)
|
||||
};
|
||||
let not_invoice_err = Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: NOT_INV_ERR.to_owned(),
|
||||
});
|
||||
|
||||
let description = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description,
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
let description_hash = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => None,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description_hash.map(|h| h.to_string()),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => Timestamp::from_secs(invoice_decoded.created_at.unwrap()),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_amount_msat.unwrap().msat(),
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded
|
||||
.invoice_relative_expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded
|
||||
.expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(e_at)),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let state = match hold_invoice.state() {
|
||||
hold::InvoiceState::Unpaid => nip47::TransactionState::Pending,
|
||||
hold::InvoiceState::Accepted => nip47::TransactionState::Accepted,
|
||||
hold::InvoiceState::Paid => nip47::TransactionState::Settled,
|
||||
hold::InvoiceState::Cancelled => nip47::TransactionState::Expired,
|
||||
};
|
||||
|
||||
let settled_at = if hold_invoice.settled_at() != 0 {
|
||||
Some(Timestamp::from_secs(hold_invoice.settled_at()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let preimage = if hold_invoice.state() == hold::InvoiceState::Paid {
|
||||
Some(hex::encode(hold_invoice.preimage()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
return Ok(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(hold_invoice.invoice.clone()),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
payment_hash: hex::encode(hold_invoice.payment_hash),
|
||||
amount,
|
||||
fees_paid: 0,
|
||||
created_at,
|
||||
expires_at,
|
||||
settled_at,
|
||||
metadata: None,
|
||||
state: Some(state),
|
||||
});
|
||||
return lookup_holdinvoice(&mut hold_client, &mut rpc, params).await;
|
||||
}
|
||||
|
||||
Err(nip47::NIP47Error {
|
||||
|
|
@ -315,6 +138,202 @@ async fn lookup_invoice(
|
|||
})
|
||||
}
|
||||
|
||||
async fn lookup_holdinvoice(
|
||||
hold_client: &mut HoldClient<Channel>,
|
||||
rpc: &mut ClnRpc,
|
||||
params: nip47::LookupInvoiceRequest,
|
||||
) -> Result<nip47::LookupInvoiceResponse, nip47::NIP47Error> {
|
||||
log::debug!("Looking up hold invoice for params {params:#?}");
|
||||
let (hold_invoice, invoice_decoded) =
|
||||
get_and_decode_holdinvoice(rpc, hold_client, params).await?;
|
||||
let not_invoice_err = Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: NOT_INV_ERR.to_owned(),
|
||||
});
|
||||
|
||||
let description = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description,
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
let description_hash = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => None,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description_hash.map(|h| h.to_string()),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => Timestamp::from_secs(invoice_decoded.created_at.unwrap()),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_amount_msat.unwrap().msat(),
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded
|
||||
.invoice_relative_expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded
|
||||
.expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(e_at)),
|
||||
_ => return not_invoice_err,
|
||||
};
|
||||
|
||||
let state = match hold_invoice.state() {
|
||||
hold::InvoiceState::Unpaid => nip47::TransactionState::Pending,
|
||||
hold::InvoiceState::Accepted => nip47::TransactionState::Accepted,
|
||||
hold::InvoiceState::Paid => nip47::TransactionState::Settled,
|
||||
hold::InvoiceState::Cancelled => nip47::TransactionState::Expired,
|
||||
};
|
||||
|
||||
let settled_at = if hold_invoice.settled_at() != 0 {
|
||||
Some(Timestamp::from_secs(hold_invoice.settled_at()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let preimage = if hold_invoice.state() == hold::InvoiceState::Paid {
|
||||
Some(hex::encode(hold_invoice.preimage()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Ok(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(hold_invoice.invoice.clone()),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
payment_hash: hex::encode(hold_invoice.payment_hash),
|
||||
amount,
|
||||
fees_paid: 0,
|
||||
created_at,
|
||||
expires_at,
|
||||
settled_at,
|
||||
metadata: None,
|
||||
state: Some(state),
|
||||
})
|
||||
}
|
||||
|
||||
async fn get_and_decode_holdinvoice(
|
||||
rpc: &mut ClnRpc,
|
||||
hold_client: &mut HoldClient<Channel>,
|
||||
params: nip47::LookupInvoiceRequest,
|
||||
) -> Result<(hold::Invoice, DecodeResponse), nip47::NIP47Error> {
|
||||
if let Some(ph) = ¶ms.payment_hash {
|
||||
let payment_hash_hash = match hex::decode(ph) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash".to_owned(),
|
||||
});
|
||||
}
|
||||
};
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash_hash)),
|
||||
};
|
||||
let hold_lookup = hold_client
|
||||
.list(list_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap();
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
Ok((hold_invoice, invoice_decoded))
|
||||
} else {
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: params.invoice.unwrap(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
let ph = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_payment_hash.unwrap(),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.payment_hash.unwrap().to_string(),
|
||||
_ => todo!(),
|
||||
};
|
||||
let payment_hash_hash = match hex::decode(&ph) {
|
||||
Ok(p) => p,
|
||||
Err(_e) => {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Invalid payment hash in invoice".to_owned(),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let list_request = ListRequest {
|
||||
constraint: Some(Constraint::PaymentHash(payment_hash_hash)),
|
||||
};
|
||||
|
||||
let hold_lookup = hold_client
|
||||
.list(list_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoice: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
if hold_lookup.invoices.len() != 1 {
|
||||
return Err(nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: "Transaction not found".to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let hold_invoice = hold_lookup.invoices.into_iter().next().unwrap();
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
Ok((hold_invoice, invoice_decoded))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_transactions_response(
|
||||
plugin: Plugin<PluginState>,
|
||||
params: nip47::ListTransactionsRequest,
|
||||
|
|
@ -389,112 +408,8 @@ async fn list_transactions(
|
|||
|
||||
if holdinvoice_support {
|
||||
let mut hold_client = plugin.state().hold_client.lock().clone().unwrap();
|
||||
|
||||
let lookup_request = ListRequest { constraint: None };
|
||||
|
||||
let hold_lookup = hold_client
|
||||
.list(lookup_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoices: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
for hold_invoice in hold_lookup.invoices {
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
let description = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description,
|
||||
_ => continue,
|
||||
};
|
||||
let description_hash = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => None,
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
invoice_decoded.description_hash.map(|h| h.to_string())
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.created_at.unwrap())
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
invoice_decoded.invoice_amount_msat.unwrap().msat()
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded
|
||||
.invoice_relative_expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded
|
||||
.expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(e_at)),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let state = match hold_invoice.state() {
|
||||
hold::InvoiceState::Unpaid => nip47::TransactionState::Pending,
|
||||
hold::InvoiceState::Accepted => nip47::TransactionState::Accepted,
|
||||
hold::InvoiceState::Paid => nip47::TransactionState::Settled,
|
||||
hold::InvoiceState::Cancelled => nip47::TransactionState::Expired,
|
||||
};
|
||||
|
||||
let settled_at = if hold_invoice.settled_at() != 0 {
|
||||
Some(Timestamp::from_secs(hold_invoice.settled_at()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let preimage = if hold_invoice.state() == hold::InvoiceState::Paid {
|
||||
Some(hex::encode(hold_invoice.preimage()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
transactions.push(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(hold_invoice.invoice.clone()),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
payment_hash: hex::encode(hold_invoice.payment_hash),
|
||||
amount,
|
||||
fees_paid: 0,
|
||||
created_at,
|
||||
expires_at,
|
||||
settled_at,
|
||||
metadata: None,
|
||||
state: Some(state),
|
||||
});
|
||||
}
|
||||
list_holdinvoices_to_transactions(&mut hold_client, &mut rpc, &mut transactions)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -548,6 +463,114 @@ async fn list_transactions(
|
|||
Ok(transactions)
|
||||
}
|
||||
|
||||
async fn list_holdinvoices_to_transactions(
|
||||
hold_client: &mut HoldClient<Channel>,
|
||||
rpc: &mut ClnRpc,
|
||||
transactions: &mut Vec<nip47::LookupInvoiceResponse>,
|
||||
) -> Result<(), nip47::NIP47Error> {
|
||||
let lookup_request = ListRequest { constraint: None };
|
||||
|
||||
let hold_lookup = hold_client
|
||||
.list(lookup_request)
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Other,
|
||||
message: format!("Could not fetch hold invoices: {e}"),
|
||||
})?
|
||||
.into_inner();
|
||||
|
||||
for hold_invoice in hold_lookup.invoices {
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
string: hold_invoice.invoice.clone(),
|
||||
})
|
||||
.await
|
||||
.map_err(|e| nip47::NIP47Error {
|
||||
code: nip47::ErrorCode::Internal,
|
||||
message: e.to_string(),
|
||||
})?;
|
||||
|
||||
let description = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description,
|
||||
_ => continue,
|
||||
};
|
||||
let description_hash = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => None,
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded.description_hash.map(|h| h.to_string()),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let created_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => {
|
||||
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
|
||||
}
|
||||
DecodeType::BOLT11_INVOICE => Timestamp::from_secs(invoice_decoded.created_at.unwrap()),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let amount = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded.invoice_amount_msat.unwrap().msat(),
|
||||
DecodeType::BOLT11_INVOICE => {
|
||||
if let Some(amt) = invoice_decoded.amount_msat {
|
||||
amt.msat()
|
||||
} else {
|
||||
// amount: `any` but have to put a value...
|
||||
0
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let expires_at = match invoice_decoded.item_type {
|
||||
DecodeType::BOLT12_INVOICE => invoice_decoded
|
||||
.invoice_relative_expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(u64::from(e_at))),
|
||||
DecodeType::BOLT11_INVOICE => invoice_decoded
|
||||
.expiry
|
||||
.map(|e_at| created_at + Timestamp::from_secs(e_at)),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let state = match hold_invoice.state() {
|
||||
hold::InvoiceState::Unpaid => nip47::TransactionState::Pending,
|
||||
hold::InvoiceState::Accepted => nip47::TransactionState::Accepted,
|
||||
hold::InvoiceState::Paid => nip47::TransactionState::Settled,
|
||||
hold::InvoiceState::Cancelled => nip47::TransactionState::Expired,
|
||||
};
|
||||
|
||||
let settled_at = if hold_invoice.settled_at() != 0 {
|
||||
Some(Timestamp::from_secs(hold_invoice.settled_at()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let preimage = if hold_invoice.state() == hold::InvoiceState::Paid {
|
||||
Some(hex::encode(hold_invoice.preimage()))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
transactions.push(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(hold_invoice.invoice.clone()),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
payment_hash: hex::encode(hold_invoice.payment_hash),
|
||||
amount,
|
||||
fees_paid: 0,
|
||||
created_at,
|
||||
expires_at,
|
||||
settled_at,
|
||||
metadata: None,
|
||||
state: Some(state),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn make_lookup_response_from_listinvoices(
|
||||
rpc: &mut ClnRpc,
|
||||
list_invoice: ListinvoicesInvoices,
|
||||
|
|
@ -557,10 +580,12 @@ async fn make_lookup_response_from_listinvoices(
|
|||
message: NOT_INV_ERR.to_owned(),
|
||||
});
|
||||
|
||||
let invstring = if list_invoice.bolt11.is_some() {
|
||||
list_invoice.bolt11.as_ref().unwrap()
|
||||
let invstring = if let Some(bolt11) = list_invoice.bolt11 {
|
||||
bolt11
|
||||
} else if let Some(bolt12) = list_invoice.bolt12 {
|
||||
bolt12
|
||||
} else {
|
||||
list_invoice.bolt12.as_ref().unwrap()
|
||||
return not_invoice_err;
|
||||
};
|
||||
let invoice_decoded = rpc
|
||||
.call_typed(&DecodeRequest {
|
||||
|
|
@ -628,7 +653,7 @@ async fn make_lookup_response_from_listinvoices(
|
|||
|
||||
Ok(nip47::LookupInvoiceResponse {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: Some(invstring.to_owned()),
|
||||
invoice: Some(invstring),
|
||||
description,
|
||||
description_hash,
|
||||
preimage,
|
||||
|
|
|
|||
|
|
@ -22,15 +22,13 @@ use nostr::{
|
|||
Tag,
|
||||
Timestamp,
|
||||
event::FinalizeEventAsync,
|
||||
key::{Keys, PublicKey, SecretKey},
|
||||
nips::{nip04, nip44, nip47},
|
||||
};
|
||||
use nostr_sdk::client::Client;
|
||||
|
||||
use crate::{
|
||||
OPT_NOTIFICATIONS,
|
||||
hold::{InvoiceState, ListRequest, TrackRequest, list_request::Constraint},
|
||||
structs::{NOT_INV_ERR, PluginState},
|
||||
structs::{NOT_INV_ERR, PluginState, WalletService},
|
||||
};
|
||||
|
||||
pub async fn payment_received_handler(
|
||||
|
|
@ -66,10 +64,14 @@ pub async fn payment_received_handler(
|
|||
let invoice = invoice_resp
|
||||
.first()
|
||||
.ok_or_else(|| anyhow!("invoice not found"))?;
|
||||
let invstring = if invoice.bolt11.is_some() {
|
||||
invoice.bolt11.as_ref().unwrap()
|
||||
let invstring = if let Some(bolt11) = &invoice.bolt11 {
|
||||
bolt11.clone()
|
||||
} else if let Some(bolt12) = &invoice.bolt12 {
|
||||
bolt12.clone()
|
||||
} else {
|
||||
invoice.bolt12.as_ref().unwrap()
|
||||
return Err(anyhow!(
|
||||
"Listinvoices has neither returned bolt11 or bolt12 field"
|
||||
));
|
||||
};
|
||||
|
||||
let invoice_decoded = rpc
|
||||
|
|
@ -83,8 +85,8 @@ pub async fn payment_received_handler(
|
|||
|
||||
let clients = plugin.state().handles.lock().await;
|
||||
|
||||
for (client, client_pubkey, wallet_secret) in clients.values() {
|
||||
send_notification(¬ification, client, client_pubkey.clone(), wallet_secret).await?;
|
||||
for wallet_service in clients.values() {
|
||||
send_notification(¬ification, wallet_service).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -92,7 +94,7 @@ pub async fn payment_received_handler(
|
|||
|
||||
fn make_payment_received_from_listinvoices(
|
||||
invoice: &ListinvoicesInvoices,
|
||||
invstring: &str,
|
||||
invstring: String,
|
||||
invoice_decoded: DecodeResponse,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
let not_invoice_err = Err(anyhow!(NOT_INV_ERR.to_owned()));
|
||||
|
|
@ -160,7 +162,7 @@ fn make_payment_received_from_listinvoices(
|
|||
notification_type: nip47::NotificationType::PaymentReceived,
|
||||
notification: nip47::NotificationResult::PaymentReceived(nip47::PaymentNotification {
|
||||
transaction_type: Some(nip47::TransactionType::Incoming),
|
||||
invoice: invstring.to_owned(),
|
||||
invoice: invstring,
|
||||
description: description.clone(),
|
||||
description_hash: description_hash.clone(),
|
||||
preimage: preimage.clone(),
|
||||
|
|
@ -220,8 +222,8 @@ pub async fn payment_sent_handler(
|
|||
|
||||
let clients = plugin.state().handles.lock().await;
|
||||
|
||||
for (client, client_pubkey, wallet_secret) in clients.values() {
|
||||
send_notification(¬ification, client, client_pubkey.clone(), wallet_secret).await?;
|
||||
for wallet_service in clients.values() {
|
||||
send_notification(¬ification, wallet_service).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
@ -342,18 +344,19 @@ async fn make_payment_sent_from_listpays(
|
|||
|
||||
async fn send_notification(
|
||||
notification: &String,
|
||||
client: &Client,
|
||||
client_pubkey: PublicKey,
|
||||
wallet_secret: &Keys,
|
||||
wallet_service: &WalletService,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
log::debug!("NOTIFICATION: {notification}");
|
||||
let content_encrypted_nip04 =
|
||||
nip04::encrypt(wallet_secret.secret_key(), &client_pubkey, notification)?;
|
||||
let content_encrypted_nip04 = nip04::encrypt(
|
||||
wallet_service.wallet_secret.secret_key(),
|
||||
&wallet_service.client_pubkey,
|
||||
notification,
|
||||
)?;
|
||||
let event_nip04 = EventBuilder::new(Kind::from_u16(23196), content_encrypted_nip04)
|
||||
.tag(Tag::public_key(client_pubkey))
|
||||
.finalize_async(wallet_secret)
|
||||
.tag(Tag::public_key(wallet_service.client_pubkey))
|
||||
.finalize_async(&wallet_service.wallet_secret)
|
||||
.await?;
|
||||
let nip04_result = client.send_event(&event_nip04).await?;
|
||||
let nip04_result = wallet_service.client.send_event(&event_nip04).await?;
|
||||
if nip04_result.success.is_empty() {
|
||||
log::warn!(
|
||||
"None of the relays accepted our nip04 notification: {}",
|
||||
|
|
@ -367,16 +370,16 @@ async fn send_notification(
|
|||
log::debug!("NIP04 NOTIFICATION SENT: {event_nip04:?}");
|
||||
|
||||
let content_encrypted_nip44 = nip44::encrypt(
|
||||
wallet_secret.secret_key(),
|
||||
&client_pubkey,
|
||||
wallet_service.wallet_secret.secret_key(),
|
||||
&wallet_service.client_pubkey,
|
||||
notification,
|
||||
nip44::Version::V2,
|
||||
)?;
|
||||
let event_nip44 = EventBuilder::new(Kind::from_u16(23197), content_encrypted_nip44)
|
||||
.tag(Tag::public_key(client_pubkey))
|
||||
.finalize_async(wallet_secret)
|
||||
.tag(Tag::public_key(wallet_service.client_pubkey))
|
||||
.finalize_async(&wallet_service.wallet_secret)
|
||||
.await?;
|
||||
let nip44_result = client.send_event(&event_nip44).await?;
|
||||
let nip44_result = wallet_service.client.send_event(&event_nip44).await?;
|
||||
if nip44_result.success.is_empty() {
|
||||
log::warn!(
|
||||
"None of the relays accepted our nip44 notification: {}",
|
||||
|
|
@ -392,6 +395,7 @@ async fn send_notification(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn holdinvoice_accepted_handler(
|
||||
plugin: Plugin<PluginState>,
|
||||
payment_hash: Vec<u8>,
|
||||
|
|
@ -514,8 +518,8 @@ pub async fn holdinvoice_accepted_handler(
|
|||
};
|
||||
let notification = serde_json::to_string(&content).unwrap();
|
||||
|
||||
for (client, client_pubkey, wallet_secret) in clients.values() {
|
||||
send_notification(¬ification, client, client_pubkey.clone(), wallet_secret).await?;
|
||||
for wallet_service in clients.values() {
|
||||
send_notification(¬ification, wallet_service).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ pub async fn nwc_budget(
|
|||
clients
|
||||
.get(&label)
|
||||
.ok_or_else(|| anyhow!("No client found for label: {label}"))?
|
||||
.0
|
||||
.client
|
||||
.clone(),
|
||||
method_capabilities,
|
||||
wallet_keys,
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ pub const NOT_INV_ERR: &str = "Not an invoice or invalid invoice";
|
|||
#[derive(Clone)]
|
||||
pub struct PluginState {
|
||||
pub config: Arc<Mutex<Config>>,
|
||||
pub handles:
|
||||
Arc<tokio::sync::Mutex<HashMap<String, (client::Client, nostr::PublicKey, nostr::Keys)>>>,
|
||||
pub handles: Arc<tokio::sync::Mutex<HashMap<String, WalletService>>>,
|
||||
pub rpc_lock: Arc<tokio::sync::Mutex<ClnRpc>>,
|
||||
pub budget_jobs: Arc<Mutex<HashMap<String, oneshot::Sender<()>>>>,
|
||||
pub hold_client: Arc<Mutex<Option<HoldClient<Channel>>>>,
|
||||
|
|
@ -33,6 +32,12 @@ impl PluginState {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct WalletService {
|
||||
pub client: client::Client,
|
||||
pub client_pubkey: nostr::PublicKey,
|
||||
pub wallet_secret: nostr::Keys,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Config {
|
||||
pub relays: Vec<RelayUrl>,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ description = "python dependencies for running tests"
|
|||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"pytest>=7.4,<9",
|
||||
"pytest>=8,<10",
|
||||
"pytest-asyncio>=0.23.8,<2",
|
||||
"pytest-xdist>=3.7,<4",
|
||||
"pytest-timeout>=2.4,<3",
|
||||
"nostr-sdk>=0.44",
|
||||
"pyln-testing>=25.5",
|
||||
"pyln-client>=25.5",
|
||||
"pyln-proto>=25.5",
|
||||
"pyln-testing>=25.9",
|
||||
"pyln-client>=25.9",
|
||||
"pyln-proto>=25.9",
|
||||
]
|
||||
|
|
|
|||
109
tests/test_cln-nip47.py
Executable file → Normal file
109
tests/test_cln-nip47.py
Executable file → Normal file
|
|
@ -1,43 +1,46 @@
|
|||
# ruff: noqa: DTZ005
|
||||
|
||||
import asyncio
|
||||
import hashlib
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
import asyncio
|
||||
import secrets
|
||||
import time
|
||||
import uuid
|
||||
from collections.abc import Awaitable, Callable
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any, Awaitable, Callable, Union
|
||||
from typing import Any, Union
|
||||
|
||||
import pytest
|
||||
from pyln.testing.fixtures import * # noqa: F403
|
||||
from pyln.testing.utils import RpcError, wait_for, TIMEOUT
|
||||
from util import generate_random_label, get_plugin, get_hold # noqa: F401
|
||||
|
||||
from nostr_sdk import (
|
||||
Client,
|
||||
RelayUrl,
|
||||
Event,
|
||||
EventBuilder,
|
||||
Filter,
|
||||
Event,
|
||||
HandleNotification,
|
||||
Keys,
|
||||
KeysendTlvRecord,
|
||||
Kind,
|
||||
HandleNotification,
|
||||
ListTransactionsRequest,
|
||||
LookupInvoiceRequest,
|
||||
MakeInvoiceRequest,
|
||||
Method,
|
||||
NostrSdkError,
|
||||
NostrSigner,
|
||||
NostrWalletConnectUri,
|
||||
Nwc,
|
||||
PayInvoiceRequest,
|
||||
PayKeysendRequest,
|
||||
PublicKey,
|
||||
RelayUrl,
|
||||
Tag,
|
||||
TagKind,
|
||||
Method,
|
||||
PublicKey,
|
||||
TransactionType,
|
||||
)
|
||||
from pyln.testing.fixtures import *
|
||||
from pyln.testing.utils import TIMEOUT, RpcError, wait_for
|
||||
from util import generate_random_label, get_hold, get_plugin # noqa: F401
|
||||
|
||||
LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -83,7 +86,7 @@ async def fetch_event_responses(
|
|||
handler = NotificationHandler(events, stop_after)
|
||||
task = asyncio.create_task(client.handle_notifications(handler))
|
||||
|
||||
time.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
if inspect.iscoroutine(action):
|
||||
action_result = await action
|
||||
elif inspect.iscoroutinefunction(action):
|
||||
|
|
@ -123,7 +126,7 @@ async def fetch_info_event(
|
|||
while events.len() < 1 and (datetime.now() - start_time) < timedelta(
|
||||
seconds=TIMEOUT
|
||||
):
|
||||
time.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
events = await client.fetch_events(
|
||||
response_filter, timeout=timedelta(seconds=1)
|
||||
)
|
||||
|
|
@ -135,7 +138,7 @@ async def fetch_info_event(
|
|||
@pytest.mark.asyncio
|
||||
async def test_get_balance(nostr_relay, node_factory, get_plugin): # noqa: F811
|
||||
url = nostr_relay
|
||||
l1, l2 = node_factory.line_graph(
|
||||
l1, _l2 = node_factory.line_graph(
|
||||
2,
|
||||
wait_for_announce=True,
|
||||
opts=[
|
||||
|
|
@ -237,7 +240,7 @@ async def test_get_info(nostr_relay, node_factory, get_plugin): # noqa: F811
|
|||
},
|
||||
)
|
||||
l1.daemon.wait_for_log("All NWC's loaded")
|
||||
time.sleep(5)
|
||||
await asyncio.sleep(5)
|
||||
await client.connect()
|
||||
info_event = await fetch_info_event(client, uri)
|
||||
get_info = await nwc.get_info()
|
||||
|
|
@ -341,7 +344,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81
|
|||
MakeInvoiceRequest(
|
||||
amount=3001,
|
||||
description="test2",
|
||||
description_hash=hashlib.sha256("test2".encode()).hexdigest(),
|
||||
description_hash=hashlib.sha256(b"test2").hexdigest(),
|
||||
expiry=120,
|
||||
)
|
||||
)
|
||||
|
|
@ -367,7 +370,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81
|
|||
MakeInvoiceRequest(
|
||||
amount=3001,
|
||||
description=None,
|
||||
description_hash=hashlib.sha256("test2".encode()).hexdigest(),
|
||||
description_hash=hashlib.sha256(b"test2").hexdigest(),
|
||||
expiry=120,
|
||||
)
|
||||
)
|
||||
|
|
@ -378,7 +381,7 @@ async def test_make_invoice(nostr_relay, node_factory, get_plugin): # noqa: F81
|
|||
MakeInvoiceRequest(
|
||||
amount=3001,
|
||||
description="test1",
|
||||
description_hash=hashlib.sha256("test2".encode()).hexdigest(),
|
||||
description_hash=hashlib.sha256(b"test2").hexdigest(),
|
||||
expiry=120,
|
||||
)
|
||||
)
|
||||
|
|
@ -562,7 +565,7 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F
|
|||
MakeInvoiceRequest(
|
||||
amount=3001,
|
||||
description="test2",
|
||||
description_hash=hashlib.sha256("test2".encode()).hexdigest(),
|
||||
description_hash=hashlib.sha256(b"test2").hexdigest(),
|
||||
expiry=1000,
|
||||
)
|
||||
)
|
||||
|
|
@ -584,9 +587,7 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F
|
|||
assert invoice_lookup.created_at.as_secs() == pytest.approx(
|
||||
invoice_decode["created_at"], abs=3
|
||||
)
|
||||
assert (
|
||||
invoice_lookup.description_hash == hashlib.sha256("test2".encode()).hexdigest()
|
||||
)
|
||||
assert invoice_lookup.description_hash == hashlib.sha256(b"test2").hexdigest()
|
||||
assert invoice_lookup.expires_at.as_secs() == pytest.approx(
|
||||
listpays_rpc["expires_at"], abs=3
|
||||
)
|
||||
|
|
@ -613,9 +614,7 @@ async def test_lookup_invoice(nostr_relay, node_factory, get_plugin): # noqa: F
|
|||
assert invoice_lookup.created_at.as_secs() == pytest.approx(
|
||||
invoice_decode["created_at"], abs=3
|
||||
)
|
||||
assert (
|
||||
invoice_lookup.description_hash == hashlib.sha256("test2".encode()).hexdigest()
|
||||
)
|
||||
assert invoice_lookup.description_hash == hashlib.sha256(b"test2").hexdigest()
|
||||
assert invoice_lookup.expires_at.as_secs() == pytest.approx(
|
||||
listpays_rpc["expires_at"], abs=3
|
||||
)
|
||||
|
|
@ -693,9 +692,9 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa
|
|||
],
|
||||
)
|
||||
l1.rpc.call(
|
||||
"pay",
|
||||
"xpay",
|
||||
{
|
||||
"bolt11": l2.rpc.call(
|
||||
"invstring": l2.rpc.call(
|
||||
"invoice",
|
||||
{
|
||||
"amount_msat": 500000000,
|
||||
|
|
@ -710,7 +709,7 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa
|
|||
l2.rpc.call("listpeerchannels", [l1.info["id"]])["channels"][0][
|
||||
"spendable_msat"
|
||||
]
|
||||
> 30001
|
||||
> 400000000
|
||||
)
|
||||
)
|
||||
uri_str = l1.rpc.call("nip47-create", ["test1"])["uri"]
|
||||
|
|
@ -741,7 +740,7 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa
|
|||
amount=3000, description="test2", description_hash=None, expiry=None
|
||||
)
|
||||
)
|
||||
result = l2.rpc.call("pay", [invoice.invoice])
|
||||
result = l2.rpc.call("xpay", [invoice.invoice])
|
||||
|
||||
invoice = await nwc.make_invoice(
|
||||
MakeInvoiceRequest(
|
||||
|
|
@ -762,19 +761,23 @@ async def test_list_transactions(nostr_relay, node_factory, get_plugin): # noqa
|
|||
)
|
||||
assert len(result) == 22
|
||||
for tx in result:
|
||||
tx.description is not None
|
||||
tx.invoice is not None
|
||||
tx.amount is not None
|
||||
tx.created_at is not None
|
||||
tx.description_hash is None
|
||||
tx.expires_at is None
|
||||
tx.preimage is not None
|
||||
tx.settled_at is not None
|
||||
tx.metadata is None
|
||||
tx.transaction_type is not None
|
||||
tx.state is not None
|
||||
tx.payment_hash is not None
|
||||
tx.fees_paid is not None
|
||||
assert tx.description is not None
|
||||
assert tx.invoice is not None
|
||||
assert tx.amount is not None
|
||||
assert tx.created_at is not None
|
||||
assert tx.description_hash is None
|
||||
assert tx.preimage is not None
|
||||
assert tx.settled_at is not None
|
||||
assert tx.metadata is None
|
||||
assert tx.transaction_type is not None
|
||||
assert tx.state is not None
|
||||
assert tx.payment_hash is not None
|
||||
assert tx.fees_paid is not None
|
||||
|
||||
if tx.transaction_type == TransactionType.INCOMING:
|
||||
assert tx.expires_at is not None
|
||||
else:
|
||||
assert tx.expires_at is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
|
@ -927,7 +930,7 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8
|
|||
},
|
||||
)
|
||||
l1.daemon.wait_for_log("All NWC's loaded")
|
||||
time.sleep(3)
|
||||
await asyncio.sleep(3)
|
||||
await client.connect()
|
||||
await fetch_info_event(client, uri)
|
||||
|
||||
|
|
@ -940,7 +943,7 @@ async def test_notifications(nostr_relay, node_factory, get_plugin): # noqa: F8
|
|||
},
|
||||
)
|
||||
with pytest.raises(AssertionError, match="0 == 1"):
|
||||
(responses3, pay3) = await fetch_event_responses(
|
||||
(_responses3, _pay3) = await fetch_event_responses(
|
||||
client,
|
||||
client_pubkey,
|
||||
23196,
|
||||
|
|
@ -1036,7 +1039,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811
|
|||
},
|
||||
)
|
||||
l1.daemon.wait_for_log("All NWC's loaded")
|
||||
time.sleep(3)
|
||||
await asyncio.sleep(3)
|
||||
uri = NostrWalletConnectUri.parse(uri_str)
|
||||
signer = NostrSigner.keys(Keys(uri.secret()))
|
||||
client = Client(signer)
|
||||
|
|
@ -1066,7 +1069,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811
|
|||
},
|
||||
)
|
||||
l1.daemon.wait_for_log("All NWC's loaded")
|
||||
time.sleep(3)
|
||||
await asyncio.sleep(3)
|
||||
await client.connect()
|
||||
await fetch_info_event(client, uri)
|
||||
with pytest.raises(NostrSdkError.Generic, match="Payment exceeds budget"):
|
||||
|
|
@ -1107,7 +1110,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811
|
|||
PayInvoiceRequest(id=None, amount=None, invoice=invoice_exceeded["bolt11"])
|
||||
)
|
||||
|
||||
time.sleep(11)
|
||||
await asyncio.sleep(11)
|
||||
|
||||
list = l1.rpc.call("nip47-list", ["test1"])[0]
|
||||
assert list["test1"]["budget_msat"] == 3000
|
||||
|
|
@ -1138,7 +1141,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811
|
|||
},
|
||||
)
|
||||
l1.daemon.wait_for_log("All NWC's loaded")
|
||||
time.sleep(3)
|
||||
await asyncio.sleep(3)
|
||||
await client.connect()
|
||||
await fetch_info_event(client, uri)
|
||||
|
||||
|
|
@ -1147,7 +1150,7 @@ async def test_persistency(nostr_relay, node_factory, get_plugin): # noqa: F811
|
|||
PayInvoiceRequest(id=None, amount=None, invoice=invoice_exceeded["bolt11"])
|
||||
)
|
||||
|
||||
time.sleep(11)
|
||||
await asyncio.sleep(11)
|
||||
|
||||
list = l1.rpc.call("nip47-list", ["test1"])[0]
|
||||
assert list["test1"]["budget_msat"] == 3000
|
||||
|
|
@ -1242,7 +1245,7 @@ async def test_budget_command(nostr_relay, node_factory, get_plugin): # noqa: F
|
|||
== "payment_received payment_sent"
|
||||
)
|
||||
|
||||
time.sleep(18)
|
||||
await asyncio.sleep(18)
|
||||
|
||||
balance = await nwc.get_balance()
|
||||
assert balance == 5000
|
||||
|
|
@ -1706,7 +1709,7 @@ async def test_hold_invoice(
|
|||
|
||||
start_time = datetime.now()
|
||||
while (datetime.now() - start_time) < timedelta(seconds=10):
|
||||
time.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
try:
|
||||
await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
|
|
@ -1757,7 +1760,7 @@ async def test_hold_invoice(
|
|||
|
||||
start_time = datetime.now()
|
||||
while (datetime.now() - start_time) < timedelta(seconds=10):
|
||||
time.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
try:
|
||||
await nwc.lookup_invoice(
|
||||
LookupInvoiceRequest(
|
||||
|
|
|
|||
949
tests/uv.lock
generated
949
tests/uv.lock
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue