From 0cbfab4779545afcc621d019ec93b30e5aec99df Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 8 Oct 2026 23:16:37 -0700 Subject: bolt12: put the requested expiry and single-use on the offer itself The expiry was only stored on our invoice record, so CLN kept issuing invoices for the offer after it had expired. Pass it as absolute_expiry, and accept single_use to cap an offer at one payment. Co-Authored-By: Claude Opus 5.5 --- lib/invoices.ts | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'lib') diff --git a/lib/invoices.ts b/lib/invoices.ts index c4e6a131..0028dc91 100644 --- a/lib/invoices.ts +++ b/lib/invoices.ts @@ -44,6 +44,7 @@ export const generate = async ({ invoice, user }) => { memo, memoPrompt, prompt, + single_use, type = PaymentType.lightning, usePreimage, webhook, @@ -195,10 +196,18 @@ export const generate = async ({ invoice, user }) => { if (bolt12) fail("Cannot provide your own bolt12 invoice"); let r; { + // The offer itself has to carry the expiry: CLN keeps answering + // invoice requests for an offer until its absolute_expiry, so an + // expiry only stored on our invoice record was never enforced. + expiry = Number.parseInt(expiry) || undefined; r = await ln.offer({ amount: amount ? `${amount + tip}sat` : "any", label: `${id} ${user.username} ${new Date()}`, description: memo || id, + absolute_expiry: expiry + ? Math.floor(Date.now() / 1000) + expiry + : undefined, + single_use: !!single_use || undefined, }); // CLN returns the same offer_id for identical offer contents, so a -- cgit v1.2.3