some small payer_note and amount fixes

This commit is contained in:
daywalker90 2025-11-13 01:00:39 +01:00
parent 4e6b1b8341
commit 26fbbb881e
No known key found for this signature in database
4 changed files with 110 additions and 77 deletions

10
Cargo.lock generated
View file

@ -800,7 +800,7 @@ checksum = "f0efe882e02d206d8d279c20eb40e03baf7cb5136a1476dc084a324fbc3ec42d"
[[package]]
name = "nostr"
version = "0.44.0"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#829d51d2594ef14dc1499b8961514b671c27bf05"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#f5acfd7b4651608a03712365dfb150855cef663c"
dependencies = [
"aes",
"base64",
@ -824,7 +824,7 @@ dependencies = [
[[package]]
name = "nostr-database"
version = "0.44.0"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#829d51d2594ef14dc1499b8961514b671c27bf05"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#f5acfd7b4651608a03712365dfb150855cef663c"
dependencies = [
"lru",
"nostr",
@ -834,7 +834,7 @@ dependencies = [
[[package]]
name = "nostr-gossip"
version = "0.44.0"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#829d51d2594ef14dc1499b8961514b671c27bf05"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#f5acfd7b4651608a03712365dfb150855cef663c"
dependencies = [
"nostr",
]
@ -842,7 +842,7 @@ dependencies = [
[[package]]
name = "nostr-relay-pool"
version = "0.44.0"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#829d51d2594ef14dc1499b8961514b671c27bf05"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#f5acfd7b4651608a03712365dfb150855cef663c"
dependencies = [
"async-utility",
"async-wsocket",
@ -859,7 +859,7 @@ dependencies = [
[[package]]
name = "nostr-sdk"
version = "0.44.0"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#829d51d2594ef14dc1499b8961514b671c27bf05"
source = "git+https://github.com/daywalker90/nostr.git?branch=bolt12#f5acfd7b4651608a03712365dfb150855cef663c"
dependencies = [
"async-utility",
"nostr",

View file

@ -167,6 +167,9 @@ fn make_payment_received_from_listinvoices(
settled_at,
metadata: None,
state: Some(state),
offer_issuer: invoice_decoded.offer_issuer,
payer_note: invoice_decoded.invreq_payer_note,
offer_id: invoice_decoded.offer_id,
}),
};
@ -234,9 +237,6 @@ async fn make_payment_sent_from_listpays(
&String::new()
};
let description;
let description_hash;
let amount;
let created_at = Timestamp::from_secs(pay.created_at);
let preimage = hex::encode(
pay.preimage
@ -245,60 +245,47 @@ async fn make_payment_sent_from_listpays(
);
let settled_at = Timestamp::from_secs(pay.completed_at.unwrap());
if invstring.is_empty() {
description = pay.description.clone();
description_hash = None;
amount = if let Some(amt) = pay.amount_msat {
amt.msat()
} else {
// Amount missing but required
0
}
} else {
let invoice_decoded = rpc
.call_typed(&DecodeRequest {
string: invstring.clone(),
})
.await?;
let invoice_decoded = rpc
.call_typed(&DecodeRequest {
string: invstring.clone(),
})
.await?;
let not_invoice_err = Err(anyhow!(NOT_INV_ERR.to_owned()));
let not_invoice_err = Err(anyhow!(NOT_INV_ERR.to_owned()));
if !invoice_decoded.valid {
return not_invoice_err;
}
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,
};
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,
};
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) = pay.amount_msat {
a.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => return not_invoice_err,
};
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) = pay.amount_msat {
a.msat()
} else {
// amount: `any` but have to put a value...
0
}
}
_ => return not_invoice_err,
};
let fees_paid = if let Some(amt_sent) = pay.amount_sent_msat {
amt_sent.msat() - amount
} else {
@ -327,6 +314,9 @@ async fn make_payment_sent_from_listpays(
settled_at,
metadata: None,
state: Some(state),
offer_issuer: invoice_decoded.offer_issuer,
payer_note: invoice_decoded.invreq_payer_note,
offer_id: invoice_decoded.offer_id,
}),
};

View file

@ -4,7 +4,7 @@ use cln_plugin::Plugin;
use cln_rpc::{
model::{
requests::{DecodeRequest, FetchinvoiceRequest, OfferRequest, PayRequest, XpayRequest},
responses::DecodeResponse,
responses::{DecodeResponse, FetchinvoiceResponse},
},
primitives::{Amount, Secret},
ClnRpc,
@ -196,11 +196,13 @@ async fn pay_offer(
) -> Result<(nip47::PayOfferResponse, Option<String>), (nip47::NIP47Error, Option<String>)> {
let mut rpc = plugin.state().rpc_lock.lock().await;
let decoded_invoice = fetch_invoice(&mut rpc, &params).await?;
let fetch_invoice_response = fetch_invoice(&mut rpc, &params).await?;
let id = get_payment_id(&params, &decoded_invoice)?;
let decoded_bolt12 = decode_bolt12_invoice(&mut rpc, &params, &fetch_invoice_response).await?;
let invoice_amt_msat = get_invoice_amount_msat(&decoded_invoice, &id)?;
let id = get_payment_id(&params, &decoded_bolt12)?;
let invoice_amt_msat = get_invoice_amount_msat(&decoded_bolt12, &params, &id)?;
let nwc_store =
load_nwc_and_check_budget(&mut rpc, label, &params, invoice_amt_msat, &id).await?;
@ -209,22 +211,36 @@ async fn pay_offer(
let use_xpay = check_cln_version(&my_version, &id)?;
if use_xpay {
pay_with_xpay_full(&mut rpc, params, label, nwc_store, &id).await
pay_with_xpay_full(
&mut rpc,
fetch_invoice_response.invoice,
label,
nwc_store,
&id,
)
.await
} else {
pay_with_legacy_full(&mut rpc, params, label, nwc_store, &id).await
pay_with_legacy_full(
&mut rpc,
fetch_invoice_response.invoice,
label,
nwc_store,
&id,
)
.await
}
}
async fn fetch_invoice(
rpc: &mut ClnRpc,
params: &nip47::PayOfferRequest,
) -> Result<DecodeResponse, (nip47::NIP47Error, Option<String>)> {
) -> Result<FetchinvoiceResponse, (nip47::NIP47Error, Option<String>)> {
let bolt12_invoice = rpc
.call_typed(&FetchinvoiceRequest {
amount_msat: params.amount.map(Amount::from_msat),
bip353: None,
payer_metadata: None,
payer_note: None,
payer_note: params.payer_note.clone(),
quantity: None,
recurrence_counter: None,
recurrence_label: None,
@ -242,9 +258,17 @@ async fn fetch_invoice(
params.id.clone(),
)
})?;
Ok(bolt12_invoice)
}
async fn decode_bolt12_invoice(
rpc: &mut ClnRpc,
params: &nip47::PayOfferRequest,
fetch_invoice_resp: &FetchinvoiceResponse,
) -> Result<DecodeResponse, (nip47::NIP47Error, Option<String>)> {
let invoice_decoded = rpc
.call_typed(&DecodeRequest {
string: bolt12_invoice.invoice.clone(),
string: fetch_invoice_resp.invoice.clone(),
})
.await
.map_err(|e| {
@ -256,7 +280,6 @@ async fn fetch_invoice(
params.id.clone(),
)
})?;
Ok(invoice_decoded)
}
@ -286,10 +309,11 @@ fn get_payment_id(
}
fn get_invoice_amount_msat(
decoded_invoice: &DecodeResponse,
invoice_decoded: &DecodeResponse,
params: &nip47::PayOfferRequest,
id: &str,
) -> Result<u64, (nip47::NIP47Error, Option<String>)> {
decoded_invoice
let amt = invoice_decoded
.invoice_amount_msat
.as_ref()
.ok_or_else(|| {
@ -301,7 +325,20 @@ fn get_invoice_amount_msat(
Some(id.to_owned()),
)
})
.map(Amount::msat)
.map(Amount::msat)?;
if let Some(a) = params.amount {
if amt != a {
return Err((
nip47::NIP47Error {
code: nip47::ErrorCode::Internal,
message: "amount in decoded bolt12 invoice does not match amount in request"
.to_owned(),
},
Some(id.to_owned()),
));
}
}
Ok(amt)
}
async fn load_nwc_and_check_budget(
@ -450,20 +487,20 @@ fn map_cln_error_to_nip47(
async fn pay_with_xpay_full(
rpc: &mut ClnRpc,
params: nip47::PayOfferRequest,
bolt12_invoice: String,
label: &str,
mut nwc_store: NwcStore,
id: &str,
) -> Result<(nip47::PayOfferResponse, Option<String>), (nip47::NIP47Error, Option<String>)> {
let payment_result = rpc
.call_typed(&XpayRequest {
amount_msat: params.amount.map(Amount::from_msat),
amount_msat: None,
maxdelay: None,
maxfee: None,
partial_msat: None,
retry_for: None,
layers: None,
invstring: params.offer,
invstring: bolt12_invoice,
})
.await
.map_err(|e| map_cln_error_to_nip47(&e, id, true))?;
@ -486,14 +523,14 @@ async fn pay_with_xpay_full(
async fn pay_with_legacy_full(
rpc: &mut ClnRpc,
params: nip47::PayOfferRequest,
bolt12_invoice: String,
label: &str,
mut nwc_store: NwcStore,
id: &str,
) -> Result<(nip47::PayOfferResponse, Option<String>), (nip47::NIP47Error, Option<String>)> {
let payment_result = rpc
.call_typed(&PayRequest {
amount_msat: params.amount.map(Amount::from_msat),
amount_msat: None,
description: None,
exemptfee: None,
label: None,
@ -505,7 +542,7 @@ async fn pay_with_legacy_full(
retry_for: None,
riskfactor: None,
exclude: None,
bolt11: params.offer,
bolt11: bolt12_invoice,
})
.await
.map_err(|e| map_cln_error_to_nip47(&e, id, false))?;

View file

@ -1270,6 +1270,8 @@ async def test_pay_offer(node_factory, get_plugin, nostr_relay): # noqa: F811
"method": "pay_offer",
"params": {
"offer": offer1["bolt12"],
"amount": 3000,
"payer_note": "for pizza",
},
}
json_content = json.dumps(content)
@ -1279,6 +1281,10 @@ async def test_pay_offer(node_factory, get_plugin, nostr_relay): # noqa: F811
assert len(success_events) == 1
pay = l1.rpc.call("listpays", {})["pays"][0]
decoded_pay_inv = l2.rpc.call(
"listinvoices", {"payment_hash": pay["payment_hash"]}
)["invoices"][0]
assert decoded_pay_inv["invreq_payer_note"] == "for pizza"
assert success_events[0]["result"]["preimage"] == pay["preimage"]
offer2 = l2.rpc.call(