Skip to content

Support HODL Invoices - #2

Merged
txalkan merged 34 commits into
mainfrom
feat_hodl_invoice
Jan 19, 2026
Merged

Support HODL Invoices#2
txalkan merged 34 commits into
mainfrom
feat_hodl_invoice

Conversation

@txalkan

@txalkan txalkan commented Jan 13, 2026

Copy link
Copy Markdown
Member

This PR adds support for HODL invoices to the RGB Lightning Node.

The main functional addition is the ability to create and manage HODL invoices, requiring updates across the API, core logic, error handling, persistence layer, and a new test suite. Incoming HTLCs are held and only settled or cancelled explicitly.

  • API: Added /invoice/hodl, /invoice/settle, and /invoice/cancel.

Minor adjustments to workflows and documentation ensure the new feature is well integrated into the development process and clearly documented. More diagrams are WIP to support advanced flows such as submarine swaps.

  • src/routes.rs added new routes to create HODL invoices, settle or cancel them. It includes request validation and JSON responses that comply with the updated OpenAPI spec. Wired the new routes into src/main.rs.
  • src/ldk.rs upgraded how relevant events are processed. This includes new handlers for holding and settling payments, and updates to invoice retrieval and status checks.
  • src/test/hodl_invoice.rs is a comprehensive test suite verifying HODL invoice creation, payment flows, and settlement/cancellation scenarios.

txalkan and others added 26 commits December 17, 2025 12:53
Reordered settle/cancel to avoid deleting claimables before we know the LDK
action succeeded. In invoice_settle, call claim_funds first and return an error
if LDK rejects it; only then best-effort remove the claimable entry. In
invoice_cancel, fetch the claimable without removing it, attempt
fail_htlc_backwards, return an error if that fails, and only then clean up the
claimable.

This prevents losing the ability to retry/cancel when LDK rejects the call.
Add mark_claimable_settling() to atomically mark claimable HTLCs as "settling" before calling claim_funds(), preventing the background expiry task from racing to fail_htlc_backwards on the same payment hash.
settle_twice_succeeds: if the invoice is already settled, settling will succeed;
cancel_then_settle_fails: cancel first, settle must fail;
settle_then_cancel_fails: settle first, cancel must fail;
expire_hodl_invoice: short-expiry path; settle/cancel fail after expiry;
expire_hodl_invoice_by_blocks: block-driven expiry; settle/cancel fail after;
reject_wrong_preimage_settle: good preimage still suceeds.
Make invoice_settle() idempotent by first checking whether the payment has already succeeded before marking the claimable as settling. This enables safe retries after the claimable entry has been cleaned up by the PaymentClaimed event.
Apply formatting and modify gh action to apply formatting in all push/pr
Trying to settle with wrong hash is caught even in the fast path.
Test settle_twice_succeeds now waits for the second settle to be observed as
Succeeded. New settle_twice_wrong_preimage_fails: second settle with a wrong
preimage returns InvalidPaymentPreimage and invoice remains Succeeded. New
settle_after_expiry_idempotent_succeeds: settle once, wait past invoice expiry,
settle again; expects idempotent success.
- make hodl persistence crash‑safe
- prevent invoice cancellation during settlement
…ter-settle return idempotent 409

- Gate /invoice/settle on inbound status (Failed/Cancelled) and clarify docstring about the no-op claim_funds case when an inbound failure isn't observed.
- Add an idempotent /invoice/cancel path that returns InvoiceAlreadySettled (409) when canceling a settled HODL invoice.
@txalkan txalkan self-assigned this Jan 13, 2026
Comment thread src/routes.rs Outdated
Comment on lines +2688 to +2696
/// Settle a HODL invoice that currently has a held HTLC. Requires the invoice
/// `payment_hash` and the matching 32-byte `payment_preimage`. Fails if the
/// invoice is not HODL, there is no claimable HTLC (already cancelled, expired,
/// or failed), or the preimage doesn't match. If the invoice is already settled,
/// this call succeeds (idempotent).
///
/// Note: if the sender fails the HTLC and we
/// don't receive an inbound failure signal, the claimable entry may still exist;
/// in that case `claim_funds` is a no-op and this endpoint still returns 200.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should add the following note to invoice_settle API:

/invoice/settle only hands the preimage to LDK and does not guarantee
settlement of the invoice even if the call succeeds. The canonical source
for whether the payment actually settled is /invoice/status. Call POST
/invoice/status with the original BOLT11 invoice, and poll until it reports
Succeeded (or Failed/Cancelled/Expired).

A HODL invoice can be settled only if its HTLCStatus is Claimable.
@txalkan txalkan mentioned this pull request Jan 15, 2026
@txalkan txalkan moved this from Todo to Review in alpha-protocol Jan 16, 2026
@txalkan txalkan added the enhancement New feature or request label Jan 16, 2026
@txalkan
txalkan merged commit b5de520 into main Jan 19, 2026
12 checks passed
@github-project-automation github-project-automation Bot moved this from Review to Done in alpha-protocol Jan 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

3 participants