Skip to content

Commit 717ca2f

Browse files
committed
add support for HODL invoices
1 parent 5a5aa12 commit 717ca2f

21 files changed

Lines changed: 1361 additions & 112 deletions

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,9 +203,11 @@ 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)
210+
- `/claimhodlinvoice` (POST)
209211
- `/closechannel` (POST)
210212
- `/connectpeer` (POST)
211213
- `/createutxos` (POST)

openapi.yaml

Lines changed: 75 additions & 2 deletions
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:
@@ -169,6 +187,24 @@ paths:
169187
application/json:
170188
schema:
171189
$ref: '#/components/schemas/EmptyResponse'
190+
/claimhodlinvoice:
191+
post:
192+
tags:
193+
- Invoices
194+
summary: Claim a HODL invoice
195+
description: Claim a held HTLC for a HODL invoice
196+
requestBody:
197+
content:
198+
application/json:
199+
schema:
200+
$ref: '#/components/schemas/ClaimHodlInvoiceRequest'
201+
responses:
202+
'200':
203+
description: Successful operation
204+
content:
205+
application/json:
206+
schema:
207+
$ref: '#/components/schemas/ClaimHodlInvoiceResponse'
172208
/closechannel:
173209
post:
174210
tags:
@@ -638,7 +674,7 @@ paths:
638674
tags:
639675
- Invoices
640676
summary: Get a LN invoice
641-
description: Get a LN invoice to receive a payment
677+
description: Get a LN invoice to receive a payment. Provide `payment_hash` to create a HODL invoice.
642678
requestBody:
643679
content:
644680
application/json:
@@ -1247,6 +1283,14 @@ components:
12471283
$ref: '#/components/schemas/BtcBalance'
12481284
colored:
12491285
$ref: '#/components/schemas/BtcBalance'
1286+
CancelHodlInvoiceRequest:
1287+
type: object
1288+
required:
1289+
- payment_hash
1290+
properties:
1291+
payment_hash:
1292+
type: string
1293+
example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
12501294
ChangePasswordRequest:
12511295
type: object
12521296
properties:
@@ -1332,9 +1376,27 @@ components:
13321376
CheckProxyEndpointRequest:
13331377
type: object
13341378
properties:
1335-
proxy_url:
1379+
proxy_endpoint:
13361380
type: string
13371381
example: rpc://127.0.0.1:3000/json-rpc
1382+
ClaimHodlInvoiceRequest:
1383+
type: object
1384+
required:
1385+
- payment_hash
1386+
- payment_preimage
1387+
properties:
1388+
payment_hash:
1389+
type: string
1390+
example: b4cb2da889477082a2e47f37a07e646e60ef6f97ffa7a4d88c823efd673da94b
1391+
payment_preimage:
1392+
type: string
1393+
example: eade701c7b23b8799465f4284ad84710fc16a776fbc6483001291149122695a8
1394+
ClaimHodlInvoiceResponse:
1395+
type: object
1396+
properties:
1397+
changed:
1398+
type: boolean
1399+
example: true
13381400
CloseChannelRequest:
13391401
type: object
13401402
properties:
@@ -1539,7 +1601,9 @@ components:
15391601
type: string
15401602
enum:
15411603
- Pending
1604+
- Claimable
15421605
- Succeeded
1606+
- Cancelled
15431607
- Failed
15441608
IndexerProtocol:
15451609
type: string
@@ -1566,6 +1630,7 @@ components:
15661630
enum:
15671631
- Pending
15681632
- Succeeded
1633+
- Cancelled
15691634
- Failed
15701635
- Expired
15711636
InvoiceStatusRequest:
@@ -1789,6 +1854,10 @@ components:
17891854
asset_amount:
17901855
type: integer
17911856
example: 42
1857+
payment_hash:
1858+
type: string
1859+
description: Optional. When provided, the invoice is created as HODL.
1860+
example: 3febfae1e68b190c15461f4c2a3290f9af1dae63fd7d620d2bd61601869026cd
17921861
LNInvoiceResponse:
17931862
type: object
17941863
properties:
@@ -1982,6 +2051,10 @@ components:
19822051
payee_pubkey:
19832052
type: string
19842053
example: 03b79a4bc1ec365524b4fab9a39eb133753646babb5a1da5c4bc94c53110b7795d
2054+
preimage:
2055+
type: string
2056+
nullable: true
2057+
example: eade701c7b23b8799465f4284ad84710fc16a776fbc6483001291149122695a8
19852058
Peer:
19862059
type: object
19872060
properties:

