diff --git a/README.md b/README.md
index 06a59f4e..e0d8096c 100644
--- a/README.md
+++ b/README.md
@@ -232,9 +232,11 @@ The node currently exposes the following APIs:
- `/assetmetadata` (POST)
- `/backup` (POST)
- `/btcbalance` (POST)
+- `/cancelhodlinvoice` (POST)
- `/changepassword` (POST)
- `/checkindexerurl` (POST)
- `/checkproxyendpoint` (POST)
+- `/claimhodlinvoice` (POST)
- `/closechannel` (POST)
- `/connectpeer` (POST)
- `/createutxos` (POST)
diff --git a/openapi.yaml b/openapi.yaml
index cfb5942c..27e9c1e5 100644
--- a/openapi.yaml
+++ b/openapi.yaml
@@ -115,6 +115,24 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/BtcBalanceResponse'
+ /cancelhodlinvoice:
+ post:
+ tags:
+ - Invoices
+ summary: Cancel a HODL invoice
+ description: Cancel a held HTLC for a HODL invoice. Rejects cancellation if a settlement is already in progress.
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/CancelHodlInvoiceRequest'
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EmptyResponse'
/changepassword:
post:
tags:
@@ -169,6 +187,24 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/EmptyResponse'
+ /claimhodlinvoice:
+ post:
+ tags:
+ - Invoices
+ summary: Claim a HODL invoice
+ description: Claim a held HTLC for a HODL invoice
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/ClaimHodlInvoiceRequest'
+ responses:
+ '200':
+ description: Successful operation
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EmptyResponse'
/closechannel:
post:
tags:
@@ -716,6 +752,8 @@ paths:
description: >-
Get a LN invoice to receive a payment. The description and
description_hash fields are mutually exclusive.
+ Provide `payment_hash` to create a HODL invoice.
+ Provide `min_final_cltv_expiry_delta` to request an explicit inbound final CLTV policy.
requestBody:
content:
application/json:
@@ -1543,6 +1581,14 @@ components:
$ref: '#/components/schemas/BtcBalance'
colored:
$ref: '#/components/schemas/BtcBalance'
+ CancelHodlInvoiceRequest:
+ type: object
+ required:
+ - payment_hash
+ properties:
+ payment_hash:
+ type: string
+ example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
ChangePasswordRequest:
type: object
required:
@@ -1665,6 +1711,18 @@ components:
proxy_endpoint:
type: string
example: rpc://127.0.0.1:3000/json-rpc
+ ClaimHodlInvoiceRequest:
+ type: object
+ required:
+ - payment_hash
+ - payment_preimage
+ properties:
+ payment_hash:
+ type: string
+ example: b4cb2da889477082a2e47f37a07e646e60ef6f97ffa7a4d88c823efd673da94b
+ payment_preimage:
+ type: string
+ example: eade701c7b23b8799465f4284ad84710fc16a776fbc6483001291149122695a8
CloseChannelRequest:
type: object
required:
@@ -1731,6 +1789,7 @@ components:
- payment_hash
- payment_secret
- network
+ - min_final_cltv_expiry_delta
properties:
amt_msat:
type:
@@ -1776,6 +1835,9 @@ components:
example: 0343851df9e0e8aff0c10b3498ce723ff4c9b4a855e6c8819adcafbbb3e24ea2af
network:
$ref: '#/components/schemas/BitcoinNetwork'
+ min_final_cltv_expiry_delta:
+ type: integer
+ example: 144
DecodeRGBInvoiceRequest:
type: object
required:
@@ -1986,10 +2048,13 @@ components:
type: object
required:
- payment_hash
+ - payment_type
properties:
payment_hash:
type: string
example: 5ca5d81b482b4015e7b14df7a27fe0a38c226273604ffd3b008b752571811938
+ payment_type:
+ $ref: '#/components/schemas/PaymentType'
GetPaymentResponse:
type: object
required:
@@ -2020,7 +2085,10 @@ components:
type: string
enum:
- Pending
+ - Claimable
+ - Claiming
- Succeeded
+ - Cancelled
- Failed
IndexerProtocol:
type: string
@@ -2083,7 +2151,10 @@ components:
type: string
enum:
- Pending
+ - Claimable
+ - Claiming
- Succeeded
+ - Cancelled
- Failed
- Expired
InvoiceStatusRequest:
@@ -2512,6 +2583,14 @@ components:
- string
- 'null'
example: 5ca5d81b482b4015e7b14df7a27fe0a38c226273604ffd3b008b752571811938
+ payment_hash:
+ type:
+ - string
+ - 'null'
+ example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
+ min_final_cltv_expiry_delta:
+ type: integer
+ example: 144
LNInvoiceResponse:
type: object
required:
@@ -2762,7 +2841,7 @@ components:
type: object
required:
- payment_hash
- - inbound
+ - payment_type
- status
- created_at
- updated_at
@@ -2786,9 +2865,8 @@ components:
payment_hash:
type: string
example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
- inbound:
- type: boolean
- example: true
+ payment_type:
+ $ref: '#/components/schemas/PaymentType'
status:
$ref: '#/components/schemas/HTLCStatus'
created_at:
@@ -2813,6 +2891,12 @@ components:
- string
- 'null'
example: 5ca5d81b482b4015e7b14df7a27fe0a38c226273604ffd3b008b752571811938
+ PaymentType:
+ type: string
+ enum:
+ - Outbound
+ - InboundAutoClaim
+ - InboundHodl
Peer:
type: object
required:
diff --git a/src/error.rs b/src/error.rs
index da56b293..98336527 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -55,6 +55,9 @@ pub enum APIError {
#[error("Cannot call other APIs while node is changing state")]
ChangingState,
+ #[error("HTLC claim deadline exceeded")]
+ ClaimDeadlineExceeded,
+
#[error("Consignment file is empty")]
ConsignmentFileEmpty,
@@ -197,6 +200,9 @@ pub enum APIError {
#[error("Invalid payment hash: {0}")]
InvalidPaymentHash(String),
+ #[error("Invalid payment preimage")]
+ InvalidPaymentPreimage,
+
#[error("Invalid payment secret")]
InvalidPaymentSecret,
@@ -248,6 +254,21 @@ pub enum APIError {
#[error("Invalid transport endpoints: {0}")]
InvalidTransportEndpoints(String),
+ #[error("Invoice is already claimed")]
+ InvoiceAlreadyClaimed,
+
+ #[error("Invoice cannot be cancelled")]
+ InvoiceNotCancellable,
+
+ #[error("No claimable HTLC found for this invoice")]
+ InvoiceNotClaimable,
+
+ #[error("Invoice is not marked as HODL")]
+ InvoiceNotHodl,
+
+ #[error("Invoice settlement is in progress")]
+ InvoiceSettlingInProgress,
+
#[error("IO error: {0}")]
IO(#[from] std::io::Error),
@@ -291,6 +312,9 @@ pub enum APIError {
#[error("Output below the dust limit")]
OutputBelowDustLimit,
+ #[error("Payment hash already used")]
+ PaymentHashAlreadyUsed,
+
#[error("Payment not found: {0}")]
PaymentNotFound(String),
@@ -473,6 +497,7 @@ impl APIError {
APIError::AnchorsRequired
| APIError::CannotProvideOutOfBandAck(_)
| APIError::CannotProvideOutOfBandConsignment(_)
+ | APIError::ClaimDeadlineExceeded
| APIError::ConsignmentFileEmpty
| APIError::ConsignmentFileNotProvided
| APIError::ConsignmentNotFound
@@ -503,6 +528,7 @@ impl APIError {
| APIError::InvalidOnionData(_)
| APIError::InvalidPassword(_)
| APIError::InvalidPaymentHash(_)
+ | APIError::InvalidPaymentPreimage
| APIError::InvalidPaymentSecret
| APIError::InvalidPeerInfo(_)
| APIError::InvalidPrecision(_)
@@ -522,6 +548,7 @@ impl APIError {
| APIError::MediaFileNotProvided
| APIError::MissingSwapPaymentPreimage
| APIError::OutputBelowDustLimit
+ | APIError::PaymentHashAlreadyUsed
| APIError::UnsupportedBackupVersion { .. } => StatusCode::BAD_REQUEST,
APIError::WrongPassword => StatusCode::UNAUTHORIZED,
APIError::AllocationsAlreadyAvailable
@@ -542,6 +569,9 @@ impl APIError {
| APIError::InsufficientFunds(_)
| APIError::InvalidIndexer(_)
| APIError::InvalidProxyProtocol(_)
+ | APIError::InvoiceNotCancellable
+ | APIError::InvoiceNotHodl
+ | APIError::InvoiceSettlingInProgress
| APIError::LockedNode
| APIError::MaxFeeExceeded(_)
| APIError::MinFeeNotMet(_)
@@ -564,6 +594,8 @@ impl APIError {
APIError::FailedBitcoindConnection(_) | APIError::NetworkMismatch(_, _) => {
StatusCode::FORBIDDEN
}
+ APIError::InvoiceAlreadyClaimed => StatusCode::CONFLICT,
+ APIError::InvoiceNotClaimable => StatusCode::NOT_FOUND,
APIError::Network(_) | APIError::NoValidTransportEndpoint => {
StatusCode::SERVICE_UNAVAILABLE
}
diff --git a/src/ldk.rs b/src/ldk.rs
index 4508a932..ff782071 100644
--- a/src/ldk.rs
+++ b/src/ldk.rs
@@ -13,8 +13,9 @@ use lightning::chain::Confirm;
use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus};
use lightning::chain::{BestBlock, Filter};
use lightning::events::bump_transaction::{BumpTransactionEventHandler, Wallet};
-use lightning::events::{Event, PaymentFailureReason, PaymentPurpose, ReplayEvent};
-use lightning::impl_writeable_tlv_based;
+use lightning::events::{
+ Event, HTLCHandlingFailureType, PaymentFailureReason, PaymentPurpose, ReplayEvent,
+};
use lightning::ln::channelmanager::{self, PaymentId, RecentPaymentDetails};
use lightning::ln::channelmanager::{
ChainParameters, ChannelManagerReadArgs, SimpleArcChannelManager,
@@ -28,9 +29,9 @@ use lightning::onion_message::messenger::{
DefaultMessageRouter, OnionMessenger as LdkOnionMessenger,
};
use lightning::rgb_utils::{
- get_rgb_channel_info_pending, is_channel_rgb, parse_rgb_payment_info, read_rgb_transfer_info,
- update_rgb_channel_amount, write_rgb_channel_info, INDEXER_URL_FNAME, STATIC_BLINDING,
- WALLET_MASTER_FINGERPRINT_FNAME,
+ get_rgb_channel_info_pending, get_rgb_payment_info_path, is_channel_rgb,
+ parse_rgb_payment_info, read_rgb_transfer_info, update_rgb_channel_amount,
+ write_rgb_channel_info, INDEXER_URL_FNAME, STATIC_BLINDING, WALLET_MASTER_FINGERPRINT_FNAME,
};
use lightning::routing::gossip;
use lightning::routing::gossip::{NodeId, P2PGossipSync};
@@ -51,6 +52,7 @@ use lightning::util::persist::{
};
use lightning::util::ser::{ReadableArgs, Writeable};
use lightning::util::sweep as ldk_sweep;
+use lightning::{impl_writeable_tlv_based, impl_writeable_tlv_based_enum};
use lightning_background_processor::{process_events_async, GossipSync, NO_LIQUIDITY_MANAGER};
#[cfg(feature = "block-sync")]
use lightning_block_sync::{init, poll, SpvClient, UnboundedCache};
@@ -165,6 +167,18 @@ pub(crate) static HELD_PAYMENT_CLAIMABLE_COUNT: AtomicUsize = AtomicUsize::new(0
#[cfg(test)]
pub(crate) static FORCE_PUSH_ASSET_AMOUNT_ON_NODE: Mutex