Skip to content

Commit 07ea124

Browse files
committed
add support for HODL invoices
1 parent e291bdf commit 07ea124

11 files changed

Lines changed: 2382 additions & 54 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ The node currently exposes the following APIs:
203203
- `/assetmetadata` (POST)
204204
- `/backup` (POST)
205205
- `/btcbalance` (POST)
206+
- `/cancelhodlinvoice` (POST)
206207
- `/changepassword` (POST)
207208
- `/checkindexerurl` (POST)
208209
- `/checkproxyendpoint` (POST)
@@ -217,7 +218,9 @@ The node currently exposes the following APIs:
217218
- `/getassetmedia` (POST)
218219
- `/getchannelid` (POST)
219220
- `/getpayment` (POST)
221+
- `/getpaymentpreimage` (POST)
220222
- `/getswap` (POST)
223+
- `/hodlinvoice` (POST)
221224
- `/init` (POST)
222225
- `/invoicestatus` (POST)
223226
- `/issueassetcfa` (POST)
@@ -248,6 +251,7 @@ The node currently exposes the following APIs:
248251
- `/sendonionmessage` (POST)
249252
- `/sendpayment` (POST)
250253
- `/sendrgb` (POST)
254+
- `/settlehodlinvoice` (POST)
251255
- `/shutdown` (POST)
252256
- `/signmessage` (POST)
253257
- `/sync` (POST)

openapi.yaml