regtest.sh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ _is_port_bound() {
3838
_wait_for_bitcoind() {
3939
# wait for bitcoind to be up
4040
start_time=$(date +%s)
41-
until $COMPOSE logs bitcoind |grep -q 'Bound to'; do
41+
until $BITCOIN_CLI getblockcount >/dev/null 2>&1; do
4242
current_time=$(date +%s)
4343
if [ $((current_time - start_time)) -gt $TIMEOUT ]; then
4444
echo "Timeout waiting for bitcoind to start"
@@ -74,9 +74,10 @@ _start_services() {
7474
_die "port $port is already bound, services can't be started"
7575
fi
7676
done
77-
$COMPOSE up -d
77+
$COMPOSE up -d bitcoind
7878
echo && echo "preparing bitcoind wallet"
7979
_wait_for_bitcoind
80+
$COMPOSE up -d electrs proxy
8081
$BITCOIN_CLI createwallet miner >/dev/null
8182
$BITCOIN_CLI -rpcwallet=miner -generate $INITIAL_BLOCKS >/dev/null
8283
echo "waiting for electrs to have completed startup"

src/error.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ pub enum APIError {
164164
#[error("Invalid payment hash: {0}")]
165165
InvalidPaymentHash(String),
166166

167+
#[error("Invalid payment preimage")]
168+
InvalidPaymentPreimage,
169+
167170
#[error("Invalid payment secret")]
168171
InvalidPaymentSecret,
169172

@@ -215,6 +218,24 @@ pub enum APIError {
215218
#[error("Invalid transport endpoints: {0}")]
216219
InvalidTransportEndpoints(String),
217220

221+
#[error("HTLC claim deadline exceeded")]
222+
ClaimDeadlineExceeded,
223+
224+
#[error("Invoice is already settled")]
225+
InvoiceAlreadySettled,
226+
227+
#[error("Invoice is expired")]
228+
InvoiceExpired,
229+
230+
#[error("No claimable HTLC found for this invoice")]
231+
InvoiceNotClaimable,
232+
233+
#[error("Invoice is not marked as HODL")]
234+
InvoiceNotHodl,
235+
236+
#[error("Invoice settlement is in progress")]
237+
InvoiceSettlingInProgress,
238+
218239
#[error("IO error: {0}")]
219240
IO(#[from] std::io::Error),
220241

@@ -260,6 +281,9 @@ pub enum APIError {
260281
#[error("Output below the dust limit")]
261282
OutputBelowDustLimit,
262283

284+
#[error("Payment hash already used")]
285+
PaymentHashAlreadyUsed,
286+
263287
#[error("Payment not found: {0}")]
264288
PaymentNotFound(String),
265289

@@ -447,6 +471,8 @@ impl IntoResponse for APIError {
447471
| APIError::InvalidOnionData(_)
448472
| APIError::InvalidPassword(_)
449473
| APIError::InvalidPaymentHash(_)
474+
| APIError::PaymentHashAlreadyUsed
475+
| APIError::InvalidPaymentPreimage
450476
| APIError::InvalidPaymentSecret
451477
| APIError::InvalidPeerInfo(_)
452478
| APIError::InvalidPrecision(_)
@@ -461,10 +487,12 @@ impl IntoResponse for APIError {
461487
| APIError::InvalidTlvType(_)
462488
| APIError::InvalidTransportEndpoint(_)
463489
| APIError::InvalidTransportEndpoints(_)
490+
| APIError::InvoiceExpired
464491
| APIError::MediaFileEmpty
465492
| APIError::MediaFileNotProvided
466493
| APIError::MissingSwapPaymentPreimage
467494
| APIError::OutputBelowDustLimit
495+
| APIError::ClaimDeadlineExceeded
468496
| APIError::UnsupportedBackupVersion { .. } => {
469497
(StatusCode::BAD_REQUEST, self.to_string(), self.name())
470498
}
@@ -489,6 +517,8 @@ impl IntoResponse for APIError {
489517
| APIError::InvalidIndexer(_)
490518
| APIError::InvalidProxyEndpoint
491519
| APIError::InvalidProxyProtocol(_)
520+
| APIError::InvoiceNotHodl
521+
| APIError::InvoiceSettlingInProgress
492522
| APIError::LockedNode
493523
| APIError::MaxFeeExceeded(_)
494524
| APIError::MinFeeNotMet(_)
@@ -510,6 +540,10 @@ impl IntoResponse for APIError {
510540
| APIError::UnsupportedTransportType => {
511541
(StatusCode::FORBIDDEN, self.to_string(), self.name())
512542
}
543+
APIError::InvoiceAlreadySettled => {
544+
(StatusCode::CONFLICT, self.to_string(), self.name())
545+
}
546+
APIError::InvoiceNotClaimable => (StatusCode::NOT_FOUND, self.to_string(), self.name()),
513547
APIError::Network(_) | APIError::NoValidTransportEndpoint => (
514548
StatusCode::SERVICE_UNAVAILABLE,
515549
self.to_string(),

0 commit comments

Comments
 (0)