thunder-bridge 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +219 -781
- package/dist/index.cjs +759 -49
- package/dist/index.d.cts +424 -8
- package/dist/index.d.ts +424 -8
- package/dist/index.js +740 -49
- package/openapi.yaml +307 -0
- package/package.json +5 -4
package/openapi.yaml
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
|
|
3
|
+
info:
|
|
4
|
+
title: thunder-bridge, the endpoint your own service serves
|
|
5
|
+
version: 0.8.1
|
|
6
|
+
license:
|
|
7
|
+
name: MIT
|
|
8
|
+
identifier: MIT
|
|
9
|
+
summary: The LNURL-pay endpoint that `lnurlPayEndpoint` turns your service into.
|
|
10
|
+
description: |
|
|
11
|
+
The SDK is a client of the gateway, and its own API is TypeScript rather than HTTP. One
|
|
12
|
+
part of it is not: `lnurlPayEndpoint` returns a Fetch handler, and mounting that handler
|
|
13
|
+
makes your service speak LNURL-pay to every Lightning wallet in the world. This document
|
|
14
|
+
is that surface, so a wallet author can read what your service answers without reading
|
|
15
|
+
the SDK, and so you can see what you exposed by mounting it.
|
|
16
|
+
|
|
17
|
+
Both halves of LUD-06 live on one path. A bare request is the payRequest and quotes your
|
|
18
|
+
address list, and a request carrying `to` is the callback and mints. Which address won is
|
|
19
|
+
decided at payRequest and pinned into the callback URL, because LUD-06 binds the invoice
|
|
20
|
+
to the metadata already served, and an invoice from a different address would be refused
|
|
21
|
+
by the payer's own wallet.
|
|
22
|
+
|
|
23
|
+
Nothing is stored between the two calls. The callback URL carries the decision and an
|
|
24
|
+
HMAC over it, so the handler holds no state and runs on Deno Deploy, Workers, Hono, Next
|
|
25
|
+
and Node alike.
|
|
26
|
+
|
|
27
|
+
`bankVerifyEndpoint` is the other handler, and it puts a second rail behind the same
|
|
28
|
+
contract: a bank transfer, proved to the gateway by the LUD-21 shape a Lightning wallet
|
|
29
|
+
would have answered with. Both paths below are therefore yours to mount and yours to
|
|
30
|
+
serve, and neither of them is the gateway.
|
|
31
|
+
|
|
32
|
+
The gateway's own API is a separate document, `openapi.yaml` in the repository root, and
|
|
33
|
+
the webhook your server receives is described there under `webhooks`.
|
|
34
|
+
|
|
35
|
+
servers:
|
|
36
|
+
- url: https://your-service.example
|
|
37
|
+
description: Wherever you mounted the handler.
|
|
38
|
+
|
|
39
|
+
security: []
|
|
40
|
+
|
|
41
|
+
tags:
|
|
42
|
+
- name: lnurl-pay
|
|
43
|
+
description: The two halves of LUD-06, on the one path a lightning address resolves to.
|
|
44
|
+
- name: bank-transfer
|
|
45
|
+
description: The settlement proof for money that arrived over a bank rail instead of Lightning.
|
|
46
|
+
|
|
47
|
+
paths:
|
|
48
|
+
/.well-known/lnurlp/{name}:
|
|
49
|
+
parameters:
|
|
50
|
+
- name: name
|
|
51
|
+
in: path
|
|
52
|
+
required: true
|
|
53
|
+
description: |
|
|
54
|
+
The local part of the lightning address. LUD-16 says `name@your-service.example`
|
|
55
|
+
resolves to this path, which is why the handler is usually mounted here. It works
|
|
56
|
+
on any path you like, and then only a raw LNURL reaches it.
|
|
57
|
+
schema:
|
|
58
|
+
type: string
|
|
59
|
+
examples:
|
|
60
|
+
tips:
|
|
61
|
+
value: tips
|
|
62
|
+
get:
|
|
63
|
+
tags: [lnurl-pay]
|
|
64
|
+
operationId: payRequestOrCallback
|
|
65
|
+
summary: Quote the price, or mint the invoice for a quote already given
|
|
66
|
+
description: |
|
|
67
|
+
Without `to` this answers the payRequest. It calls your `amountMsat()` once, quotes
|
|
68
|
+
your address list through the gateway, and returns the first address that answers and
|
|
69
|
+
accepts the amount, along with that wallet's own LUD-06 metadata string byte for byte.
|
|
70
|
+
`minSendable` and `maxSendable` are both the price, so the payer's wallet offers no
|
|
71
|
+
amount field and the payment is exactly what you asked for.
|
|
72
|
+
|
|
73
|
+
With `to` this is the callback. The signature is checked first, and a request that was
|
|
74
|
+
not signed by this service is refused before anything is minted, which is what stops a
|
|
75
|
+
stranger making your endpoint mint invoices on wallets of their choosing. The invoice
|
|
76
|
+
then comes from the recipient's own wallet, never from this service and never from the
|
|
77
|
+
gateway, and `verify` on the answer points at that recipient's LUD-21 endpoint.
|
|
78
|
+
|
|
79
|
+
Every refusal is HTTP 200 with `status: "ERROR"`, as LUD-06 requires. A wallet reads
|
|
80
|
+
the body, not the status line.
|
|
81
|
+
parameters:
|
|
82
|
+
- name: to
|
|
83
|
+
in: query
|
|
84
|
+
required: false
|
|
85
|
+
description: |
|
|
86
|
+
The address that won the payRequest, put here by this service when it built the
|
|
87
|
+
callback URL. Its presence is what makes a request the callback.
|
|
88
|
+
schema:
|
|
89
|
+
$ref: "#/components/schemas/ln_address"
|
|
90
|
+
- name: msat
|
|
91
|
+
in: query
|
|
92
|
+
required: false
|
|
93
|
+
description: The price quoted at payRequest, in millisatoshi. Signed, so it cannot be edited.
|
|
94
|
+
schema:
|
|
95
|
+
type: string
|
|
96
|
+
pattern: "^[0-9]+$"
|
|
97
|
+
- name: n
|
|
98
|
+
in: query
|
|
99
|
+
required: false
|
|
100
|
+
description: |
|
|
101
|
+
A random nonce minted per payRequest. It is in the signature and it is also the
|
|
102
|
+
idempotency key for the mint, so a wallet that retries the same callback URL gets
|
|
103
|
+
the same invoice back instead of a second one.
|
|
104
|
+
schema:
|
|
105
|
+
type: string
|
|
106
|
+
pattern: "^[0-9a-f]{32}$"
|
|
107
|
+
- name: sig
|
|
108
|
+
in: query
|
|
109
|
+
required: false
|
|
110
|
+
description: |
|
|
111
|
+
Hex HMAC-SHA256 over `<to>|<msat>|<n>`, keyed with the `secret` you configured.
|
|
112
|
+
Compared in constant time, and a mismatch is refused with `this callback was not
|
|
113
|
+
signed here`.
|
|
114
|
+
schema:
|
|
115
|
+
type: string
|
|
116
|
+
pattern: "^[0-9a-f]{64}$"
|
|
117
|
+
- name: amount
|
|
118
|
+
in: query
|
|
119
|
+
required: false
|
|
120
|
+
description: |
|
|
121
|
+
What the payer's wallet intends to send, in millisatoshi, per LUD-06. This is a
|
|
122
|
+
fixed-price endpoint, so anything other than the quoted price is refused rather
|
|
123
|
+
than quietly minted.
|
|
124
|
+
schema:
|
|
125
|
+
type: string
|
|
126
|
+
pattern: "^[0-9]+$"
|
|
127
|
+
responses:
|
|
128
|
+
"200":
|
|
129
|
+
description: |
|
|
130
|
+
A payRequest, an invoice, or a refusal. Which one is decided by `to` and by
|
|
131
|
+
whether the signature held, and LUD-06 gives all three the same status code.
|
|
132
|
+
content:
|
|
133
|
+
application/json:
|
|
134
|
+
schema:
|
|
135
|
+
oneOf:
|
|
136
|
+
- $ref: "#/components/schemas/pay_request"
|
|
137
|
+
- $ref: "#/components/schemas/invoice"
|
|
138
|
+
- $ref: "#/components/schemas/refusal"
|
|
139
|
+
|
|
140
|
+
/verify/bank:
|
|
141
|
+
get:
|
|
142
|
+
tags: [bank-transfer]
|
|
143
|
+
operationId: verifyBankTransfer
|
|
144
|
+
summary: Say whether the transfer landed, and release the preimage when it did
|
|
145
|
+
description: |
|
|
146
|
+
Mounted wherever you passed `verifyUrl` to `bankTransfer`, which puts what to look for
|
|
147
|
+
in the query and signs it. The gateway polls this exactly as it polls a wallet's LUD-21
|
|
148
|
+
endpoint, and it cannot tell the difference, which is the whole point.
|
|
149
|
+
|
|
150
|
+
A query this service did not sign is refused. Without that check the endpoint would
|
|
151
|
+
answer "did anyone send you this amount with this note" to whoever asked, which is a
|
|
152
|
+
bank statement handed out one question at a time.
|
|
153
|
+
|
|
154
|
+
The preimage is an HMAC of the reference, the amount and the currency under a secret
|
|
155
|
+
only this service holds, so the gateway can check it hashes to what it was given and
|
|
156
|
+
still cannot produce it. Nothing is stored between calls.
|
|
157
|
+
parameters:
|
|
158
|
+
- name: ref
|
|
159
|
+
in: query
|
|
160
|
+
required: true
|
|
161
|
+
description: |
|
|
162
|
+
What the payer had to leave on the transfer. Matched as a substring against every
|
|
163
|
+
field the bank puts a note in, so noise around it is fine.
|
|
164
|
+
schema:
|
|
165
|
+
type: string
|
|
166
|
+
- name: minor
|
|
167
|
+
in: query
|
|
168
|
+
required: true
|
|
169
|
+
description: The price in the smallest unit of the currency, so 48055 is 480.55 CZK.
|
|
170
|
+
schema:
|
|
171
|
+
type: integer
|
|
172
|
+
minimum: 1
|
|
173
|
+
- name: cc
|
|
174
|
+
in: query
|
|
175
|
+
required: true
|
|
176
|
+
description: The three letter currency code the credit has to be in.
|
|
177
|
+
schema:
|
|
178
|
+
type: string
|
|
179
|
+
minLength: 3
|
|
180
|
+
maxLength: 3
|
|
181
|
+
- name: sig
|
|
182
|
+
in: query
|
|
183
|
+
required: true
|
|
184
|
+
description: Hex HMAC-SHA256 over `<ref>|<minor>|<cc>`, minted by `bankTransfer`.
|
|
185
|
+
schema:
|
|
186
|
+
type: string
|
|
187
|
+
pattern: "^[0-9a-f]{64}$"
|
|
188
|
+
responses:
|
|
189
|
+
"200":
|
|
190
|
+
description: |
|
|
191
|
+
Whether a matching credit is on the statement. The preimage is present only once
|
|
192
|
+
one is, and it never appears without `settled` being true.
|
|
193
|
+
content:
|
|
194
|
+
application/json:
|
|
195
|
+
schema:
|
|
196
|
+
$ref: "#/components/schemas/settlement"
|
|
197
|
+
"400":
|
|
198
|
+
description: The query is missing something, so there is nothing to look for.
|
|
199
|
+
content:
|
|
200
|
+
application/json:
|
|
201
|
+
schema:
|
|
202
|
+
$ref: "#/components/schemas/unsettled"
|
|
203
|
+
"403":
|
|
204
|
+
description: The signature does not hold, so this service never minted the question.
|
|
205
|
+
content:
|
|
206
|
+
application/json:
|
|
207
|
+
schema:
|
|
208
|
+
$ref: "#/components/schemas/unsettled"
|
|
209
|
+
|
|
210
|
+
components:
|
|
211
|
+
schemas:
|
|
212
|
+
settlement:
|
|
213
|
+
type: object
|
|
214
|
+
description: The LUD-21 answer, whichever rail the money took.
|
|
215
|
+
properties:
|
|
216
|
+
settled:
|
|
217
|
+
type: boolean
|
|
218
|
+
preimage:
|
|
219
|
+
type: string
|
|
220
|
+
pattern: "^[0-9a-f]{64}$"
|
|
221
|
+
description: |
|
|
222
|
+
Hashes to the payment hash the gateway was given at watch time. Present only when
|
|
223
|
+
`settled` is true.
|
|
224
|
+
required: [settled]
|
|
225
|
+
|
|
226
|
+
unsettled:
|
|
227
|
+
type: object
|
|
228
|
+
description: A refusal, shaped so a caller that only reads `settled` still reads it right.
|
|
229
|
+
properties:
|
|
230
|
+
settled:
|
|
231
|
+
type: boolean
|
|
232
|
+
const: false
|
|
233
|
+
required: [settled]
|
|
234
|
+
|
|
235
|
+
ln_address:
|
|
236
|
+
type: string
|
|
237
|
+
format: email
|
|
238
|
+
description: A LUD-16 lightning address, `user@domain`.
|
|
239
|
+
examples: ["someone@blink.sv"]
|
|
240
|
+
|
|
241
|
+
pay_request:
|
|
242
|
+
type: object
|
|
243
|
+
description: The LUD-06 payRequest, answered when the request carries no `to`.
|
|
244
|
+
properties:
|
|
245
|
+
tag:
|
|
246
|
+
type: string
|
|
247
|
+
const: payRequest
|
|
248
|
+
callback:
|
|
249
|
+
type: string
|
|
250
|
+
format: uri
|
|
251
|
+
description: |
|
|
252
|
+
This same path with the winning address, the price, a nonce and the signature on
|
|
253
|
+
it. Set `baseUrl` when a proxy hides your public URL, because the wallet has to be
|
|
254
|
+
able to reach whatever is in here.
|
|
255
|
+
metadata:
|
|
256
|
+
type: string
|
|
257
|
+
description: |
|
|
258
|
+
The winning wallet's own LUD-06 metadata string, passed through unchanged. It must
|
|
259
|
+
stay byte for byte identical, because the payer's wallet hashes it and compares the
|
|
260
|
+
result to the invoice's description hash, and only the recipient's wallet mints
|
|
261
|
+
that invoice.
|
|
262
|
+
examples: ['[["text/plain","Paying someone@blink.sv"]]']
|
|
263
|
+
minSendable:
|
|
264
|
+
type: integer
|
|
265
|
+
description: The price in millisatoshi. Equal to `maxSendable`, so the amount is not the payer's to choose.
|
|
266
|
+
maxSendable:
|
|
267
|
+
type: integer
|
|
268
|
+
description: The same price. A fixed-price endpoint quotes one number twice.
|
|
269
|
+
required: [tag, callback, metadata, minSendable, maxSendable]
|
|
270
|
+
|
|
271
|
+
invoice:
|
|
272
|
+
type: object
|
|
273
|
+
description: The LUD-06 callback answer, carrying an invoice the recipient's wallet issued.
|
|
274
|
+
properties:
|
|
275
|
+
status:
|
|
276
|
+
type: string
|
|
277
|
+
const: OK
|
|
278
|
+
pr:
|
|
279
|
+
type: string
|
|
280
|
+
description: The BOLT11 invoice. This is what the payer pays, and it pays the recipient directly.
|
|
281
|
+
routes:
|
|
282
|
+
type: array
|
|
283
|
+
description: Always empty. The field is required by LUD-06 and means nothing here.
|
|
284
|
+
items: {}
|
|
285
|
+
verify:
|
|
286
|
+
type: string
|
|
287
|
+
format: uri
|
|
288
|
+
description: |
|
|
289
|
+
The recipient's own LUD-21 endpoint, on their server rather than yours. Anyone
|
|
290
|
+
holding the invoice can ask it whether the payment settled, so settlement is
|
|
291
|
+
proved by the wallet that was paid and not asserted by anything in this chain.
|
|
292
|
+
required: [status, pr, routes, verify]
|
|
293
|
+
|
|
294
|
+
refusal:
|
|
295
|
+
type: object
|
|
296
|
+
description: |
|
|
297
|
+
A LUD-06 error. HTTP is still 200, because that is what the standard says and what
|
|
298
|
+
every wallet parses.
|
|
299
|
+
properties:
|
|
300
|
+
status:
|
|
301
|
+
type: string
|
|
302
|
+
const: ERROR
|
|
303
|
+
reason:
|
|
304
|
+
type: string
|
|
305
|
+
description: For a human reading a wallet's error toast, never for a program to branch on.
|
|
306
|
+
examples: ["this callback was not signed here"]
|
|
307
|
+
required: [status, reason]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "thunder-bridge",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Trustless JavaScript client for the Thunder Bridge Lightning payment gateway. Proves the invoice came from your own wallet before the payer sees it.",
|
|
5
5
|
"author": "i-am-fatik",
|
|
6
6
|
"homepage": "https://agora.gripe/en/tools/thunder-bridge",
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
|
-
"dist"
|
|
25
|
+
"dist",
|
|
26
|
+
"openapi.yaml"
|
|
26
27
|
],
|
|
27
28
|
"engines": {
|
|
28
29
|
"node": ">=22"
|
|
@@ -38,10 +39,10 @@
|
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@biomejs/biome": "^2.4.16",
|
|
41
|
-
"@types/node": "^
|
|
42
|
+
"@types/node": "^24.13.3",
|
|
42
43
|
"tsup": "^8.0.0",
|
|
43
44
|
"typescript": "^5.5.0",
|
|
44
|
-
"vitest": "^
|
|
45
|
+
"vitest": "^4.1.10"
|
|
45
46
|
},
|
|
46
47
|
"keywords": [
|
|
47
48
|
"lightning",
|