Lines changed: 164 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,24 @@ paths:
115115
application/json:
116116
schema:
117117
$ref: '#/components/schemas/BtcBalanceResponse'
118+
/cancelhodlinvoice:
119+
post:
120+
tags:
121+
- Invoices
122+
summary: Cancel a HODL invoice
123+
description: Cancel a held HTLC for a HODL invoice. Rejects cancellation if a settlement is already in progress.
124+
requestBody:
125+
content:
126+
application/json:
127+
schema:
128+
$ref: '#/components/schemas/InvoiceCancelRequest'
129+
responses:
130+
'200':
131+
description: Successful operation
132+
content:
133+
application/json:
134+
schema:
135+
$ref: '#/components/schemas/EmptyResponse'
118136
/changepassword:
119137
post:
120138
tags:
@@ -368,6 +386,24 @@ paths:
368386
application/json:
369387
schema:
370388
$ref: '#/components/schemas/GetPaymentResponse'
389+
/getpaymentpreimage:
390+
post:
391+
tags:
392+
- Payments
393+
summary: Get a payment preimage by its payment hash
394+
description: Get the preimage for an outbound payment when it has been completed successfully
395+
requestBody:
396+
content:
397+
application/json:
398+
schema:
399+
$ref: '#/components/schemas/GetPaymentPreimageRequest'
400+
responses:
401+
'200':
402+
description: Successful operation
403+
content:
404+
application/json:
405+
schema:
406+
$ref: '#/components/schemas/GetPaymentPreimageResponse'
371407
/getswap:
372408
post:
373409
tags:
@@ -386,6 +422,24 @@ paths:
386422
application/json:
387423
schema:
388424
$ref: '#/components/schemas/GetSwapResponse'
425+
/hodlinvoice:
426+
post:
427+
tags:
428+
- Invoices
429+
summary: Create a HODL LN invoice
430+
description: Create a BOLT11 invoice with a caller-provided payment hash; settlement is deferred until settle/cancel. Metadata is persisted first to preserve HODL semantics across restarts.
431+
requestBody:
432+
content:
433+
application/json:
434+
schema:
435+
$ref: '#/components/schemas/InvoiceHodlRequest'
436+
responses:
437+
'200':
438+
description: Successful operation
439+
content:
440+
application/json:
441+
schema:
442+
$ref: '#/components/schemas/InvoiceHodlResponse'
389443
/init:
390444
post:
391445
tags:
@@ -892,6 +946,42 @@ paths:
892946
application/json:
893947
schema:
894948
$ref: '#/components/schemas/SendRgbResponse'
949+
/settlehodlinvoice:
950+
post:
951+
tags:
952+
- Invoices
953+
summary: Settle a HODL invoice
954+
description: Claim a held HTLC for a HODL invoice
955+
requestBody:
956+
content:
957+
application/json:
958+
schema:
959+
$ref: '#/components/schemas/InvoiceSettleRequest'
960+
responses:
961+
'200':
962+
description: Successful operation
963+
content:
964+
application/json:
965+
schema:
966+
$ref: '#/components/schemas/EmptyResponse'
967+
/shutdown:
968+
post:
969+
tags:
970+
- RGB
971+
summary: Send RGB assets
972+
description: Send RGB assets on-chain, supporting batch transfers to multiple recipients and/or multiple assets in a single transaction
973+
requestBody:
974+
content:
975+
application/json:
976+
schema:
977+
$ref: '#/components/schemas/SendRgbRequest'
978+
responses:
979+
'200':
980+
description: Successful operation
981+
content:
982+
application/json:
983+
schema:
984+
$ref: '#/components/schemas/SendRgbResponse'
895985
/shutdown:
896986
post:
897987
tags:
@@ -1330,7 +1420,7 @@ components:
13301420
CheckProxyEndpointRequest:
13311421
type: object
13321422
properties:
1333-
proxy_url:
1423+
proxy_endpoint:
13341424
type: string
13351425
example: rpc://127.0.0.1:3000/json-rpc
13361426
CloseChannelRequest:
@@ -1519,6 +1609,23 @@ components:
15191609
properties:
15201610
payment:
15211611
$ref: '#/components/schemas/Payment'
1612+
GetPaymentPreimageRequest:
1613+
type: object
1614+
required:
1615+
- payment_hash
1616+
properties:
1617+
payment_hash:
1618+
type: string
1619+
example: b4cb2da889477082a2e47f37a07e646e60ef6f97ffa7a4d88c823efd673da94b
1620+
GetPaymentPreimageResponse:
1621+
type: object
1622+
properties:
1623+
status:
1624+
$ref: '#/components/schemas/HTLCStatus'
1625+
preimage:
1626+
type: string
1627+
nullable: true
1628+
example: eade701c7b23b8799465f4284ad84710fc16a776fbc6483001291149122695a8
15221629
GetSwapRequest:
15231630
type: object
15241631
properties:
@@ -1537,7 +1644,9 @@ components:
15371644
type: string
15381645
enum:
15391646
- Pending
1647+
- Claimable
15401648
- Succeeded
1649+
- Cancelled
15411650
- Failed
15421651
IndexerProtocol:
15431652
type: string
@@ -1556,11 +1665,65 @@ components:
15561665
mnemonic:
15571666
type: string
15581667
example: skill lamp please gown put season degree collect decline account monitor insane
1668+
InvoiceCancelRequest:
1669+
type: object
1670+
required:
1671+
- payment_hash
1672+
properties:
1673+
payment_hash:
1674+
type: string
1675+
example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
1676+
InvoiceHodlRequest:
1677+
type: object
1678+
required:
1679+
- payment_hash
1680+
- expiry_sec
1681+
properties:
1682+
amt_msat:
1683+
type: integer
1684+
example: 3000000
1685+
expiry_sec:
1686+
type: integer
1687+
example: 86400
1688+
asset_id:
1689+
type: string
1690+
example: rgb:CJkb4YZw-jRiz2sk-~PARPio-wtVYI1c-XAEYCqO-wTfvRZ8
1691+
asset_amount:
1692+
type: integer
1693+
example: 42
1694+
payment_hash:
1695+
type: string
1696+
example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
1697+
external_ref:
1698+
type: string
1699+
example: swap-123
1700+
InvoiceHodlResponse:
1701+
type: object
1702+
properties:
1703+
invoice:
1704+
type: string
1705+
example: lnbcrt30u1pjv6yzndqud3jxktt5w46x7unfv9kz6mn0v3jsnp4qdpc280eur52luxppv6f3nnj8l6vnd9g2hnv3qv6mjhmhvlzf6327pp5tjjasx6g9dqptea3fhm6yllq5wxzycnnvp8l6wcq3d6j2uvpryuqsp5l8az8x3g8fe05dg7cmgddld3da09nfjvky8xftwsk4cj8p2l7kfq9qyysgqcqpcxqzdylzlwfnkyw3jv344x4rzwgkk53ng0fhxy5rdduk4g5tpvea8xa6rfckkza35va28xjn2tqkhgarcxep5umm4x5k56wfcdvu95eq7qzp20vrl4xz76syapsa3c09j7lg5gerkaj63llj0ark7ph8hfketn6fkqzm8laf66dhsncm23wkwm5l5377we9e8lnlknnkwje5eefkccusqm6rqt8
1706+
payment_secret:
1707+
type: string
1708+
example: 777a7756c620868199ed5fdc35bee4095b5709d543e5c2bf0494396bf27d2ea2
1709+
InvoiceSettleRequest:
1710+
type: object
1711+
required:
1712+
- payment_hash
1713+
- payment_preimage
1714+
properties:
1715+
payment_hash:
1716+
type: string
1717+
example: b4cb2da889477082a2e47f37a07e646e60ef6f97ffa7a4d88c823efd673da94b
1718+
payment_preimage:
1719+
type: string
1720+
example: eade701c7b23b8799465f4284ad84710fc16a776fbc6483001291149122695a8
15591721
InvoiceStatus:
15601722
type: string
15611723
enum:
15621724
- Pending
15631725
- Succeeded
1726+
- Cancelled
15641727
- Failed
15651728
- Expired
15661729
InvoiceStatusRequest:

