beignet 0.2.0 → 0.4.0

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.
Files changed (215) hide show
  1. package/README.md +306 -1
  2. package/dist/cli/auth.js +238 -0
  3. package/dist/cli/auth.js.map +1 -0
  4. package/dist/cli/beignet-node.js +1034 -57
  5. package/dist/cli/beignet-node.js.map +1 -1
  6. package/dist/cli/cli.js +1257 -18
  7. package/dist/cli/cli.js.map +1 -1
  8. package/dist/cli/config.js +45 -1
  9. package/dist/cli/config.js.map +1 -1
  10. package/dist/cli/daemon.js +398 -38
  11. package/dist/cli/daemon.js.map +1 -1
  12. package/dist/cli/errors.js +2 -0
  13. package/dist/cli/errors.js.map +1 -1
  14. package/dist/cli/index.js +6 -1
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/openapi.js +1343 -89
  17. package/dist/cli/openapi.js.map +1 -1
  18. package/dist/cli/restore.js +89 -0
  19. package/dist/cli/restore.js.map +1 -0
  20. package/dist/cli/wallet-storage.js +34 -0
  21. package/dist/cli/wallet-storage.js.map +1 -0
  22. package/dist/electrum/index.js +107 -12
  23. package/dist/electrum/index.js.map +1 -1
  24. package/dist/index.js +1 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/lightning/advisor/fee-tuner.js +42 -0
  27. package/dist/lightning/advisor/fee-tuner.js.map +1 -0
  28. package/dist/lightning/advisor/index.js +9 -1
  29. package/dist/lightning/advisor/index.js.map +1 -1
  30. package/dist/lightning/advisor/rebalance-planner.js +55 -0
  31. package/dist/lightning/advisor/rebalance-planner.js.map +1 -0
  32. package/dist/lightning/backup/index.js +18 -0
  33. package/dist/lightning/backup/index.js.map +1 -0
  34. package/dist/lightning/backup/scb.js +42 -0
  35. package/dist/lightning/backup/scb.js.map +1 -0
  36. package/dist/lightning/chain/chain-monitor.js +27 -0
  37. package/dist/lightning/chain/chain-monitor.js.map +1 -1
  38. package/dist/lightning/chain/output-resolver.js +66 -15
  39. package/dist/lightning/chain/output-resolver.js.map +1 -1
  40. package/dist/lightning/chain/types.js +1 -0
  41. package/dist/lightning/chain/types.js.map +1 -1
  42. package/dist/lightning/channel/channel-manager.js +91 -24
  43. package/dist/lightning/channel/channel-manager.js.map +1 -1
  44. package/dist/lightning/channel/channel-state.js.map +1 -1
  45. package/dist/lightning/channel/channel.js +122 -4
  46. package/dist/lightning/channel/channel.js.map +1 -1
  47. package/dist/lightning/channel/commitment-builder.js +4 -7
  48. package/dist/lightning/channel/commitment-builder.js.map +1 -1
  49. package/dist/lightning/channel/dual-funding.js +6 -5
  50. package/dist/lightning/channel/dual-funding.js.map +1 -1
  51. package/dist/lightning/channel/splice-tx.js.map +1 -1
  52. package/dist/lightning/channel/types.js +3 -1
  53. package/dist/lightning/channel/types.js.map +1 -1
  54. package/dist/lightning/channel/validation.js +3 -3
  55. package/dist/lightning/channel/validation.js.map +1 -1
  56. package/dist/lightning/crypto/index.js +1 -0
  57. package/dist/lightning/crypto/index.js.map +1 -1
  58. package/dist/lightning/crypto/message-signing.js +116 -0
  59. package/dist/lightning/crypto/message-signing.js.map +1 -0
  60. package/dist/lightning/crypto/xchacha20poly1305.js +77 -0
  61. package/dist/lightning/crypto/xchacha20poly1305.js.map +1 -0
  62. package/dist/lightning/features/flags.js +1 -0
  63. package/dist/lightning/features/flags.js.map +1 -1
  64. package/dist/lightning/gossip/messages.js +37 -1
  65. package/dist/lightning/gossip/messages.js.map +1 -1
  66. package/dist/lightning/index.js +2 -1
  67. package/dist/lightning/index.js.map +1 -1
  68. package/dist/lightning/keys/signer.js +24 -3
  69. package/dist/lightning/keys/signer.js.map +1 -1
  70. package/dist/lightning/keys/wallet-keys.js +1 -0
  71. package/dist/lightning/keys/wallet-keys.js.map +1 -1
  72. package/dist/lightning/message/index.js +1 -0
  73. package/dist/lightning/message/index.js.map +1 -1
  74. package/dist/lightning/message/peer-storage.js +43 -0
  75. package/dist/lightning/message/peer-storage.js.map +1 -0
  76. package/dist/lightning/message/types.js +2 -0
  77. package/dist/lightning/message/types.js.map +1 -1
  78. package/dist/lightning/node/lightning-node.js +1593 -236
  79. package/dist/lightning/node/lightning-node.js.map +1 -1
  80. package/dist/lightning/node/types.js +18 -1
  81. package/dist/lightning/node/types.js.map +1 -1
  82. package/dist/lightning/offer/offer-manager.js +2 -5
  83. package/dist/lightning/offer/offer-manager.js.map +1 -1
  84. package/dist/lightning/offer/tlv.js +3 -1
  85. package/dist/lightning/offer/tlv.js.map +1 -1
  86. package/dist/lightning/storage/encryption.js +67 -0
  87. package/dist/lightning/storage/encryption.js.map +1 -0
  88. package/dist/lightning/storage/index.js +1 -0
  89. package/dist/lightning/storage/index.js.map +1 -1
  90. package/dist/lightning/storage/serialization.js +6 -2
  91. package/dist/lightning/storage/serialization.js.map +1 -1
  92. package/dist/lightning/storage/sqlite-storage.js +405 -33
  93. package/dist/lightning/storage/sqlite-storage.js.map +1 -1
  94. package/dist/lightning/watchtower/blob.js +221 -0
  95. package/dist/lightning/watchtower/blob.js.map +1 -0
  96. package/dist/lightning/watchtower/index.js +55 -0
  97. package/dist/lightning/watchtower/index.js.map +1 -0
  98. package/dist/lightning/watchtower/justice.js +272 -0
  99. package/dist/lightning/watchtower/justice.js.map +1 -0
  100. package/dist/lightning/watchtower/tower-connection.js +208 -0
  101. package/dist/lightning/watchtower/tower-connection.js.map +1 -0
  102. package/dist/lightning/watchtower/types.js +3 -0
  103. package/dist/lightning/watchtower/types.js.map +1 -0
  104. package/dist/lightning/watchtower/watchtower-client.js +472 -0
  105. package/dist/lightning/watchtower/watchtower-client.js.map +1 -0
  106. package/dist/lightning/watchtower/wtwire.js +201 -0
  107. package/dist/lightning/watchtower/wtwire.js.map +1 -0
  108. package/dist/logger.js +41 -0
  109. package/dist/logger.js.map +1 -0
  110. package/dist/shapes/electrum.js +32 -1
  111. package/dist/shapes/electrum.js.map +1 -1
  112. package/dist/shapes/wallet.js +11 -1
  113. package/dist/shapes/wallet.js.map +1 -1
  114. package/dist/transaction/index.js +163 -15
  115. package/dist/transaction/index.js.map +1 -1
  116. package/dist/types/cli/auth.d.ts +34 -0
  117. package/dist/types/cli/beignet-node.d.ts +148 -4
  118. package/dist/types/cli/daemon.d.ts +5 -0
  119. package/dist/types/cli/errors.d.ts +2 -0
  120. package/dist/types/cli/index.d.ts +1 -0
  121. package/dist/types/cli/restore.d.ts +10 -0
  122. package/dist/types/cli/types.d.ts +235 -1
  123. package/dist/types/cli/wallet-storage.d.ts +3 -0
  124. package/dist/types/electrum/index.d.ts +12 -0
  125. package/dist/types/electrum.js +1 -0
  126. package/dist/types/electrum.js.map +1 -1
  127. package/dist/types/index.d.ts +1 -0
  128. package/dist/types/lightning/advisor/fee-tuner.d.ts +20 -0
  129. package/dist/types/lightning/advisor/index.d.ts +4 -0
  130. package/dist/types/lightning/advisor/rebalance-planner.d.ts +12 -0
  131. package/dist/types/lightning/backup/index.d.ts +1 -0
  132. package/dist/types/lightning/backup/scb.d.ts +24 -0
  133. package/dist/types/lightning/chain/chain-monitor.d.ts +1 -0
  134. package/dist/types/lightning/chain/types.d.ts +1 -0
  135. package/dist/types/lightning/channel/channel-manager.d.ts +11 -0
  136. package/dist/types/lightning/channel/channel-state.d.ts +2 -0
  137. package/dist/types/lightning/channel/channel.d.ts +11 -5
  138. package/dist/types/lightning/channel/commitment-builder.d.ts +7 -7
  139. package/dist/types/lightning/channel/dual-funding.d.ts +2 -1
  140. package/dist/types/lightning/channel/splice-tx.d.ts +2 -2
  141. package/dist/types/lightning/channel/types.d.ts +2 -0
  142. package/dist/types/lightning/channel/validation.d.ts +1 -1
  143. package/dist/types/lightning/crypto/index.d.ts +1 -0
  144. package/dist/types/lightning/crypto/message-signing.d.ts +9 -0
  145. package/dist/types/lightning/crypto/xchacha20poly1305.d.ts +3 -0
  146. package/dist/types/lightning/features/flags.d.ts +1 -0
  147. package/dist/types/lightning/gossip/messages.d.ts +4 -0
  148. package/dist/types/lightning/index.d.ts +1 -0
  149. package/dist/types/lightning/keys/signer.d.ts +18 -1
  150. package/dist/types/lightning/keys/wallet-keys.d.ts +2 -1
  151. package/dist/types/lightning/message/index.d.ts +1 -0
  152. package/dist/types/lightning/message/peer-storage.d.ts +9 -0
  153. package/dist/types/lightning/message/types.d.ts +2 -0
  154. package/dist/types/lightning/node/lightning-node.d.ts +120 -4
  155. package/dist/types/lightning/node/types.d.ts +66 -1
  156. package/dist/types/lightning/storage/encryption.d.ts +11 -0
  157. package/dist/types/lightning/storage/index.d.ts +1 -0
  158. package/dist/types/lightning/storage/serialization.d.ts +2 -0
  159. package/dist/types/lightning/storage/sqlite-storage.d.ts +43 -3
  160. package/dist/types/lightning/storage/types.d.ts +62 -0
  161. package/dist/types/lightning/watchtower/blob.d.ts +46 -0
  162. package/dist/types/lightning/watchtower/index.d.ts +9 -0
  163. package/dist/types/lightning/watchtower/justice.d.ts +30 -0
  164. package/dist/types/lightning/watchtower/tower-connection.d.ts +34 -0
  165. package/dist/types/lightning/watchtower/types.d.ts +46 -0
  166. package/dist/types/lightning/watchtower/watchtower-client.d.ts +68 -0
  167. package/dist/types/lightning/watchtower/wtwire.d.ts +90 -0
  168. package/dist/types/logger.d.ts +17 -0
  169. package/dist/types/shapes/electrum.d.ts +3 -1
  170. package/dist/types/transaction/index.d.ts +6 -0
  171. package/dist/types/transaction.js.map +1 -1
  172. package/dist/types/types/electrum.d.ts +3 -2
  173. package/dist/types/types/transaction.d.ts +1 -1
  174. package/dist/types/types/wallet.d.ts +80 -4
  175. package/dist/types/utils/descriptors.d.ts +2 -0
  176. package/dist/types/utils/fees.d.ts +3 -0
  177. package/dist/types/utils/helpers.d.ts +3 -1
  178. package/dist/types/utils/index.d.ts +5 -0
  179. package/dist/types/utils/multisig.d.ts +29 -0
  180. package/dist/types/utils/transaction.d.ts +7 -0
  181. package/dist/types/utils/wallet-storage-encryption.d.ts +3 -0
  182. package/dist/types/utils/wallet.d.ts +1 -1
  183. package/dist/types/utils/watch-only.d.ts +12 -0
  184. package/dist/types/wallet/constants.d.ts +1 -0
  185. package/dist/types/wallet/index.d.ts +63 -4
  186. package/dist/types/wallet.js +3 -0
  187. package/dist/types/wallet.js.map +1 -1
  188. package/dist/utils/derivation-path.js +8 -7
  189. package/dist/utils/derivation-path.js.map +1 -1
  190. package/dist/utils/descriptors.js +56 -0
  191. package/dist/utils/descriptors.js.map +1 -0
  192. package/dist/utils/electrum.js +14 -4
  193. package/dist/utils/electrum.js.map +1 -1
  194. package/dist/utils/fees.js +19 -0
  195. package/dist/utils/fees.js.map +1 -0
  196. package/dist/utils/helpers.js +22 -5
  197. package/dist/utils/helpers.js.map +1 -1
  198. package/dist/utils/index.js +5 -0
  199. package/dist/utils/index.js.map +1 -1
  200. package/dist/utils/multisig.js +90 -0
  201. package/dist/utils/multisig.js.map +1 -0
  202. package/dist/utils/transaction.js +30 -3
  203. package/dist/utils/transaction.js.map +1 -1
  204. package/dist/utils/wallet-storage-encryption.js +45 -0
  205. package/dist/utils/wallet-storage-encryption.js.map +1 -0
  206. package/dist/utils/wallet.js +3 -1
  207. package/dist/utils/wallet.js.map +1 -1
  208. package/dist/utils/watch-only.js +102 -0
  209. package/dist/utils/watch-only.js.map +1 -0
  210. package/dist/wallet/constants.js +2 -1
  211. package/dist/wallet/constants.js.map +1 -1
  212. package/dist/wallet/index.js +810 -51
  213. package/dist/wallet/index.js.map +1 -1
  214. package/docs/ROADMAP.md +365 -0
  215. package/package.json +1 -1
