refactor nwc_lookup

This commit is contained in:
daywalker90 2025-11-09 13:34:23 +01:00
parent 66e6378a72
commit 061b27653c
No known key found for this signature in database
2 changed files with 228 additions and 393 deletions

View file

@ -4,14 +4,17 @@ use cln_plugin::Plugin;
use cln_rpc::{
model::{
requests::{DecodeRequest, ListinvoicesRequest, ListpaysRequest},
responses::{ListinvoicesInvoicesStatus, ListpaysPaysStatus},
responses::{
ListinvoicesInvoices, ListinvoicesInvoicesStatus, ListpaysPays, ListpaysPaysStatus,
},
},
primitives::Sha256,
ClnRpc,
};
use nostr_sdk::nips::nip47;
use nostr_sdk::Timestamp;
use crate::structs::PluginState;
use crate::structs::{PluginState, NOT_INV_ERR};
pub async fn lookup_invoice(
plugin: Plugin<PluginState>,
@ -26,11 +29,6 @@ pub async fn lookup_invoice(
});
}
let not_invoice_err = Err(nip47::NIP47Error {
code: nip47::ErrorCode::Other,
message: "Not an invoice or invalid invoice".to_owned(),
});
let invoice = if params.payment_hash.is_some() && params.invoice.is_some() {
None
} else {
@ -56,92 +54,8 @@ pub async fn lookup_invoice(
if invoices.len() == 1 {
let invoice_response = invoices.into_iter().next().unwrap();
let invstring = if invoice_response.bolt11.is_some() {
invoice_response.bolt11.unwrap()
} else {
invoice_response.bolt12.unwrap()
};
let invoice_decoded = rpc
.call_typed(&DecodeRequest {
string: invstring.clone(),
})
.await
.map_err(|e| nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: e.to_string(),
})?;
if !invoice_decoded.valid {
return not_invoice_err;
}
let description = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
invoice_decoded.offer_description
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => invoice_decoded.description,
_ => return not_invoice_err,
};
let description_hash = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => None,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
invoice_decoded.description_hash.map(|h| h.to_string())
}
_ => return not_invoice_err,
};
let amount = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
invoice_decoded.invoice_amount_msat.unwrap().msat()
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
if let Some(amt) = invoice_decoded.amount_msat {
amt.msat()
} else if let Some(a) = invoice_response.amount_msat {
a.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => return not_invoice_err,
};
let created_at = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
Timestamp::from_secs(invoice_decoded.created_at.unwrap())
}
_ => return not_invoice_err,
};
let preimage = invoice_response
.payment_preimage
.map(|p| hex::encode(p.to_vec()));
let state = match invoice_response.status {
ListinvoicesInvoicesStatus::UNPAID => nip47::TransactionState::Pending,
ListinvoicesInvoicesStatus::PAID => nip47::TransactionState::Settled,
ListinvoicesInvoicesStatus::EXPIRED => nip47::TransactionState::Expired,
};
Ok(nip47::LookupInvoiceResponse {
transaction_type: Some(nip47::TransactionType::Incoming),
invoice: Some(invstring),
description,
description_hash,
preimage,
payment_hash: invoice_response.payment_hash.to_string(),
amount,
fees_paid: 0,
created_at,
expires_at: Some(Timestamp::from_secs(invoice_response.expires_at)),
settled_at: invoice_response.paid_at.map(Timestamp::from_secs),
metadata: None,
state: Some(state),
})
make_lookup_response_from_listinvoices(&mut rpc, invoice_response).await
} else {
let payment_hash_hash = if let Some(hash) = params.payment_hash {
if let Ok(res) = Sha256::from_str(&hash) {
@ -179,101 +93,8 @@ pub async fn lookup_invoice(
});
}
let list_pay = pays.into_iter().next().unwrap();
let invstring = if list_pay.bolt11.is_some() {
list_pay.bolt11
} else {
list_pay.bolt12
};
let invoice_decoded = if let Some(invstr) = &invstring {
Some(
rpc.call_typed(&DecodeRequest {
string: invstr.clone(),
})
.await
.map_err(|e| nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: e.to_string(),
})?,
)
} else {
None
};
if invoice_decoded.is_some() && !invoice_decoded.as_ref().unwrap().valid {
return not_invoice_err;
}
let description_hash = if let Some(inv_dec) = &invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => None,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
inv_dec.description_hash.map(|h| h.to_string())
}
_ => return not_invoice_err,
}
} else {
None
};
let amount = if let Some(amt) = list_pay.amount_msat {
amt.msat()
} else if let Some(inv_dec) = &invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
inv_dec.invoice_amount_msat.unwrap().msat()
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
if let Some(amt) = inv_dec.amount_msat {
amt.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => return not_invoice_err,
}
} else {
return not_invoice_err;
};
let description = if let Some(inv_dec) = invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => inv_dec.offer_description,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => inv_dec.description,
_ => return not_invoice_err,
}
} else {
list_pay.description
};
let fees_paid = if let Some(amt_sent) = list_pay.amount_sent_msat {
amt_sent.msat() - amount
} else {
0
};
let preimage = list_pay.preimage.map(|p| hex::encode(p.to_vec()));
let state = match list_pay.status {
ListpaysPaysStatus::PENDING => nip47::TransactionState::Pending,
ListpaysPaysStatus::FAILED => nip47::TransactionState::Failed,
ListpaysPaysStatus::COMPLETE => nip47::TransactionState::Settled,
};
Ok(nip47::LookupInvoiceResponse {
transaction_type: Some(nip47::TransactionType::Outgoing),
invoice: invstring,
description,
description_hash,
preimage,
payment_hash: list_pay.payment_hash.to_string(),
amount,
fees_paid,
created_at: Timestamp::from_secs(list_pay.created_at),
expires_at: None,
settled_at: list_pay.completed_at.map(Timestamp::from_secs),
metadata: None,
state: Some(state),
})
make_lookup_response_from_listpays(&mut rpc, list_pay).await
}
}
@ -316,112 +137,11 @@ pub async fn list_transactions(
if !unpaid && list_invoice.status == ListinvoicesInvoicesStatus::UNPAID {
continue;
}
let invstring = if list_invoice.bolt11.is_some() {
list_invoice.bolt11.unwrap()
} else {
list_invoice.bolt12.unwrap()
};
let invoice_decoded = rpc
.call_typed(&DecodeRequest {
string: invstring.clone(),
})
.await
.map_err(|e| nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: e.to_string(),
})?;
if !invoice_decoded.valid {
continue;
match make_lookup_response_from_listinvoices(&mut rpc, list_invoice).await {
Ok(t) => transactions.push(t),
Err(_e) => (),
}
let created_at = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
Timestamp::from_secs(invoice_decoded.created_at.unwrap())
}
_ => continue,
};
if let Some(f) = params.from {
if created_at < f {
continue;
}
}
if let Some(u) = params.until {
if created_at > u {
continue;
}
}
let description = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
invoice_decoded.offer_description
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
invoice_decoded.description
}
_ => continue,
};
let description_hash = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => None,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
invoice_decoded.description_hash.map(|h| h.to_string())
}
_ => continue,
};
let amount = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
invoice_decoded.invoice_amount_msat.unwrap().msat()
}
cln_rpc::model::responses::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 {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
invoice_decoded.invoice_relative_expiry.map(|e_at| {
Timestamp::from_secs(
invoice_decoded.invoice_created_at.unwrap() + u64::from(e_at),
)
})
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => invoice_decoded
.expiry
.map(|e_at| Timestamp::from_secs(invoice_decoded.created_at.unwrap() + e_at)),
_ => continue,
};
let preimage = list_invoice
.payment_preimage
.map(|p| hex::encode(p.to_vec()));
let state = match list_invoice.status {
ListinvoicesInvoicesStatus::PAID => nip47::TransactionState::Settled,
ListinvoicesInvoicesStatus::EXPIRED => nip47::TransactionState::Expired,
ListinvoicesInvoicesStatus::UNPAID => nip47::TransactionState::Pending,
};
transactions.push(nip47::LookupInvoiceResponse {
transaction_type: Some(nip47::TransactionType::Incoming),
invoice: Some(invstring),
description,
description_hash,
preimage,
payment_hash: list_invoice.payment_hash.to_string(),
amount,
fees_paid: 0,
created_at,
expires_at,
settled_at: list_invoice.paid_at.map(Timestamp::from_secs),
metadata: None,
state: Some(state),
});
}
}
@ -443,126 +163,239 @@ pub async fn list_transactions(
.pays;
for list_pay in list_pays {
let invstring = if list_pay.bolt11.is_some() {
list_pay.bolt11
} else {
list_pay.bolt12
};
let invoice_decoded = if let Some(invstr) = &invstring {
Some(
rpc.call_typed(&DecodeRequest {
string: invstr.clone(),
})
.await
.map_err(|e| nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: e.to_string(),
})?,
)
} else {
None
};
if invoice_decoded.is_some() && !invoice_decoded.as_ref().unwrap().valid {
continue;
match make_lookup_response_from_listpays(&mut rpc, list_pay).await {
Ok(t) => transactions.push(t),
Err(_e) => (),
}
let description_hash = if let Some(inv_dec) = &invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => None,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
inv_dec.description_hash.map(|h| h.to_string())
}
_ => continue,
}
} else {
None
};
let amount = if let Some(amt) = list_pay.amount_msat {
amt.msat()
} else if let Some(inv_dec) = &invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
inv_dec.invoice_amount_msat.unwrap().msat()
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
if let Some(amt) = inv_dec.amount_msat {
amt.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => continue,
}
} else {
continue;
};
let description = if let Some(inv_dec) = invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
inv_dec.offer_description
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => inv_dec.description,
_ => continue,
}
} else {
list_pay.description
};
let fees_paid = if let Some(amt_sent) = list_pay.amount_sent_msat {
amt_sent.msat() - amount
} else {
0
};
let preimage = list_pay.preimage.map(|p| hex::encode(p.to_vec()));
let state = match list_pay.status {
ListpaysPaysStatus::COMPLETE => nip47::TransactionState::Settled,
ListpaysPaysStatus::FAILED => nip47::TransactionState::Failed,
ListpaysPaysStatus::PENDING => nip47::TransactionState::Pending,
};
transactions.push(nip47::LookupInvoiceResponse {
transaction_type: Some(nip47::TransactionType::Outgoing),
invoice: invstring,
description,
description_hash,
preimage,
payment_hash: list_pay.payment_hash.to_string(),
amount,
fees_paid,
created_at: Timestamp::from_secs(list_pay.created_at),
expires_at: None,
settled_at: list_pay.completed_at.map(Timestamp::from_secs),
metadata: None,
state: Some(state),
});
}
}
transactions.sort_by_key(|t| Reverse(t.created_at));
if let Some(off) = params.offset.map(|o| o as usize) {
if off < transactions.len() {
transactions.drain(0..off);
} else {
if let Some(offset) = params.offset {
let len = transactions.len() as u64;
if offset >= len {
transactions.clear();
} else {
let off = usize::try_from(offset).unwrap();
transactions.drain(0..off);
}
}
if let Some(l) = params.limit {
if transactions.len() > (l as usize) {
transactions = transactions.drain(0..(l as usize)).collect();
if let Some(limit) = params.limit {
let len = transactions.len() as u64;
if limit < len {
let l = usize::try_from(limit).unwrap();
transactions = transactions.drain(0..l).collect();
}
}
transactions = trim_to_size(transactions, 127 * 1024);
Ok(transactions)
}
async fn make_lookup_response_from_listinvoices(
rpc: &mut ClnRpc,
list_invoice: ListinvoicesInvoices,
) -> Result<nip47::LookupInvoiceResponse, nip47::NIP47Error> {
let not_invoice_err = Err(nip47::NIP47Error {
code: nip47::ErrorCode::Other,
message: NOT_INV_ERR.to_owned(),
});
let invstring = if list_invoice.bolt11.is_some() {
list_invoice.bolt11.as_ref().unwrap()
} else {
list_invoice.bolt12.as_ref().unwrap()
};
let invoice_decoded = rpc
.call_typed(&DecodeRequest {
string: invstring.clone(),
})
.await
.map_err(|e| nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: e.to_string(),
})?;
if !invoice_decoded.valid {
return not_invoice_err;
}
let description = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => invoice_decoded.offer_description,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => invoice_decoded.description,
_ => return not_invoice_err,
};
let description_hash = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => None,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
invoice_decoded.description_hash.map(|h| h.to_string())
}
_ => return not_invoice_err,
};
let amount = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
invoice_decoded.invoice_amount_msat.unwrap().msat()
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
if let Some(amt) = invoice_decoded.amount_msat {
amt.msat()
} else if let Some(a) = list_invoice.amount_msat {
a.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => return not_invoice_err,
};
let created_at = match invoice_decoded.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
Timestamp::from_secs(invoice_decoded.invoice_created_at.unwrap())
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
Timestamp::from_secs(invoice_decoded.created_at.unwrap())
}
_ => return not_invoice_err,
};
let preimage = list_invoice
.payment_preimage
.map(|p| hex::encode(p.to_vec()));
let state = match list_invoice.status {
ListinvoicesInvoicesStatus::UNPAID => nip47::TransactionState::Pending,
ListinvoicesInvoicesStatus::PAID => nip47::TransactionState::Settled,
ListinvoicesInvoicesStatus::EXPIRED => nip47::TransactionState::Expired,
};
Ok(nip47::LookupInvoiceResponse {
transaction_type: Some(nip47::TransactionType::Incoming),
invoice: Some(invstring.to_owned()),
description,
description_hash,
preimage,
payment_hash: list_invoice.payment_hash.to_string(),
amount,
fees_paid: 0,
created_at,
expires_at: Some(Timestamp::from_secs(list_invoice.expires_at)),
settled_at: list_invoice.paid_at.map(Timestamp::from_secs),
metadata: None,
state: Some(state),
})
}
async fn make_lookup_response_from_listpays(
rpc: &mut ClnRpc,
list_pay: ListpaysPays,
) -> Result<nip47::LookupInvoiceResponse, nip47::NIP47Error> {
let not_invoice_err = Err(nip47::NIP47Error {
code: nip47::ErrorCode::Other,
message: NOT_INV_ERR.to_owned(),
});
let invstring = if list_pay.bolt11.is_some() {
list_pay.bolt11
} else {
list_pay.bolt12
};
let invoice_decoded = if let Some(invstr) = &invstring {
Some(
rpc.call_typed(&DecodeRequest {
string: invstr.clone(),
})
.await
.map_err(|e| nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: e.to_string(),
})?,
)
} else {
None
};
if invoice_decoded.is_some() && !invoice_decoded.as_ref().unwrap().valid {
return not_invoice_err;
}
let description_hash = if let Some(inv_dec) = &invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => None,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
inv_dec.description_hash.map(|h| h.to_string())
}
_ => return not_invoice_err,
}
} else {
None
};
let amount = if let Some(amt) = list_pay.amount_msat {
amt.msat()
} else if let Some(inv_dec) = &invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => {
inv_dec.invoice_amount_msat.unwrap().msat()
}
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => {
if let Some(amt) = inv_dec.amount_msat {
amt.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => return not_invoice_err,
}
} else {
return not_invoice_err;
};
let description = if let Some(inv_dec) = invoice_decoded {
match inv_dec.item_type {
cln_rpc::model::responses::DecodeType::BOLT12_INVOICE => inv_dec.offer_description,
cln_rpc::model::responses::DecodeType::BOLT11_INVOICE => inv_dec.description,
_ => return not_invoice_err,
}
} else {
list_pay.description
};
let fees_paid = if let Some(amt_sent) = list_pay.amount_sent_msat {
amt_sent.msat() - amount
} else {
0
};
let preimage = list_pay.preimage.map(|p| hex::encode(p.to_vec()));
let state = match list_pay.status {
ListpaysPaysStatus::PENDING => nip47::TransactionState::Pending,
ListpaysPaysStatus::FAILED => nip47::TransactionState::Failed,
ListpaysPaysStatus::COMPLETE => nip47::TransactionState::Settled,
};
Ok(nip47::LookupInvoiceResponse {
transaction_type: Some(nip47::TransactionType::Outgoing),
invoice: invstring,
description,
description_hash,
preimage,
payment_hash: list_pay.payment_hash.to_string(),
amount,
fees_paid,
created_at: Timestamp::from_secs(list_pay.created_at),
expires_at: None,
settled_at: list_pay.completed_at.map(Timestamp::from_secs),
metadata: None,
state: Some(state),
})
}
fn trim_to_size(
mut transactions: Vec<nip47::LookupInvoiceResponse>,
max_size: usize,

View file

@ -8,6 +8,8 @@ use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use tokio::sync::oneshot;
pub const NOT_INV_ERR: &str = "Not an invoice or invalid invoice";
#[derive(Clone)]
pub struct PluginState {
pub config: Arc<Mutex<Config>>,