regtest.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,8 @@ _is_port_bound() {
3636
}
3737

3838
_wait_for_bitcoind() {
39-
# wait for bitcoind to be up
4039
start_time=$(date +%s)
41-
until $COMPOSE logs bitcoind |grep -q 'Bound to'; do
40+
until $BITCOIN_CLI getblockcount >/dev/null 2>&1; do
4241
current_time=$(date +%s)
4342
if [ $((current_time - start_time)) -gt $TIMEOUT ]; then
4443
echo "Timeout waiting for bitcoind to start"
@@ -74,9 +73,10 @@ _start_services() {
7473
_die "port $port is already bound, services can't be started"
7574
fi
7675
done
77-
$COMPOSE up -d
76+
$COMPOSE up -d bitcoind
7877
echo && echo "preparing bitcoind wallet"
7978
_wait_for_bitcoind
79+
$COMPOSE up -d electrs proxy
8080
$BITCOIN_CLI createwallet miner >/dev/null
8181
$BITCOIN_CLI -rpcwallet=miner -generate $INITIAL_BLOCKS >/dev/null
8282
echo "waiting for electrs to have completed startup"

src/disk.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,17 @@ use std::sync::Arc;
1515

1616
use crate::error::APIError;
1717
use crate::ldk::{
18-
ChannelIdsMap, InboundPaymentInfoStorage, NetworkGraph, OutboundPaymentInfoStorage,
19-
OutputSpenderTxes, SwapMap,
18+
ChannelIdsMap, ClaimablePaymentStorage, InboundPaymentInfoStorage, InvoiceMetadataStorage,
19+
NetworkGraph, OutboundPaymentInfoStorage, OutputSpenderTxes, SwapMap,
2020
};
2121
use crate::utils::{parse_peer_info, LOGS_DIR};
2222

2323
pub(crate) const LDK_LOGS_FILE: &str = "logs.txt";
2424

2525
pub(crate) const INBOUND_PAYMENTS_FNAME: &str = "inbound_payments";
2626
pub(crate) const OUTBOUND_PAYMENTS_FNAME: &str = "outbound_payments";
27+
pub(crate) const INVOICE_METADATA_FNAME: &str = "invoice_metadata";
28+
pub(crate) const CLAIMABLE_HTLCS_FNAME: &str = "claimable_htlcs";
2729

2830
pub(crate) const CHANNEL_PEER_DATA: &str = "channel_peer_data";
2931

@@ -178,6 +180,28 @@ pub(crate) fn read_outbound_payment_info(path: &Path) -> OutboundPaymentInfoStor
178180
}
179181
}
180182

183+
pub(crate) fn read_invoice_metadata(path: &Path) -> InvoiceMetadataStorage {
184+
if let Ok(file) = File::open(path) {
185+
if let Ok(info) = InvoiceMetadataStorage::read(&mut BufReader::new(file)) {
186+
return info;
187+
}
188+
}
189+
InvoiceMetadataStorage {
190+
invoices: new_hash_map(),
191+
}
192+
}
193+
194+
pub(crate) fn read_claimable_htlcs(path: &Path) -> ClaimablePaymentStorage {
195+
if let Ok(file) = File::open(path) {
196+
if let Ok(info) = ClaimablePaymentStorage::read(&mut BufReader::new(file)) {
197+
return info;
198+
}
199+
}
200+
ClaimablePaymentStorage {
201+
payments: new_hash_map(),
202+
}
203+
}
204+
181205
pub(crate) fn read_output_spender_txes(path: &Path) -> OutputSpenderTxes {
182206
if let Ok(file) = File::open(path) {
183207
if let Ok(info) = OutputSpenderTxes::read(&mut BufReader::new(file)) {

0 commit comments

Comments
 (0)