@@ -23,16 +23,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.startDaemon = exports.parseBody = void 0;
26
+ exports.startDaemon = exports.getRelayedEvents = exports.AUTH_EXEMPT_ROUTES = exports.parseBody = void 0;
27
27
  const http = __importStar(require("http"));
28
28
  const https = __importStar(require("https"));
29
29
  const fs = __importStar(require("fs"));
30
+ const console_1 = require("console");
30
31
  const beignet_node_1 = require("./beignet-node");
32
+ const logger_1 = require("../logger");
31
33
  const errors_1 = require("./errors");
32
34
  const openapi_1 = require("./openapi");
33
35
  const webhooks_1 = require("./webhooks");
34
36
  const payment_queue_1 = require("./payment-queue");
35
37
  const http_rate_limiter_1 = require("./http-rate-limiter");
38
+ const transaction_1 = require("../utils/transaction");
39
+ const auth_1 = require("./auth");
36
40
  const MAX_BODY_BYTES = 1048576;
37
41
  const IDEMPOTENCY_TTL_MS = 24 * 60 * 60 * 1000;
38
42
  const IDEMPOTENCY_CLEANUP_INTERVAL_MS = 10 * 60 * 1000;
@@ -42,7 +46,9 @@ const IDEMPOTENT_ROUTES = new Set([
42
46
  'POST /invoice/pay-async',
43
47
  'POST /invoice/pay-retry',
44
48
  'POST /keysend',
45
- 'POST /keysend/safe'
49
+ 'POST /keysend/safe',
50
+ 'POST /rebalance',
51
+ 'POST /advisor/execute-rebalances'
46
52
  ]);
