Skip to content

Commit 0a3d077

Browse files
grunchclaude
andcommitted
fix(add-bond-invoice): pin newest info event and require a bolt11 reply
- fetch_bond_claim_window_days: select the kind-38385 info event by max created_at instead of relying on limit(1) + .first(), so a lagging or multi-relay setup can't surface a stale bond_payout_claim_window_days and render the wrong, user-facing forfeit deadline. - execute_add_bond_invoice: the protocol's bond-payout reply is a bolt11, so stop forwarding Lightning Addresses verbatim (they'd bounce back as cant-do/invalid-invoice from Mostro). Validate the bolt11 locally and fail fast with a clear error; drop the now-unused LightningAddress/FromStr imports. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 1a7c3e7 commit 0a3d077

2 files changed

Lines changed: 14 additions & 17 deletions

File tree

src/cli/add_bond_invoice.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ use crate::parser::common::{
44
use crate::util::{print_dm_events, send_dm, wait_for_dm, WaitForDmTimeout};
55
use crate::{cli::Context, db::Order, lightning::is_valid_invoice};
66
use anyhow::Result;
7-
use lnurl::lightning_address::LightningAddress;
87
use mostro_core::prelude::*;
98
use nostr_sdk::prelude::*;
10-
use std::str::FromStr;
119
use uuid::Uuid;
1210

1311
/// Reply to a Mostro `add-bond-invoice` request: the non-slashed counterparty
@@ -50,18 +48,14 @@ pub async fn execute_add_bond_invoice(order_id: &Uuid, invoice: &str, ctx: &Cont
5048
));
5149
println!("{table}");
5250
println!("💡 Sending bond payout invoice to Mostro...\n");
53-
// Parse invoice (Lightning address or BOLT11) and build payload
54-
let ln_addr = LightningAddress::from_str(invoice);
55-
let payload = if ln_addr.is_ok() {
56-
Payload::PaymentRequest(None, invoice.to_string(), None)
57-
} else {
58-
match is_valid_invoice(invoice) {
59-
Ok(i) => Payload::PaymentRequest(None, i.to_string(), None),
60-
Err(e) => {
61-
return Err(anyhow::anyhow!("Invalid invoice: {}", e));
62-
}
63-
}
64-
};
51+
// The bond payout reply must be a bolt11 sized at the counterparty share.
52+
// Lightning Addresses are not accepted here (the protocol's "Bond payout
53+
// invoice" reply is a bolt11): validate locally so a bad input fails fast
54+
// instead of bouncing back as a `cant-do` / `invalid-invoice` from Mostro.
55+
let invoice = is_valid_invoice(invoice)
56+
.map_err(|e| anyhow::anyhow!("Invalid invoice: {}", e))?
57+
.to_string();
58+
let payload = Payload::PaymentRequest(None, invoice, None);
6559

6660
// Create request id
6761
let request_id = Uuid::new_v4().as_u128() as u64;

src/util/events.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,19 @@ pub fn create_filter(
9494
pub async fn fetch_bond_claim_window_days(ctx: &crate::cli::Context) -> Option<i64> {
9595
let filter = Filter::new()
9696
.author(ctx.mostro_pubkey)
97-
.kind(nostr_sdk::Kind::Custom(NOSTR_INFO_EVENT_KIND))
98-
.limit(1);
97+
.kind(nostr_sdk::Kind::Custom(NOSTR_INFO_EVENT_KIND));
9998

10099
let events = ctx
101100
.client
102101
.fetch_events(filter, FETCH_EVENTS_TIMEOUT)
103102
.await
104103
.ok()?;
105104

106-
let event = events.first()?;
105+
// kind-38385 is replaceable, but pick the newest revision by `created_at`
106+
// explicitly: a lagging relay (or several relays at once) can still surface
107+
// an older copy, and a stale claim window would render the wrong, very
108+
// user-facing forfeit deadline.
109+
let event = events.iter().max_by_key(|e| e.created_at)?;
107110
for tag in event.tags.iter() {
108111
let slice = tag.as_slice();
109112
if slice.first().map(String::as_str) == Some("bond_payout_claim_window_days") {

0 commit comments

Comments
 (0)