thunder-bridge 0.8.0 → 0.8.1

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/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.0",
3
+ "version": "0.8.1",
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"