47
53
  function success(result) {
48
54
  return { ok: true, result };
@@ -50,6 +56,11 @@ function success(result) {
50
56
  function failure(code, message) {
51
57
  return { ok: false, error: { code, message } };
52
58
  }
59
+ function insufficientScopeMessage(auth, routeKey) {
60
+ const accepted = [...(0, auth_1.getRouteScopes)(routeKey), 'admin'].join(', ');
61
+ const who = auth.keyName === null ? 'API key' : `API key "${auth.keyName}"`;
62
+ return `${who} lacks the required scope (accepted: ${accepted})`;
63
+ }
53
64
  async function parseBody(req) {
54
65
  return new Promise((resolve, reject) => {
55
66
  const chunks = [];
@@ -81,28 +92,48 @@ async function parseBody(req) {
81
92
  });
82
93
  }
83
94
  exports.parseBody = parseBody;
84
- function checkAuth(req, apiToken) {
85
- const header = req.headers['authorization'];
86
- if (!header)
87
- return false;
88
- const match = header.match(/^bearer\s+(.+)$/i);
89
- if (!match)
90
- return false;
91
- return match[1] === apiToken;
92
- }
93
- const AUTH_EXEMPT_ROUTES = new Set([
95
+ exports.AUTH_EXEMPT_ROUTES = new Set([
94
96
  'GET /health',
95
97
  'GET /ready',
96
98
  'GET /openapi.json',
97
99
  'GET /metrics'
98
100
  ]);
101
+ function getRelayedEvents(htlcEvents) {
102
+ const events = [
103
+ 'payment:received',
104
+ 'payment:sent',
105
+ 'payment:failed',
106
+ 'invoice:settled',
107
+ 'channel:opening',
108
+ 'channel:ready',
109
+ 'channel:pending-close',
110
+ 'channel:force-closing',
111
+ 'channel:closed',
112
+ 'peer:connect',
113
+ 'peer:disconnect',
114
+ 'node:ready'
115
+ ];
116
+ if (htlcEvents === true) {
117
+ events.push('htlc:forwarded', 'htlc:fulfilled', 'htlc:failed');
118
+ }
119
+ return events;
120
+ }
121
+ exports.getRelayedEvents = getRelayedEvents;
99
122
  async function startDaemon(opts) {
100
123
  const port = opts.daemonPort !== undefined && opts.daemonPort !== null
101
124
  ? opts.daemonPort
102
125
  : 2112;
103
126
  const host = opts.daemonHost || '127.0.0.1';
104
- const apiToken = opts.apiToken;
105
- const node = await beignet_node_1.BeignetNode.create(opts);
127
+ const authenticator = new auth_1.ApiKeyAuthenticator(opts.apiToken, opts.apiKeys);
128
+ let logger = opts.logger;
129
+ if (!logger && opts.logLevel && opts.logLevel !== 'silent') {
130
+ const stderrConsole = new console_1.Console({
131
+ stdout: process.stderr,
132
+ stderr: process.stderr
133
+ });
134
+ logger = (0, logger_1.createConsoleLogger)(opts.logLevel, stderrConsole);
135
+ }
136
+ const node = await beignet_node_1.BeignetNode.create(logger ? { ...opts, logger } : opts);
106
137
  const storage = node.getStorage();
107
138
  const webhookManager = new webhooks_1.WebhookManager(storage);
108
139
  const paymentQueue = new payment_queue_1.PaymentQueue((bolt11, timeout, maxFee, amount, meta) => node.payInvoiceSafe(bolt11, timeout, maxFee, amount, meta), (amount) => node.canSend(amount), undefined, storage);
@@ -122,8 +153,8 @@ async function startDaemon(opts) {
122
153
  const routes = {
123
154
  'GET /info': () => success(node.getInfo()),
124
155
  'GET /mnemonic': () => {
125
- if (!apiToken) {
126
- return failure('MNEMONIC_REQUIRES_AUTH', 'Configure apiToken to enable mnemonic access');
156
+ if (!authenticator.enabled) {
157
+ return failure('MNEMONIC_REQUIRES_AUTH', 'Configure apiToken or apiKeys to enable mnemonic access');
127
158
  }
128
159
  return success({ mnemonic: node.getMnemonic() });
129
160
  },
@@ -148,6 +179,24 @@ async function startDaemon(opts) {
148
179
  filter.metadataValue = query.get('metadataValue');
149
180
  return success(node.listPayments(Object.keys(filter).length > 0 ? filter : undefined));
150
181
  },
182
+ 'GET /forwards': (_body, query) => {
183
+ const filter = {};
184
+ if (query.get('since'))
185
+ filter.since = Number(query.get('since'));
186
+ if (query.get('until'))
187
+ filter.until = Number(query.get('until'));
188
+ if (query.get('limit'))
189
+ filter.limit = Number(query.get('limit'));
190
+ if (query.get('offset'))
191
+ filter.offset = Number(query.get('offset'));
192
+ if (query.get('channelId'))
193
+ filter.channelId = query.get('channelId');
194
+ return success(node.listForwards(Object.keys(filter).length > 0 ? filter : undefined));
195
+ },
196
+ 'GET /forwards/summary': (_body, query) => {
197
+ const since = query.get('since') ? Number(query.get('since')) : undefined;
198
+ return success(node.getForwardingSummary(since));
199
+ },
151
200
  'GET /invoices': () => success(node.listInvoices()),
152
201
  'GET /invoice': (_body, query) => {
153
202
  const paymentHash = query.get('paymentHash');
@@ -170,6 +219,41 @@ async function startDaemon(opts) {
170
219
  },
171
220
  'GET /spend-limit': () => success(node.getDailySpendInfo()),
172
221
  'GET /liquidity': () => success(node.getLiquiditySnapshot()),
222
+ 'GET /watchtowers': () => success({ towers: node.listWatchtowers() }),
223
+ 'POST /watchtower/add': (body) => {
224
+ const { uri } = body;
225
+ if (!uri)
226
+ return failure('INVALID_PARAMS', 'uri required');
227
+ try {
228
+ node.addWatchtower(uri);
229
+ }
230
+ catch (err) {
231
+ return failure('INVALID_PARAMS', err instanceof Error ? err.message : String(err));
232
+ }
233
+ return success({ added: uri });
234
+ },
235
+ 'DELETE /watchtower/remove': (body) => {
236
+ const { uri } = body;
237
+ if (!uri)
238
+ return failure('INVALID_PARAMS', 'uri required');
239
+ node.removeWatchtower(uri);
240
+ return success({ removed: uri });
241
+ },
242
+ 'GET /advisor/recommendations': () => success(node.getAdvisorRecommendations()),
243
+ 'POST /advisor/execute-rebalances': async (body) => {
244
+ const { budgetSatsPerDay } = body;
245
+ return success(await node.executeRebalances(budgetSatsPerDay));
246
+ },
247
+ 'POST /rebalance': async (body) => {
248
+ const { fromChannelId, toChannelId, amountSats, maxFeeSats } = body;
249
+ if (!fromChannelId ||
250
+ !toChannelId ||
251
+ amountSats === undefined ||
252
+ maxFeeSats === undefined) {
253
+ return failure('INVALID_PARAMS', 'fromChannelId, toChannelId, amountSats, and maxFeeSats required');
254
+ }
255
+ return success(await node.rebalanceChannel(fromChannelId, toChannelId, amountSats, maxFeeSats));
256
+ },
173
257
  'GET /fees': () => {
174
258
  const snapshot = node.getFeeSnapshot();
175
259
  if (!snapshot)
@@ -189,6 +273,26 @@ async function startDaemon(opts) {
189
273
  return success(txs);
190
274
  },
191
275
  'GET /utxos': () => success(node.listUtxos()),
276
+ 'POST /utxo/freeze': async (body) => {
277
+ const { txid, index } = body;
278
+ if (!txid || index === undefined)
279
+ return failure('INVALID_PARAMS', 'txid and index required');
280
+ return success(await node.freezeUtxo(txid, index));
281
+ },
282
+ 'POST /utxo/unfreeze': async (body) => {
283
+ const { txid, index } = body;
284
+ if (!txid || index === undefined)
285
+ return failure('INVALID_PARAMS', 'txid and index required');
286
+ return success(await node.unfreezeUtxo(txid, index));
287
+ },
288
+ 'POST /address/label': async (body) => {
289
+ const { address, label } = body;
290
+ if (!address || label === undefined)
291
+ return failure('INVALID_PARAMS', 'address and label required (empty label clears)');
292
+ return success(await node.setAddressLabel(address, label));
293
+ },
294
+ 'GET /address/labels': () => success(node.listAddressLabels()),
295
+ 'GET /wallet/descriptors': () => success(node.exportDescriptors()),
192
296
  'GET /channel/suggestions': (_body, query) => {
193
297
  const count = query.get('count') ? Number(query.get('count')) : undefined;
194
298
  return success(node.getChannelSuggestions(count));
@@ -203,7 +307,16 @@ async function startDaemon(opts) {
203
307
  options.limit = Number(query.get('limit'));
204
308
  return success(node.getActionLog(Object.keys(options).length > 0 ? options : undefined));
205
309
  },
206
- 'POST /address/new': async () => success({ address: await node.getNewAddress() }),
310
+ 'POST /address/new': async (body) => {
311
+ const { bip21: wantBip21, amountSats, label, message } = (body ?? {});
312
+ const address = await node.getNewAddress();
313
+ if (!wantBip21)
314
+ return success({ address });
315
+ const uri = (0, transaction_1.encodeBip21)({ address, amountSats, label, message });
316
+ if (uri.isErr())
317
+ return failure('INVALID_PARAMS', uri.error.message);
318
+ return success({ address, bip21: uri.value });
319
+ },
207
320
  'POST /wallet/refresh': async () => {
208
321
  await node.refreshWallet();
209
322
  return success({ refreshed: true });
@@ -214,10 +327,53 @@ async function startDaemon(opts) {
214
327
  return failure('INVALID_PARAMS', 'address and amountSats required');
215
328
  return success(await node.sendOnchain(address, amountSats, satsPerVbyte));
216
329
  },
330
+ 'POST /send-max': async (body) => {
331
+ const { address, satsPerVbyte } = body;
332
+ if (!address)
333
+ return failure('INVALID_PARAMS', 'address required');
334
+ return success(await node.sendMaxOnchain(address, satsPerVbyte));
335
+ },
336
+ 'POST /tx/bump-fee': async (body) => {
337
+ const { txid, satsPerVbyte } = body;
338
+ if (!txid || satsPerVbyte === undefined)
339
+ return failure('INVALID_PARAMS', 'txid and satsPerVbyte required');
340
+ return success(await node.bumpFeeOnchain(txid, satsPerVbyte));
341
+ },
342
+ 'POST /tx/boost': async (body) => {
343
+ const { txid, satsPerVbyte } = body;
344
+ if (!txid)
345
+ return failure('INVALID_PARAMS', 'txid required');
346
+ return success(await node.boostOnchain(txid, satsPerVbyte));
347
+ },
348
+ 'GET /transactions/boostable': () => success(node.listBoostableTransactions()),
349
+ 'POST /consolidate': async (body) => {
350
+ const { satsPerVbyte } = body;
351
+ return success(await node.consolidateUtxos(satsPerVbyte));
352
+ },
353
+ 'POST /psbt/build': async (body) => {
354
+ const { outputs, satsPerVbyte } = body;
355
+ if (!outputs || !Array.isArray(outputs) || outputs.length === 0)
356
+ return failure('INVALID_PARAMS', 'outputs array of { address, amountSats } required');
357
+ return success(await node.buildPsbt(outputs, satsPerVbyte));
358
+ },
359
+ 'POST /psbt/import-signed': (body) => {
360
+ const { psbtBase64 } = body;
361
+ if (!psbtBase64)
362
+ return failure('INVALID_PARAMS', 'psbtBase64 required');
363
+ return success(node.importSignedPsbt(psbtBase64));
364
+ },
365
+ 'POST /psbt/combine': (body) => {
366
+ const { psbts } = body;
367
+ if (!psbts || !Array.isArray(psbts) || psbts.length < 2)
368
+ return failure('INVALID_PARAMS', 'psbts array with at least two base64 PSBTs required');
369
+ return success(node.combinePsbts(psbts));
370
+ },
217
371
  'POST /peer/connect': async (body) => {
218
372
  const { pubkey, host: peerHost, port: peerPort } = body;
219
- if (!pubkey || !peerHost || !peerPort)
220
- return failure('INVALID_PARAMS', 'pubkey, host, and port required');
373
+ if (!pubkey)
374
+ return failure('INVALID_PARAMS', 'pubkey required');
375
+ if ((peerHost === undefined) !== (peerPort === undefined))
376
+ return failure('INVALID_PARAMS', 'host and port must be provided together (omit both to resolve by node id)');
221
377
  return success(await node.connectPeer(pubkey, peerHost, peerPort));
222
378
  },
223
379
  'POST /peer/disconnect': (body) => {
@@ -254,7 +410,7 @@ async function startDaemon(opts) {
254
410
  commitmentTxid: result.commitmentTxid
255
411
  });
256
412
  },
257
- 'POST /channel/update-fee': (body) => {
413
+ 'POST /channel/update-commitment-feerate': (body) => {
258
414
  const { channelId, feeratePerKw } = body;
259
415
  if (!channelId)
260
416
  return failure('INVALID_PARAMS', 'channelId required');
@@ -262,6 +418,32 @@ async function startDaemon(opts) {
262
418
  return failure('INVALID_PARAMS', 'feeratePerKw required');
263
419
  return success(node.updateChannelFee(channelId, feeratePerKw));
264
420
  },
421
+ 'POST /channel/update-policy': (body) => {
422
+ const { channelId, all, feeBaseMsat, feeProportionalMillionths, cltvExpiryDelta, htlcMinimumMsat, htlcMaximumMsat } = body;
423
+ if (!channelId && all !== true)
424
+ return failure('INVALID_PARAMS', 'channelId or all:true required');
425
+ try {
426
+ return success(node.updateChannelPolicy(all === true ? 'all' : channelId, {
427
+ feeBaseMsat,
428
+ feeProportionalMillionths,
429
+ cltvExpiryDelta,
430
+ htlcMinimumMsat,
431
+ htlcMaximumMsat
432
+ }));
433
+ }
434
+ catch (err) {
435
+ return failure('INVALID_PARAMS', err.message);
436
+ }
437
+ },
438
+ 'GET /channel/policy': (body, query) => {
439
+ const channelId = query.get('channelId') || body.channelId;
440
+ if (!channelId)
441
+ return failure('INVALID_PARAMS', 'channelId required');
442
+ const policy = node.getChannelPolicy(channelId);
443
+ if (!policy)
444
+ return failure('NOT_FOUND', 'Channel not found');
445
+ return success(policy);
446
+ },
265
447
  'GET /channel': (body, query) => {
266
448
  const channelId = query.get('channelId') || body.channelId;
267
449
  if (!channelId)
@@ -306,6 +488,40 @@ async function startDaemon(opts) {
306
488
  : undefined;
307
489
  return success(node.createInvoice(amountSats, description, expirySecs, hashBuf));
308
490
  },
491
+ 'POST /invoice/create-hold': (body) => {
492
+ const { paymentHash, amountMsat, amountSats, description, expiry } = body;
493
+ if (!paymentHash)
494
+ return failure('INVALID_PARAMS', 'paymentHash required');
495
+ let amountMsatBig;
496
+ if (amountMsat !== undefined) {
497
+ try {
498
+ amountMsatBig = BigInt(amountMsat);
499
+ }
500
+ catch {
501
+ return failure('INVALID_PARAMS', 'amountMsat must be an integer');
502
+ }
503
+ }
504
+ return success(node.createHoldInvoice({
505
+ paymentHash,
506
+ amountMsat: amountMsatBig,
507
+ amountSats,
508
+ description,
509
+ expiry
510
+ }));
511
+ },
512
+ 'POST /invoice/settle-hold': (body) => {
513
+ const { preimage } = body;
514
+ if (!preimage)
515
+ return failure('INVALID_PARAMS', 'preimage required');
516
+ return success(node.settleHoldInvoice(preimage));
517
+ },
518
+ 'POST /invoice/cancel-hold': (body) => {
519
+ const { paymentHash } = body;
520
+ if (!paymentHash)
521
+ return failure('INVALID_PARAMS', 'paymentHash required');
522
+ return success(node.cancelHoldInvoice(paymentHash));
523
+ },
524
+ 'GET /invoices/held': () => success(node.listHoldInvoices()),
309
525
  'POST /invoice/decode': (body) => {
310
526
  const { bolt11 } = body;
311
527
  if (!bolt11)
@@ -525,6 +741,101 @@ async function startDaemon(opts) {
525
741
  return failure('INVALID_PARAMS', 'destination and amountSats required');
526
742
  return success(node.probeRoute(destination, amountSats));
527
743
  },
744
+ 'GET /graph/info': () => success(node.getGraphInfo()),
745
+ 'GET /graph/node': (_body, query) => {
746
+ const pubkey = query.get('pubkey');
747
+ if (!pubkey)
748
+ return failure('INVALID_PARAMS', 'pubkey required');
749
+ const info = node.getGraphNode(pubkey);
750
+ if (!info)
751
+ return failure('NOT_FOUND', 'Node not found in graph');
752
+ return success(info);
753
+ },
754
+ 'GET /graph/channel': (_body, query) => {
755
+ const scid = query.get('scid');
756
+ if (!scid)
757
+ return failure('INVALID_PARAMS', 'scid required');
758
+ const info = node.getGraphChannel(scid);
759
+ if (!info)
760
+ return failure('NOT_FOUND', 'Channel not found in graph');
761
+ return success(info);
762
+ },
763
+ 'GET /graph/describe': (_body, query) => {
764
+ const limitParam = query.get('limit');
765
+ const offsetParam = query.get('offset');
766
+ let limit;
767
+ let offset;
768
+ if (limitParam !== null) {
769
+ limit = Number(limitParam);
770
+ if (!Number.isInteger(limit) || limit < 1)
771
+ return failure('INVALID_PARAMS', 'limit must be a positive integer');
772
+ }
773
+ if (offsetParam !== null) {
774
+ offset = Number(offsetParam);
775
+ if (!Number.isInteger(offset) || offset < 0)
776
+ return failure('INVALID_PARAMS', 'offset must be a non-negative integer');
777
+ }
778
+ return success(node.describeGraph(limit, offset));
779
+ },
780
+ 'POST /route/query': (body) => {
781
+ const { destination, amountSats, maxFeeSats } = body;
782
+ if (!destination || amountSats === undefined)
783
+ return failure('INVALID_PARAMS', 'destination and amountSats required');
784
+ return success(node.queryRoute(destination, amountSats, maxFeeSats));
785
+ },
786
+ 'POST /payment/send-to-route': (body) => {
787
+ const { paymentHash, route, paymentSecret } = body;
788
+ if (!paymentHash || !route)
789
+ return failure('INVALID_PARAMS', 'paymentHash and route required');
790
+ return success(node.sendToRoute(paymentHash, route, paymentSecret));
791
+ },
792
+ 'POST /message/sign': (body) => {
793
+ const { message } = body;
794
+ if (!message)
795
+ return failure('INVALID_PARAMS', 'message required');
796
+ return success(node.signMessage(message));
797
+ },
798
+ 'POST /message/verify': (body) => {
799
+ const { message, signature } = body;
800
+ if (!message || !signature)
801
+ return failure('INVALID_PARAMS', 'message and signature required');
802
+ return success(node.verifyMessage(message, signature));
803
+ },
804
+ 'POST /gossip/sync': (body) => {
805
+ const { pubkey } = body;
806
+ return success({ syncedFrom: node.syncGossip(pubkey) });
807
+ },
808
+ 'POST /gossip/sync-rapid': async () => {
809
+ const result = await node.syncRapidGossip();
810
+ if (!result) {
811
+ return failure('INVALID_PARAMS', 'Rapid gossip sync is only available on mainnet');
812
+ }
813
+ return success(result);
814
+ },
815
+ 'GET /channel/diagnostics': (body, query) => {
816
+ const channelId = query.get('channelId') || body.channelId;
817
+ if (!channelId)
818
+ return failure('INVALID_PARAMS', 'channelId required');
819
+ const diagnostics = node.getChannelDiagnostics(channelId);
820
+ if (!diagnostics)
821
+ return failure('NOT_FOUND', 'Channel not found');
822
+ return success(diagnostics);
823
+ },
824
+ 'POST /address/validate': (body) => {
825
+ const { address } = body;
826
+ if (!address)
827
+ return failure('INVALID_PARAMS', 'address required');
828
+ return success({ address, valid: node.validateAddress(address) });
829
+ },
830
+ 'POST /recover-fallback-funds': async (body) => {
831
+ const { feeRatePerVbyte } = body;
832
+ const result = await node.recoverFallbackFunds(feeRatePerVbyte !== undefined ? { feeRatePerVbyte } : undefined);
833
+ return success(result ?? { recovered: false });
834
+ },
835
+ 'POST /backup/trigger': () => {
836
+ node.triggerBackup();
837
+ return success({ triggered: true });
838
+ },
528
839
  'POST /backup': async (body) => {
529
840
  const { destPath } = body;
530
841
  if (!destPath)
@@ -537,6 +848,35 @@ async function startDaemon(opts) {
537
848
  await node.backup(destPath);
538
849
  return success({ backed_up: true });
539
850
  },
851
+ 'GET /backup/scb': () => success(node.exportStaticChannelBackup()),
852
+ 'GET /backup/peer-retrieved': () => {
853
+ const retrieved = node.getPeerRetrievedBackup();
854
+ if (!retrieved) {
855
+ return failure('NOT_FOUND', 'No peer-retrieved backup this session (no capable peer has returned our SCB yet)');
856
+ }
857
+ return success(retrieved);
858
+ },
859
+ 'POST /restore/scb': async (body) => {
860
+ const { encoded, path: scbPath } = body;
861
+ if ((encoded ? 1 : 0) + (scbPath ? 1 : 0) !== 1) {
862
+ return failure('INVALID_PARAMS', 'Provide exactly one of encoded or path');
863
+ }
864
+ let blob = encoded;
865
+ if (scbPath) {
866
+ if (scbPath.includes('..') ||
867
+ scbPath.includes('%2e%2e') ||
868
+ scbPath.includes('%2E%2E')) {
869
+ return failure('INVALID_PARAMS', 'Path traversal not allowed');
870
+ }
871
+ try {
872
+ blob = fs.readFileSync(scbPath, 'utf8');
873
+ }
874
+ catch (err) {
875
+ return failure('INVALID_PARAMS', `Cannot read SCB file: ${err.message}`);
876
+ }
877
+ }
878
+ return success(await node.restoreFromScb(blob));
879
+ },
540
880
  'POST /offer/create': (body) => {
541
881
  const { description, amountSats, issuer } = body;
542
882
  if (!description)
@@ -586,8 +926,20 @@ async function startDaemon(opts) {
586
926
  if (!cancelled)
587
927
  return failure('NOT_FOUND', 'Queued payment not found or already processing');
588
928
  return success({ cancelled: true });
929
+ },
930
+ 'GET /auth/keys': () => success({ keys: authenticator.listKeys() }),
931
+ 'POST /auth/keys/revoke': (body) => {
932
+ const { name } = body;
933
+ if (!name)
934
+ return failure('INVALID_PARAMS', 'name required');
935
+ const revoked = authenticator.revoke(name);
936
+ if (!revoked)
937
+ return failure('NOT_FOUND', `No API key named "${name}" (the legacy apiToken has no name; remove it from the config and restart)`);
938
+ return success({ revoked: name });
589
939
  }
590
940
  };
941
+ routes['POST /channel/update-fee'] =
942
+ routes['POST /channel/update-commitment-feerate'];
591
943
  const sseClients = new Set();
592
944
  const corsOrigin = opts.cors === true ? '*' : typeof opts.cors === 'string' ? opts.cors : null;
593
945
  if (opts.tlsCert && !opts.tlsKey) {
@@ -616,11 +968,20 @@ async function startDaemon(opts) {
616
968
  return;
617
969
  }
618
970
  if (routeKey === 'GET /events') {
619
- if (apiToken && !checkAuth(req, apiToken)) {
620
- res.setHeader('Content-Type', 'application/json');
621
- res.statusCode = 401;
622
- res.end(JSON.stringify(failure('UNAUTHORIZED', 'Invalid or missing Authorization header')));
623
- return;
971
+ if (authenticator.enabled) {
972
+ const auth = authenticator.authenticate(req.headers['authorization']);
973
+ if (!auth.ok) {
974
+ res.setHeader('Content-Type', 'application/json');
975
+ res.statusCode = 401;
976
+ res.end(JSON.stringify(failure('UNAUTHORIZED', 'Invalid or missing Authorization header')));
977
+ return;
978
+ }
979
+ if (!(0, auth_1.scopesAllowRoute)(auth.scopes, routeKey)) {
980
+ res.setHeader('Content-Type', 'application/json');
981
+ res.statusCode = 403;
982
+ res.end(JSON.stringify(failure('FORBIDDEN', insufficientScopeMessage(auth, routeKey))));
983
+ return;
984
+ }
624
985
  }
625
986
  const sseHeaders = {
626
987
  'Content-Type': 'text/event-stream',
@@ -634,6 +995,7 @@ async function startDaemon(opts) {
634
995
  'Content-Type, Authorization';
635
996
  }
636
997
  res.writeHead(200, sseHeaders);
998
+ res.write(': connected\n\n');
637
999
  sseClients.add(res);
638
1000
  const keepalive = setInterval(() => {
639
1001
  res.write(': keepalive\n\n');
@@ -650,14 +1012,20 @@ async function startDaemon(opts) {
650
1012
  return;
651
1013
  }
652
1014
  res.setHeader('Content-Type', 'application/json');
653
- if (apiToken && !AUTH_EXEMPT_ROUTES.has(routeKey)) {
654
- if (!checkAuth(req, apiToken)) {
1015
+ if (authenticator.enabled && !exports.AUTH_EXEMPT_ROUTES.has(routeKey)) {
1016
+ const auth = authenticator.authenticate(req.headers['authorization']);
1017
+ if (!auth.ok) {
655
1018
  res.statusCode = 401;
656
1019
  res.end(JSON.stringify(failure('UNAUTHORIZED', 'Invalid or missing Authorization header')));
657
1020
  return;
658
1021
  }
1022
+ if (!(0, auth_1.scopesAllowRoute)(auth.scopes, routeKey)) {
1023
+ res.statusCode = 403;
1024
+ res.end(JSON.stringify(failure('FORBIDDEN', insufficientScopeMessage(auth, routeKey))));
1025
+ return;
1026
+ }
659
1027
  }
660
- if (rateLimiter && !AUTH_EXEMPT_ROUTES.has(routeKey)) {
1028
+ if (rateLimiter && !exports.AUTH_EXEMPT_ROUTES.has(routeKey)) {
661
1029
  const clientKey = req.headers['authorization'] || req.socket.remoteAddress || 'unknown';
662
1030
  if (!rateLimiter.isAllowed(clientKey)) {
663
1031
  res.statusCode = 429;
@@ -747,16 +1115,7 @@ async function startDaemon(opts) {
747
1115
  else {
748
1116
  server = http.createServer(requestHandler);
749
1117
  }
750
- const sseEvents = [
751
- 'payment:received',
752
- 'payment:sent',
753
- 'payment:failed',
754
- 'channel:ready',
755
- 'channel:closed',
756
- 'peer:connect',
757
- 'peer:disconnect',
758
- 'node:ready'
759
- ];
1118
+ const sseEvents = getRelayedEvents(opts.htlcEvents);
760
1119
  for (const eventName of sseEvents) {
761
1120
  node.on(eventName, (data) => {
762
1121
  if (sseClients.size === 0)
@@ -775,6 +1134,7 @@ async function startDaemon(opts) {
775
1134
  return new Promise((resolve, reject) => {
776
1135
  server.on('error', reject);
777
1136
  server.listen(port, host, () => {
1137
+ logger?.info(`Daemon listening on ${host}:${port}`);
778
1138
  resolve({ server, node });
779
1139
  });
780
1140
  });