@openreceive/node 0.3.1 → 0.3.3
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/dist/{chunk-6HALOJYF.js → chunk-ZYS6QECW.js} +968 -25
- package/dist/cli.d.ts +19 -1
- package/dist/cli.js +273 -9
- package/dist/index.d.ts +6 -4
- package/dist/index.js +35 -974
- package/package.json +3 -2
- package/skills/debug-openreceive-payment/SKILL.md +88 -0
- package/skills/integrate-openreceive/SKILL.md +116 -0
- package/skills/integrate-openreceive/references/node.md +480 -0
- package/skills/integrate-openreceive/references/rails.md +568 -0
package/dist/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
|
+
HEX_64,
|
|
2
3
|
OPENRECEIVE_SWAP_PAY_IN_ASSETS,
|
|
4
|
+
ReceiveCheckoutValidationError,
|
|
3
5
|
TransientSwapCache,
|
|
6
|
+
WalletPreflightError,
|
|
4
7
|
createLscSwapProvidersFromEnvironment,
|
|
5
8
|
createNwcEndpointLogger,
|
|
9
|
+
createNwcReceiveClient,
|
|
6
10
|
emitLog,
|
|
7
11
|
fixedFloatCompatibleSwapProvider,
|
|
8
12
|
fixedFloatProvider,
|
|
@@ -12,953 +16,10 @@ import {
|
|
|
12
16
|
sanitizeEvent,
|
|
13
17
|
summarizeSwapProviderApiRequest,
|
|
14
18
|
summarizeSwapProviderApiResponse
|
|
15
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-ZYS6QECW.js";
|
|
16
20
|
|
|
17
21
|
// src/index.ts
|
|
18
|
-
import { OpenReceiveError as
|
|
19
|
-
|
|
20
|
-
// src/alby-nwc.ts
|
|
21
|
-
import {
|
|
22
|
-
compact,
|
|
23
|
-
formatSpendCapabilityRefusedMessage,
|
|
24
|
-
formatSpendCapabilityWarningMessage,
|
|
25
|
-
OpenReceiveError as OpenReceiveError2,
|
|
26
|
-
parseNwcUri
|
|
27
|
-
} from "@openreceive/core";
|
|
28
|
-
|
|
29
|
-
// src/nwc/errors.ts
|
|
30
|
-
import {
|
|
31
|
-
isErrorCode,
|
|
32
|
-
isRetryableErrorCode,
|
|
33
|
-
OpenReceiveError
|
|
34
|
-
} from "@openreceive/core";
|
|
35
|
-
var WalletPreflightError = class extends Error {
|
|
36
|
-
code;
|
|
37
|
-
summary;
|
|
38
|
-
constructor(code, message, summary) {
|
|
39
|
-
super(message);
|
|
40
|
-
this.name = "WalletPreflightError";
|
|
41
|
-
this.code = code;
|
|
42
|
-
this.summary = summary;
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
var ReceiveCheckoutValidationError = class extends Error {
|
|
46
|
-
constructor(message) {
|
|
47
|
-
super(message);
|
|
48
|
-
this.name = "ReceiveCheckoutValidationError";
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
var NWC_ERROR_CODE_ALIASES = {
|
|
52
|
-
ABORT_ERROR: "TIMEOUT",
|
|
53
|
-
BAD_REQUEST: "INVALID_REQUEST",
|
|
54
|
-
CONNECTION_ERROR: "WALLET_UNAVAILABLE",
|
|
55
|
-
EXPIRED: "INVOICE_EXPIRED",
|
|
56
|
-
FETCH_ERROR: "WALLET_UNAVAILABLE",
|
|
57
|
-
FORBIDDEN: "RESTRICTED",
|
|
58
|
-
INVOICE_NOT_FOUND: "NOT_FOUND",
|
|
59
|
-
INVALID_PARAMETER: "INVALID_REQUEST",
|
|
60
|
-
INVALID_PARAMETERS: "INVALID_REQUEST",
|
|
61
|
-
INVALID_PARAMS: "INVALID_REQUEST",
|
|
62
|
-
METHOD_NOT_FOUND: "UNSUPPORTED_METHOD",
|
|
63
|
-
NETWORK_ERROR: "WALLET_UNAVAILABLE",
|
|
64
|
-
NIP47_NETWORK_ERROR: "WALLET_UNAVAILABLE",
|
|
65
|
-
NOSTR_NETWORK_ERROR: "WALLET_UNAVAILABLE",
|
|
66
|
-
NOT_AUTHORIZED: "UNAUTHORIZED",
|
|
67
|
-
NOT_SUPPORTED: "UNSUPPORTED_METHOD",
|
|
68
|
-
NOTFOUND: "NOT_FOUND",
|
|
69
|
-
PERMISSION_DENIED: "RESTRICTED",
|
|
70
|
-
RELAY_CONNECTION_ERROR: "WALLET_UNAVAILABLE",
|
|
71
|
-
REQUEST_TIMEOUT: "TIMEOUT",
|
|
72
|
-
SERVICE_UNAVAILABLE: "WALLET_UNAVAILABLE",
|
|
73
|
-
TIMED_OUT: "TIMEOUT",
|
|
74
|
-
TIMEOUT_ERROR: "TIMEOUT",
|
|
75
|
-
UNKNOWN_METHOD: "UNSUPPORTED_METHOD",
|
|
76
|
-
UNSUPPORTED: "UNSUPPORTED_METHOD",
|
|
77
|
-
UNSUPPORTED_ENCRYPTION_MODE: "UNSUPPORTED_ENCRYPTION",
|
|
78
|
-
WALLET_OFFLINE: "WALLET_UNAVAILABLE",
|
|
79
|
-
WALLET_UNREACHABLE: "WALLET_UNAVAILABLE"
|
|
80
|
-
};
|
|
81
|
-
var OPENRECEIVE_ERROR_MESSAGES = {
|
|
82
|
-
NOT_IMPLEMENTED: "NWC wallet service does not implement this method.",
|
|
83
|
-
RESTRICTED: "NWC wallet service restricted this request.",
|
|
84
|
-
UNAUTHORIZED: "NWC wallet service rejected authorization.",
|
|
85
|
-
FORBIDDEN: "The host application did not authorize this request.",
|
|
86
|
-
RATE_LIMITED: "NWC wallet service rate limited this request.",
|
|
87
|
-
QUOTA_EXCEEDED: "NWC wallet service quota was exceeded.",
|
|
88
|
-
INTERNAL: "NWC wallet service returned an internal error.",
|
|
89
|
-
UNSUPPORTED_ENCRYPTION: "NWC wallet service does not support the required encryption mode.",
|
|
90
|
-
OTHER: "NWC wallet service returned an unknown error.",
|
|
91
|
-
NOT_FOUND: "NWC wallet service could not find the requested resource.",
|
|
92
|
-
TIMEOUT: "NWC wallet service request timed out.",
|
|
93
|
-
INVALID_REQUEST: "OpenReceive sent an invalid NWC wallet request.",
|
|
94
|
-
WALLET_UNAVAILABLE: "NWC wallet service is unavailable.",
|
|
95
|
-
INVOICE_EXPIRED: "NWC wallet reported that the invoice is expired.",
|
|
96
|
-
UNSUPPORTED_METHOD: "NWC wallet service does not support the requested method.",
|
|
97
|
-
CONFLICT: "NWC wallet service reported a conflicting request."
|
|
98
|
-
};
|
|
99
|
-
function normalizeNwcWalletError(error) {
|
|
100
|
-
if (error instanceof OpenReceiveError) return error;
|
|
101
|
-
const records = collectErrorRecords(error);
|
|
102
|
-
const code = knownErrorCode(error) ?? errorCodeFromRecords(records) ?? (typeof error === "string" ? normalizeNwcErrorCode(error) : void 0) ?? "OTHER";
|
|
103
|
-
const message = errorMessageFromRecords(records, error, code);
|
|
104
|
-
const retryable = firstBooleanField(records, ["retryable"]) ?? isRetryableErrorCode(code);
|
|
105
|
-
const requestId = firstStringField(records, ["request_id", "requestId"]);
|
|
106
|
-
const details = firstRecordField(records, ["details"]);
|
|
107
|
-
const body = {
|
|
108
|
-
code,
|
|
109
|
-
message,
|
|
110
|
-
retryable,
|
|
111
|
-
...requestId === void 0 ? {} : { request_id: requestId },
|
|
112
|
-
...details === void 0 ? {} : { details }
|
|
113
|
-
};
|
|
114
|
-
return new OpenReceiveError(body, { cause: error });
|
|
115
|
-
}
|
|
116
|
-
function knownErrorCode(error) {
|
|
117
|
-
if (error instanceof ReceiveCheckoutValidationError) {
|
|
118
|
-
return "INVALID_REQUEST";
|
|
119
|
-
}
|
|
120
|
-
if (error instanceof WalletPreflightError) {
|
|
121
|
-
if (error.code === "missing_required_method") return "UNSUPPORTED_METHOD";
|
|
122
|
-
if (error.code === "spend_capability_advertised") return "RESTRICTED";
|
|
123
|
-
if (error.code === "unsupported_encryption") return "UNSUPPORTED_ENCRYPTION";
|
|
124
|
-
return "WALLET_UNAVAILABLE";
|
|
125
|
-
}
|
|
126
|
-
return void 0;
|
|
127
|
-
}
|
|
128
|
-
function errorCodeFromRecords(records) {
|
|
129
|
-
for (const record of records) {
|
|
130
|
-
const directCode = normalizeNwcErrorCode(record.code) ?? normalizeNwcErrorCode(record.error_code) ?? normalizeNwcErrorCode(record.errorCode) ?? normalizeNwcErrorCode(record.type);
|
|
131
|
-
if (directCode !== void 0 && directCode !== "OTHER") return directCode;
|
|
132
|
-
const nameCode = normalizeNwcErrorCode(record.name);
|
|
133
|
-
if (nameCode !== void 0 && nameCode !== "OTHER") return nameCode;
|
|
134
|
-
if (directCode !== void 0) return directCode;
|
|
135
|
-
}
|
|
136
|
-
return void 0;
|
|
137
|
-
}
|
|
138
|
-
function normalizeNwcErrorCode(value) {
|
|
139
|
-
if (typeof value !== "string" || value.trim().length === 0) return void 0;
|
|
140
|
-
const normalized = normalizeNwcErrorCodeText(value);
|
|
141
|
-
return NWC_ERROR_CODE_ALIASES[normalized] ?? (isErrorCode(normalized) ? normalized : void 0);
|
|
142
|
-
}
|
|
143
|
-
function normalizeNwcErrorCodeText(value) {
|
|
144
|
-
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
145
|
-
}
|
|
146
|
-
function errorMessageFromRecords(records, error, code) {
|
|
147
|
-
const message = firstStringField(records, ["message", "description", "reason"]);
|
|
148
|
-
if (message !== void 0 && normalizeNwcErrorCode(message) !== code) {
|
|
149
|
-
return message;
|
|
150
|
-
}
|
|
151
|
-
if (typeof error === "string" && normalizeNwcErrorCode(error) === void 0) {
|
|
152
|
-
const trimmed = error.trim();
|
|
153
|
-
if (trimmed.length > 0) return trimmed;
|
|
154
|
-
}
|
|
155
|
-
return OPENRECEIVE_ERROR_MESSAGES[code];
|
|
156
|
-
}
|
|
157
|
-
function collectErrorRecords(value) {
|
|
158
|
-
const records = [];
|
|
159
|
-
collectErrorRecordsInto(value, records, /* @__PURE__ */ new Set());
|
|
160
|
-
return records;
|
|
161
|
-
}
|
|
162
|
-
function collectErrorRecordsInto(value, records, seen) {
|
|
163
|
-
if (typeof value !== "object" || value === null || seen.has(value)) return;
|
|
164
|
-
seen.add(value);
|
|
165
|
-
const record = value;
|
|
166
|
-
records.push(record);
|
|
167
|
-
for (const key of ["error", "result", "cause", "data"]) {
|
|
168
|
-
collectErrorRecordsInto(record[key], records, seen);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
function firstStringField(records, fields) {
|
|
172
|
-
for (const record of records) {
|
|
173
|
-
for (const field of fields) {
|
|
174
|
-
const value = record[field];
|
|
175
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
176
|
-
return value;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
return void 0;
|
|
181
|
-
}
|
|
182
|
-
function firstBooleanField(records, fields) {
|
|
183
|
-
for (const record of records) {
|
|
184
|
-
for (const field of fields) {
|
|
185
|
-
const value = record[field];
|
|
186
|
-
if (typeof value === "boolean") return value;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return void 0;
|
|
190
|
-
}
|
|
191
|
-
function firstRecordField(records, fields) {
|
|
192
|
-
for (const record of records) {
|
|
193
|
-
for (const field of fields) {
|
|
194
|
-
const value = record[field];
|
|
195
|
-
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
196
|
-
return value;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
return void 0;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// src/nwc/normalize.ts
|
|
204
|
-
import {
|
|
205
|
-
OPENRECEIVE_MAX_AMOUNT_MSATS,
|
|
206
|
-
OPENRECEIVE_MIN_AMOUNT_MSATS,
|
|
207
|
-
OPENRECEIVE_NWC_METADATA_MAX_BYTES,
|
|
208
|
-
nonEmptyString,
|
|
209
|
-
recordOrEmpty
|
|
210
|
-
} from "@openreceive/core";
|
|
211
|
-
|
|
212
|
-
// src/hex.ts
|
|
213
|
-
var HEX_64 = /^[0-9a-fA-F]{64}$/;
|
|
214
|
-
|
|
215
|
-
// src/nwc/normalize.ts
|
|
216
|
-
var REQUIRED_RECEIVE_METHODS = ["make_invoice", "list_transactions"];
|
|
217
|
-
var SPEND_METHODS = [
|
|
218
|
-
"pay_invoice",
|
|
219
|
-
"multi_pay_invoice",
|
|
220
|
-
"pay_keysend",
|
|
221
|
-
"multi_pay_keysend"
|
|
222
|
-
];
|
|
223
|
-
function summarizeWalletCapabilities(connection, rawInfo, rawServiceInfo) {
|
|
224
|
-
const unwrappedInfo = unwrapNwcResult(rawInfo);
|
|
225
|
-
const info = recordOrEmpty(unwrappedInfo);
|
|
226
|
-
const serviceInfo = rawServiceInfo === void 0 ? void 0 : recordOrEmpty(unwrapNwcResult(rawServiceInfo));
|
|
227
|
-
const methods = normalizeStringList(
|
|
228
|
-
info.methods ?? info.capabilities ?? info.supported_methods ?? info.supportedMethods ?? (typeof unwrappedInfo === "string" ? unwrappedInfo : void 0)
|
|
229
|
-
).map(normalizeNwcMethodName);
|
|
230
|
-
const encryption = chooseEncryptionMode(
|
|
231
|
-
normalizeStringList(
|
|
232
|
-
serviceInfo?.encryption ?? serviceInfo?.encryptions ?? info.encryption ?? info.encryptions
|
|
233
|
-
)
|
|
234
|
-
);
|
|
235
|
-
const spendMethods = spendMethodsIn(methods);
|
|
236
|
-
const missingMethods = REQUIRED_RECEIVE_METHODS.filter((method) => !methods.includes(method));
|
|
237
|
-
const warnings = spendMethods.map(
|
|
238
|
-
(method) => `Wallet advertises spend method '${method}'; OpenReceive checkout will not expose it.`
|
|
239
|
-
);
|
|
240
|
-
return {
|
|
241
|
-
walletPubkey: connection.walletPubkey,
|
|
242
|
-
relays: [...connection.relays],
|
|
243
|
-
methods,
|
|
244
|
-
encryption,
|
|
245
|
-
spendCapabilityAdvertised: spendMethods.length > 0,
|
|
246
|
-
receiveCheckoutReady: missingMethods.length === 0,
|
|
247
|
-
warnings
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
function spendMethodsIn(methods) {
|
|
251
|
-
return methods.filter(
|
|
252
|
-
(method) => SPEND_METHODS.includes(method)
|
|
253
|
-
);
|
|
254
|
-
}
|
|
255
|
-
function toNip47MakeInvoiceParams(request) {
|
|
256
|
-
const params = {
|
|
257
|
-
amount: toSafeNumber(request.amount_msats, "amount_msats")
|
|
258
|
-
};
|
|
259
|
-
if (request.description !== void 0) params.description = request.description;
|
|
260
|
-
if (request.description_hash !== void 0) {
|
|
261
|
-
params.description_hash = request.description_hash;
|
|
262
|
-
}
|
|
263
|
-
if (request.expiry !== void 0) params.expiry = request.expiry;
|
|
264
|
-
if (request.metadata !== void 0) params.metadata = request.metadata;
|
|
265
|
-
return params;
|
|
266
|
-
}
|
|
267
|
-
function toNip47ListTransactionsParams(request) {
|
|
268
|
-
const params = {};
|
|
269
|
-
if (request.from !== void 0) params.from = request.from;
|
|
270
|
-
if (request.until !== void 0) params.until = request.until;
|
|
271
|
-
if (request.limit !== void 0) params.limit = request.limit;
|
|
272
|
-
if (request.offset !== void 0) params.offset = request.offset;
|
|
273
|
-
if (request.unpaid !== void 0) params.unpaid = request.unpaid;
|
|
274
|
-
if (request.type !== void 0) params.type = request.type;
|
|
275
|
-
return params;
|
|
276
|
-
}
|
|
277
|
-
function validateMakeInvoiceRequest(request) {
|
|
278
|
-
if (request.amount_msats < OPENRECEIVE_MIN_AMOUNT_MSATS) {
|
|
279
|
-
throw new ReceiveCheckoutValidationError("amount_msats must be at least 1000");
|
|
280
|
-
}
|
|
281
|
-
if (request.amount_msats > OPENRECEIVE_MAX_AMOUNT_MSATS) {
|
|
282
|
-
throw new ReceiveCheckoutValidationError("amount_msats exceeds JSON safe integer boundary");
|
|
283
|
-
}
|
|
284
|
-
if (request.description !== void 0 && request.description_hash !== void 0) {
|
|
285
|
-
throw new ReceiveCheckoutValidationError(
|
|
286
|
-
"At most one of description or description_hash may be present"
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
if (request.description_hash !== void 0 && !HEX_64.test(request.description_hash)) {
|
|
290
|
-
throw new ReceiveCheckoutValidationError("description_hash must be 64 hex characters");
|
|
291
|
-
}
|
|
292
|
-
if (request.metadata !== void 0) {
|
|
293
|
-
const metadataBytes = byteLength(JSON.stringify(request.metadata));
|
|
294
|
-
if (metadataBytes > OPENRECEIVE_NWC_METADATA_MAX_BYTES) {
|
|
295
|
-
throw new ReceiveCheckoutValidationError(
|
|
296
|
-
`metadata must serialize below ${OPENRECEIVE_NWC_METADATA_MAX_BYTES} bytes`
|
|
297
|
-
);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
function validateListTransactionsRequest(request) {
|
|
302
|
-
validateOptionalNonNegativeInteger(request.from, "from");
|
|
303
|
-
validateOptionalNonNegativeInteger(request.until, "until");
|
|
304
|
-
validateOptionalNonNegativeInteger(request.offset, "offset");
|
|
305
|
-
if (request.limit !== void 0 && (!Number.isSafeInteger(request.limit) || request.limit <= 0)) {
|
|
306
|
-
throw new ReceiveCheckoutValidationError("limit must be a positive safe integer");
|
|
307
|
-
}
|
|
308
|
-
if (request.from !== void 0 && request.until !== void 0 && request.from > request.until) {
|
|
309
|
-
throw new ReceiveCheckoutValidationError("from must be less than or equal to until");
|
|
310
|
-
}
|
|
311
|
-
if (request.type !== void 0 && request.type !== "incoming" && request.type !== "outgoing") {
|
|
312
|
-
throw new ReceiveCheckoutValidationError("type must be incoming or outgoing");
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
function validateOptionalNonNegativeInteger(value, field) {
|
|
316
|
-
if (value === void 0) return;
|
|
317
|
-
if (!Number.isSafeInteger(value) || value < 0) {
|
|
318
|
-
throw new ReceiveCheckoutValidationError(`${field} must be a non-negative safe integer`);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
function normalizeMakeInvoiceResult(rawResult) {
|
|
322
|
-
const result = recordOrEmpty(unwrapNwcResult(rawResult));
|
|
323
|
-
const invoice = requiredString(result.invoice, "invoice");
|
|
324
|
-
const paymentHash = requiredString(result.payment_hash ?? result.paymentHash, "payment_hash");
|
|
325
|
-
if (!HEX_64.test(paymentHash)) {
|
|
326
|
-
throw new TypeError("payment_hash must be 64 hex characters");
|
|
327
|
-
}
|
|
328
|
-
const amountMsats = toBigInt(result.amount_msats ?? result.amount, "amount_msats");
|
|
329
|
-
const createdAt = parseOptionalInteger(result.created_at ?? result.createdAt, "created_at");
|
|
330
|
-
const expiresAt = parseOptionalInteger(result.expires_at ?? result.expiresAt, "expires_at");
|
|
331
|
-
return {
|
|
332
|
-
invoice,
|
|
333
|
-
payment_hash: paymentHash,
|
|
334
|
-
amount_msats: amountMsats,
|
|
335
|
-
...createdAt === void 0 ? {} : { created_at: createdAt },
|
|
336
|
-
...expiresAt === void 0 ? {} : { expires_at: expiresAt }
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
function normalizeListTransactionsResult(rawResult) {
|
|
340
|
-
const unwrapped = unwrapNwcResult(rawResult);
|
|
341
|
-
const result = recordOrEmpty(unwrapped);
|
|
342
|
-
let rawTransactions;
|
|
343
|
-
if (Array.isArray(result.transactions)) {
|
|
344
|
-
rawTransactions = result.transactions;
|
|
345
|
-
} else if (Array.isArray(unwrapped)) {
|
|
346
|
-
rawTransactions = unwrapped;
|
|
347
|
-
} else if ((unwrapped === void 0 || unwrapped === null || typeof unwrapped === "object") && Object.keys(result).length === 0) {
|
|
348
|
-
rawTransactions = [];
|
|
349
|
-
} else {
|
|
350
|
-
throw new TypeError("list_transactions returned an unrecognized result shape");
|
|
351
|
-
}
|
|
352
|
-
const transactions = [];
|
|
353
|
-
let skippedRows = 0;
|
|
354
|
-
for (const rawTransaction of rawTransactions) {
|
|
355
|
-
try {
|
|
356
|
-
transactions.push(normalizeNwcTransaction(rawTransaction));
|
|
357
|
-
} catch {
|
|
358
|
-
skippedRows += 1;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
if (transactions.length === 0 && skippedRows > 0) {
|
|
362
|
-
throw new TypeError("list_transactions returned no usable rows");
|
|
363
|
-
}
|
|
364
|
-
return { transactions, skippedRows };
|
|
365
|
-
}
|
|
366
|
-
function normalizeNwcTransaction(rawTransaction) {
|
|
367
|
-
const result = recordOrEmpty(rawTransaction);
|
|
368
|
-
const normalized = {};
|
|
369
|
-
const type = normalizeTransactionType(result.type);
|
|
370
|
-
if (type !== void 0) normalized.type = type;
|
|
371
|
-
const invoice = nonEmptyString(result.invoice);
|
|
372
|
-
if (invoice !== void 0) normalized.invoice = invoice;
|
|
373
|
-
const paymentHash = nonEmptyString(result.payment_hash ?? result.paymentHash);
|
|
374
|
-
if (paymentHash !== void 0) {
|
|
375
|
-
if (!/^[0-9a-fA-F]{64}$/.test(paymentHash)) {
|
|
376
|
-
throw new TypeError("payment_hash must be 64 hexadecimal characters");
|
|
377
|
-
}
|
|
378
|
-
normalized.payment_hash = paymentHash;
|
|
379
|
-
}
|
|
380
|
-
if (result.amount_msats !== void 0 || result.amount !== void 0) {
|
|
381
|
-
normalized.amount_msats = toBigInt(result.amount_msats ?? result.amount, "amount_msats");
|
|
382
|
-
}
|
|
383
|
-
const transactionState = normalizeTransactionState(
|
|
384
|
-
result.transaction_state ?? result.transactionState ?? result.state
|
|
385
|
-
) ?? (result.settled === true || result.paid === true ? "settled" : void 0);
|
|
386
|
-
if (transactionState !== void 0) {
|
|
387
|
-
normalized.transaction_state = transactionState;
|
|
388
|
-
}
|
|
389
|
-
const createdAt = optionalUnixSeconds(result.created_at ?? result.createdAt, "created_at");
|
|
390
|
-
if (createdAt !== void 0) normalized.created_at = createdAt;
|
|
391
|
-
const expiresAt = optionalUnixSeconds(result.expires_at ?? result.expiresAt, "expires_at");
|
|
392
|
-
if (expiresAt !== void 0) normalized.expires_at = expiresAt;
|
|
393
|
-
const settledAt = optionalUnixSeconds(result.settled_at ?? result.settledAt, "settled_at");
|
|
394
|
-
if (settledAt !== void 0) normalized.settled_at = settledAt;
|
|
395
|
-
const preimage = nonEmptyString(result.preimage);
|
|
396
|
-
if (preimage !== void 0) normalized.preimage = preimage;
|
|
397
|
-
const description = nonEmptyString(result.description);
|
|
398
|
-
if (description !== void 0) normalized.description = description;
|
|
399
|
-
const descriptionHash = nonEmptyString(result.description_hash ?? result.descriptionHash);
|
|
400
|
-
if (descriptionHash !== void 0) normalized.description_hash = descriptionHash;
|
|
401
|
-
if (result.fees_paid !== void 0 || result.feesPaid !== void 0) {
|
|
402
|
-
normalized.fees_paid_msats = toBigInt(result.fees_paid ?? result.feesPaid, "fees_paid");
|
|
403
|
-
}
|
|
404
|
-
return normalized;
|
|
405
|
-
}
|
|
406
|
-
function normalizeNwcNotification(rawNotification) {
|
|
407
|
-
const record = recordOrEmpty(rawNotification);
|
|
408
|
-
const type = typeof record.notification_type === "string" ? record.notification_type : typeof record.notificationType === "string" ? record.notificationType : typeof record.type === "string" ? record.type : "unknown";
|
|
409
|
-
const payload = recordOrEmpty(record.notification);
|
|
410
|
-
let transaction;
|
|
411
|
-
if (Object.keys(payload).length > 0) {
|
|
412
|
-
try {
|
|
413
|
-
transaction = normalizeNwcTransaction(payload);
|
|
414
|
-
} catch {
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
const rawHash = payload.payment_hash ?? payload.paymentHash ?? record.payment_hash ?? record.paymentHash;
|
|
418
|
-
const paymentHash = typeof rawHash === "string" && rawHash.length > 0 ? rawHash : transaction?.payment_hash;
|
|
419
|
-
return {
|
|
420
|
-
type,
|
|
421
|
-
...paymentHash === void 0 ? {} : { payment_hash: paymentHash },
|
|
422
|
-
...transaction === void 0 ? {} : { transaction }
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
function chooseEncryptionMode(encryptionModes) {
|
|
426
|
-
const normalized = encryptionModes.map((mode) => mode.toLowerCase().replace(/[- ]/g, "_"));
|
|
427
|
-
if (normalized.includes("nip44_v2") || normalized.includes("nip44") || normalized.includes("nip_44")) {
|
|
428
|
-
return "nip44_v2";
|
|
429
|
-
}
|
|
430
|
-
if (normalized.length === 0 || normalized.includes("nip04") || normalized.includes("nip_04")) {
|
|
431
|
-
return "nip04";
|
|
432
|
-
}
|
|
433
|
-
return void 0;
|
|
434
|
-
}
|
|
435
|
-
function normalizeStringList(value) {
|
|
436
|
-
if (Array.isArray(value)) {
|
|
437
|
-
return value.filter((item) => typeof item === "string").map((item) => item.trim()).filter(Boolean);
|
|
438
|
-
}
|
|
439
|
-
if (typeof value === "string") {
|
|
440
|
-
return value.split(/[,\s]+/).map((item) => item.trim()).filter(Boolean);
|
|
441
|
-
}
|
|
442
|
-
return [];
|
|
443
|
-
}
|
|
444
|
-
function normalizeNwcMethodName(value) {
|
|
445
|
-
return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").toLowerCase();
|
|
446
|
-
}
|
|
447
|
-
function normalizeTransactionState(value) {
|
|
448
|
-
if (typeof value !== "string") return void 0;
|
|
449
|
-
const normalized = value.toLowerCase();
|
|
450
|
-
if (normalized === "pending" || normalized === "settled" || normalized === "expired" || normalized === "failed" || normalized === "accepted") {
|
|
451
|
-
return normalized;
|
|
452
|
-
}
|
|
453
|
-
return void 0;
|
|
454
|
-
}
|
|
455
|
-
function normalizeTransactionType(value) {
|
|
456
|
-
if (typeof value !== "string") return void 0;
|
|
457
|
-
const normalized = value.toLowerCase();
|
|
458
|
-
if (normalized === "incoming" || normalized === "outgoing") return normalized;
|
|
459
|
-
return void 0;
|
|
460
|
-
}
|
|
461
|
-
function parseOptionalInteger(value, fieldName) {
|
|
462
|
-
if (value === void 0 || value === null) return void 0;
|
|
463
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
464
|
-
throw new TypeError(`${fieldName} must be a non-negative safe integer`);
|
|
465
|
-
}
|
|
466
|
-
return value;
|
|
467
|
-
}
|
|
468
|
-
function optionalUnixSeconds(value, fieldName) {
|
|
469
|
-
if (value === void 0 || value === null) return void 0;
|
|
470
|
-
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
471
|
-
throw new TypeError(`${fieldName} must be a non-negative number of Unix seconds`);
|
|
472
|
-
}
|
|
473
|
-
return Math.floor(value);
|
|
474
|
-
}
|
|
475
|
-
function requiredString(value, fieldName) {
|
|
476
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
477
|
-
throw new TypeError(`${fieldName} must be a non-empty string`);
|
|
478
|
-
}
|
|
479
|
-
return value;
|
|
480
|
-
}
|
|
481
|
-
function toBigInt(value, fieldName) {
|
|
482
|
-
if (typeof value === "bigint") return value;
|
|
483
|
-
if (typeof value === "number" && Number.isSafeInteger(value)) {
|
|
484
|
-
return BigInt(value);
|
|
485
|
-
}
|
|
486
|
-
if (typeof value === "string" && /^[0-9]+$/.test(value)) {
|
|
487
|
-
return BigInt(value);
|
|
488
|
-
}
|
|
489
|
-
throw new TypeError(`${fieldName} must be an integer`);
|
|
490
|
-
}
|
|
491
|
-
function toSafeNumber(value, fieldName) {
|
|
492
|
-
if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
493
|
-
throw new ReceiveCheckoutValidationError(`${fieldName} exceeds JSON safe integer boundary`);
|
|
494
|
-
}
|
|
495
|
-
return Number(value);
|
|
496
|
-
}
|
|
497
|
-
function byteLength(value) {
|
|
498
|
-
return new TextEncoder().encode(value).length;
|
|
499
|
-
}
|
|
500
|
-
function unwrapNwcResult(value) {
|
|
501
|
-
const record = recordOrEmpty(value);
|
|
502
|
-
return record.result ?? value;
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// src/nwc/transport.ts
|
|
506
|
-
import { createRequire } from "module";
|
|
507
|
-
import { pathToFileURL } from "url";
|
|
508
|
-
import { recordOrEmpty as recordOrEmpty2 } from "@openreceive/core";
|
|
509
|
-
var require2 = createRequire(import.meta.url);
|
|
510
|
-
async function callRequiredMethod(client, names, request) {
|
|
511
|
-
for (const name of names) {
|
|
512
|
-
const method = client[name];
|
|
513
|
-
if (typeof method === "function") {
|
|
514
|
-
return await method.call(
|
|
515
|
-
client,
|
|
516
|
-
request
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
throw new WalletPreflightError(
|
|
521
|
-
"wallet_unavailable",
|
|
522
|
-
`NWC client does not expose ${names.join(" or ")}.`
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
async function createDefaultAlbyNwcClient(connectionString) {
|
|
526
|
-
ensureNodeWebSocket();
|
|
527
|
-
const dynamicImport = new Function("specifier", "return import(specifier)");
|
|
528
|
-
const namespace = recordOrEmpty2(
|
|
529
|
-
await dynamicImport(pathToFileURL(require2.resolve("@getalby/sdk/nwc")).href)
|
|
530
|
-
);
|
|
531
|
-
const Constructor = namespace.NWCClient;
|
|
532
|
-
if (typeof Constructor !== "function") {
|
|
533
|
-
throw new WalletPreflightError(
|
|
534
|
-
"wallet_unavailable",
|
|
535
|
-
"@getalby/sdk/nwc did not expose NWCClient."
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
const NWCClientConstructor = Constructor;
|
|
539
|
-
return new NWCClientConstructor({
|
|
540
|
-
nostrWalletConnectUrl: connectionString
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
function ensureNodeWebSocket() {
|
|
544
|
-
if (globalThis.WebSocket !== void 0) return;
|
|
545
|
-
throw new WalletPreflightError(
|
|
546
|
-
"wallet_unavailable",
|
|
547
|
-
"OpenReceive requires Node 22 or newer with the built-in WebSocket API."
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
async function closeNwcNotificationSubscription(subscription) {
|
|
551
|
-
if (typeof subscription !== "function") {
|
|
552
|
-
throw new TypeError("NWC subscribeNotifications must resolve to an unsubscribe function.");
|
|
553
|
-
}
|
|
554
|
-
await subscription();
|
|
555
|
-
}
|
|
556
|
-
function delay(milliseconds) {
|
|
557
|
-
return new Promise((resolve) => {
|
|
558
|
-
setTimeout(resolve, milliseconds);
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
// src/alby-nwc.ts
|
|
563
|
-
var AlbyNwcReceiveClient = class {
|
|
564
|
-
/**
|
|
565
|
-
* Safe, loggable view of the connection (wallet pubkey, relays, lud16, and
|
|
566
|
-
* the secret-redacted URI). The parsed connection with the client secret is
|
|
567
|
-
* deliberately private: `console.log(client)` or host error serialization
|
|
568
|
-
* must never leak the secret.
|
|
569
|
-
*/
|
|
570
|
-
connection;
|
|
571
|
-
#connection;
|
|
572
|
-
#connectionString;
|
|
573
|
-
#client;
|
|
574
|
-
#clientPromise;
|
|
575
|
-
#clientFactory;
|
|
576
|
-
#preflightSummary;
|
|
577
|
-
#preflightPromise;
|
|
578
|
-
#requirePreflight;
|
|
579
|
-
#logger;
|
|
580
|
-
#allowSpendCapableWallet;
|
|
581
|
-
#spendCapabilityWarningDelayMs;
|
|
582
|
-
#spendCapabilityWarning;
|
|
583
|
-
#closed = false;
|
|
584
|
-
constructor(options) {
|
|
585
|
-
this.#connectionString = options.connectionString;
|
|
586
|
-
this.#connection = parseNwcUri(options.connectionString);
|
|
587
|
-
this.connection = {
|
|
588
|
-
walletPubkey: this.#connection.walletPubkey,
|
|
589
|
-
relays: [...this.#connection.relays],
|
|
590
|
-
...this.#connection.lud16 === void 0 ? {} : { lud16: this.#connection.lud16 },
|
|
591
|
-
redacted: this.#connection.redacted
|
|
592
|
-
};
|
|
593
|
-
this.#client = options.client;
|
|
594
|
-
this.#clientFactory = options.clientFactory;
|
|
595
|
-
this.#requirePreflight = options.requirePreflight ?? true;
|
|
596
|
-
this.#logger = options.logger;
|
|
597
|
-
this.#allowSpendCapableWallet = options.allowSpendCapableWallet ?? false;
|
|
598
|
-
this.#spendCapabilityWarningDelayMs = options.spendCapabilityWarningDelayMs ?? 0;
|
|
599
|
-
this.#spendCapabilityWarning = options.spendCapabilityWarning ?? ((message) => {
|
|
600
|
-
if (this.#logger === void 0) console.error(message);
|
|
601
|
-
else this.#log("error", "nwc.spend_capability_warning", message);
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
#log(level, event, message, fields = {}) {
|
|
605
|
-
if (this.#logger === void 0) return;
|
|
606
|
-
try {
|
|
607
|
-
this.#logger({
|
|
608
|
-
level,
|
|
609
|
-
event,
|
|
610
|
-
message,
|
|
611
|
-
wallet_pubkey: this.connection.walletPubkey,
|
|
612
|
-
...fields
|
|
613
|
-
});
|
|
614
|
-
} catch {
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
async preflight() {
|
|
618
|
-
const client = await this.getClient();
|
|
619
|
-
const hasGetInfo = typeof client.getInfo === "function" || typeof client.get_info === "function";
|
|
620
|
-
const hasInfoEvent = typeof client.getWalletServiceInfo === "function";
|
|
621
|
-
const rawInfo = hasGetInfo || !hasInfoEvent ? await this.#fetchWalletInfo(
|
|
622
|
-
"get_info",
|
|
623
|
-
() => callRequiredMethod(client, ["getInfo", "get_info"], {})
|
|
624
|
-
) : void 0;
|
|
625
|
-
const rawServiceInfo = hasInfoEvent ? await this.#fetchWalletInfo(
|
|
626
|
-
"info_event",
|
|
627
|
-
() => client.getWalletServiceInfo()
|
|
628
|
-
) : void 0;
|
|
629
|
-
if (rawInfo === void 0) {
|
|
630
|
-
this.#log(
|
|
631
|
-
"warn",
|
|
632
|
-
"nwc.info_event.methods_fallback",
|
|
633
|
-
"NWC client exposes no get_info; proving receive-only from the service-wide info event (kind 13194) rather than this connection's own method list."
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
const summary = summarizeWalletCapabilities(
|
|
637
|
-
this.#connection,
|
|
638
|
-
rawInfo ?? rawServiceInfo,
|
|
639
|
-
rawServiceInfo
|
|
640
|
-
);
|
|
641
|
-
this.#log("debug", "nwc.preflight.summarized", "NWC wallet capabilities summarized.", {
|
|
642
|
-
methods_source: rawInfo === void 0 ? "info_event" : "get_info",
|
|
643
|
-
methods: summary.methods,
|
|
644
|
-
encryption: summary.encryption,
|
|
645
|
-
receive_checkout_ready: summary.receiveCheckoutReady,
|
|
646
|
-
spend_capability_advertised: summary.spendCapabilityAdvertised
|
|
647
|
-
});
|
|
648
|
-
if (!summary.receiveCheckoutReady) {
|
|
649
|
-
throw new WalletPreflightError(
|
|
650
|
-
"missing_required_method",
|
|
651
|
-
"NWC wallet must advertise make_invoice and list_transactions for receive checkout.",
|
|
652
|
-
summary
|
|
653
|
-
);
|
|
654
|
-
}
|
|
655
|
-
if (summary.encryption !== "nip04" && summary.encryption !== "nip44_v2") {
|
|
656
|
-
throw new WalletPreflightError(
|
|
657
|
-
"unsupported_encryption",
|
|
658
|
-
"NWC wallet must support NIP-04 or NIP-44 v2 encryption.",
|
|
659
|
-
summary
|
|
660
|
-
);
|
|
661
|
-
}
|
|
662
|
-
if (summary.spendCapabilityAdvertised) {
|
|
663
|
-
const spendMethods = spendMethodsIn(summary.methods);
|
|
664
|
-
if (!this.#allowSpendCapableWallet) {
|
|
665
|
-
this.#log(
|
|
666
|
-
"error",
|
|
667
|
-
"nwc.spend_capability_advertised",
|
|
668
|
-
"NWC connection advertises send-payment methods; refusing to boot without an explicit override.",
|
|
669
|
-
{ methods: spendMethods }
|
|
670
|
-
);
|
|
671
|
-
throw new WalletPreflightError(
|
|
672
|
-
"spend_capability_advertised",
|
|
673
|
-
formatSpendCapabilityRefusedMessage({ spendMethods }),
|
|
674
|
-
summary
|
|
675
|
-
);
|
|
676
|
-
}
|
|
677
|
-
const warning = formatSpendCapabilityWarningMessage({
|
|
678
|
-
spendMethods
|
|
679
|
-
});
|
|
680
|
-
this.#log(
|
|
681
|
-
"error",
|
|
682
|
-
"nwc.spend_capability_advertised",
|
|
683
|
-
"NWC connection advertises send-payment methods; continuing because the spend-capable override is set.",
|
|
684
|
-
{ methods: spendMethods }
|
|
685
|
-
);
|
|
686
|
-
try {
|
|
687
|
-
this.#spendCapabilityWarning(warning);
|
|
688
|
-
} catch {
|
|
689
|
-
}
|
|
690
|
-
if (this.#spendCapabilityWarningDelayMs > 0) {
|
|
691
|
-
await delay(this.#spendCapabilityWarningDelayMs);
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
this.#preflightSummary = summary;
|
|
695
|
-
return summary;
|
|
696
|
-
}
|
|
697
|
-
async makeInvoice(request) {
|
|
698
|
-
await this.ensurePreflight();
|
|
699
|
-
validateMakeInvoiceRequest(request);
|
|
700
|
-
this.#log(
|
|
701
|
-
"debug",
|
|
702
|
-
"nwc.make_invoice.requested",
|
|
703
|
-
"Calling NWC wallet make_invoice.",
|
|
704
|
-
compact({
|
|
705
|
-
method: "make_invoice",
|
|
706
|
-
// String form: msats above 2^53 would silently round through Number().
|
|
707
|
-
amount_msats: request.amount_msats.toString(),
|
|
708
|
-
expiry: request.expiry,
|
|
709
|
-
has_description: request.description !== void 0,
|
|
710
|
-
has_description_hash: request.description_hash !== void 0
|
|
711
|
-
})
|
|
712
|
-
);
|
|
713
|
-
const startedAt = Date.now();
|
|
714
|
-
let rawResult;
|
|
715
|
-
try {
|
|
716
|
-
rawResult = await callRequiredMethod(
|
|
717
|
-
await this.getClient(),
|
|
718
|
-
["makeInvoice", "make_invoice"],
|
|
719
|
-
toNip47MakeInvoiceParams(request)
|
|
720
|
-
);
|
|
721
|
-
} catch (error) {
|
|
722
|
-
const normalized = normalizeNwcWalletError(error);
|
|
723
|
-
this.#log("error", "nwc.make_invoice.failed", "NWC wallet make_invoice failed.", {
|
|
724
|
-
method: "make_invoice",
|
|
725
|
-
duration_ms: Date.now() - startedAt,
|
|
726
|
-
error_code: normalized.code,
|
|
727
|
-
error_message: normalized.message
|
|
728
|
-
});
|
|
729
|
-
throw normalized;
|
|
730
|
-
}
|
|
731
|
-
let result;
|
|
732
|
-
try {
|
|
733
|
-
result = normalizeMakeInvoiceResult(rawResult);
|
|
734
|
-
} catch (error) {
|
|
735
|
-
const normalized = normalizeNwcWalletError(error);
|
|
736
|
-
this.#log("error", "nwc.make_invoice.failed", "NWC wallet make_invoice failed.", {
|
|
737
|
-
method: "make_invoice",
|
|
738
|
-
duration_ms: Date.now() - startedAt,
|
|
739
|
-
error_code: normalized.code,
|
|
740
|
-
error_message: normalized.message
|
|
741
|
-
});
|
|
742
|
-
throw normalized;
|
|
743
|
-
}
|
|
744
|
-
this.#log("debug", "nwc.make_invoice.completed", "NWC wallet make_invoice completed.", {
|
|
745
|
-
method: "make_invoice",
|
|
746
|
-
duration_ms: Date.now() - startedAt,
|
|
747
|
-
payment_hash: result.payment_hash,
|
|
748
|
-
amount_msats: result.amount_msats.toString()
|
|
749
|
-
});
|
|
750
|
-
return result;
|
|
751
|
-
}
|
|
752
|
-
async listTransactions(request) {
|
|
753
|
-
await this.ensurePreflight();
|
|
754
|
-
validateListTransactionsRequest(request);
|
|
755
|
-
this.#log(
|
|
756
|
-
"debug",
|
|
757
|
-
"nwc.list_transactions.requested",
|
|
758
|
-
"Calling NWC wallet list_transactions.",
|
|
759
|
-
compact({
|
|
760
|
-
method: "list_transactions",
|
|
761
|
-
from: request.from,
|
|
762
|
-
until: request.until,
|
|
763
|
-
limit: request.limit,
|
|
764
|
-
offset: request.offset,
|
|
765
|
-
unpaid: request.unpaid,
|
|
766
|
-
type: request.type
|
|
767
|
-
})
|
|
768
|
-
);
|
|
769
|
-
const startedAt = Date.now();
|
|
770
|
-
let rawResult;
|
|
771
|
-
try {
|
|
772
|
-
rawResult = await callRequiredMethod(
|
|
773
|
-
await this.getClient(),
|
|
774
|
-
["listTransactions", "list_transactions"],
|
|
775
|
-
toNip47ListTransactionsParams(request)
|
|
776
|
-
);
|
|
777
|
-
} catch (error) {
|
|
778
|
-
const normalized = normalizeNwcWalletError(error);
|
|
779
|
-
this.#log("error", "nwc.list_transactions.failed", "NWC wallet list_transactions failed.", {
|
|
780
|
-
method: "list_transactions",
|
|
781
|
-
duration_ms: Date.now() - startedAt,
|
|
782
|
-
error_code: normalized.code,
|
|
783
|
-
error_message: normalized.message
|
|
784
|
-
});
|
|
785
|
-
throw normalized;
|
|
786
|
-
}
|
|
787
|
-
let result;
|
|
788
|
-
try {
|
|
789
|
-
result = normalizeListTransactionsResult(rawResult);
|
|
790
|
-
} catch (error) {
|
|
791
|
-
const normalized = normalizeNwcWalletError(error);
|
|
792
|
-
this.#log("error", "nwc.list_transactions.failed", "NWC wallet list_transactions failed.", {
|
|
793
|
-
method: "list_transactions",
|
|
794
|
-
duration_ms: Date.now() - startedAt,
|
|
795
|
-
error_code: normalized.code,
|
|
796
|
-
error_message: normalized.message
|
|
797
|
-
});
|
|
798
|
-
throw normalized;
|
|
799
|
-
}
|
|
800
|
-
if (result.skippedRows > 0) {
|
|
801
|
-
this.#log(
|
|
802
|
-
"warn",
|
|
803
|
-
"nwc.list_transactions.rows_skipped",
|
|
804
|
-
"Skipped wallet transaction rows that could not be normalized.",
|
|
805
|
-
{
|
|
806
|
-
method: "list_transactions",
|
|
807
|
-
skipped_count: result.skippedRows
|
|
808
|
-
}
|
|
809
|
-
);
|
|
810
|
-
}
|
|
811
|
-
this.#log(
|
|
812
|
-
"debug",
|
|
813
|
-
"nwc.list_transactions.completed",
|
|
814
|
-
"NWC wallet list_transactions completed.",
|
|
815
|
-
{
|
|
816
|
-
method: "list_transactions",
|
|
817
|
-
duration_ms: Date.now() - startedAt,
|
|
818
|
-
transaction_count: result.transactions.length
|
|
819
|
-
}
|
|
820
|
-
);
|
|
821
|
-
return {
|
|
822
|
-
transactions: result.transactions,
|
|
823
|
-
...result.skippedRows === 0 ? {} : { skippedRows: result.skippedRows }
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
/**
|
|
827
|
-
* Opt-in NWC-02 notification subscription, limited to `payment_received`.
|
|
828
|
-
* Notifications are authenticated wallet data: the handler receives the
|
|
829
|
-
* payload normalized like a `list_transactions` row so a payload satisfying
|
|
830
|
-
* the settlement rule can settle its pending attempt directly, while
|
|
831
|
-
* anything less only wakes reconciliation. Direct settlement assumes the
|
|
832
|
-
* client binds notification decryption to the connection's wallet pubkey
|
|
833
|
-
* (the bundled @getalby/sdk does). Resolves to an unsubscribe function.
|
|
834
|
-
* Throws an OpenReceiveError with code `UNSUPPORTED_METHOD` when the
|
|
835
|
-
* underlying wallet client does not support NWC notifications.
|
|
836
|
-
*/
|
|
837
|
-
async subscribeNotifications(handler) {
|
|
838
|
-
const client = await this.getClient();
|
|
839
|
-
const subscribe = client.subscribeNotifications;
|
|
840
|
-
if (typeof subscribe !== "function") {
|
|
841
|
-
throw new OpenReceiveError2({
|
|
842
|
-
code: "UNSUPPORTED_METHOD",
|
|
843
|
-
message: "NWC wallet client does not support NWC notifications (subscribeNotifications). Keep polling reconciliation.",
|
|
844
|
-
retryable: false
|
|
845
|
-
});
|
|
846
|
-
}
|
|
847
|
-
this.#log(
|
|
848
|
-
"debug",
|
|
849
|
-
"nwc.notifications.subscribe.requested",
|
|
850
|
-
"Subscribing to NWC-02 payment_received notifications.",
|
|
851
|
-
{ notification_types: ["payment_received"] }
|
|
852
|
-
);
|
|
853
|
-
let subscription;
|
|
854
|
-
try {
|
|
855
|
-
subscription = await subscribe.call(
|
|
856
|
-
client,
|
|
857
|
-
(rawNotification) => {
|
|
858
|
-
const notification = normalizeNwcNotification(rawNotification);
|
|
859
|
-
if (notification.type !== "payment_received") return;
|
|
860
|
-
this.#log(
|
|
861
|
-
"debug",
|
|
862
|
-
"nwc.notifications.received",
|
|
863
|
-
"NWC payment_received notification received.",
|
|
864
|
-
compact({
|
|
865
|
-
notification_type: notification.type,
|
|
866
|
-
payment_hash: notification.payment_hash
|
|
867
|
-
})
|
|
868
|
-
);
|
|
869
|
-
try {
|
|
870
|
-
handler(notification);
|
|
871
|
-
} catch {
|
|
872
|
-
}
|
|
873
|
-
},
|
|
874
|
-
["payment_received"]
|
|
875
|
-
);
|
|
876
|
-
} catch (error) {
|
|
877
|
-
const normalized = normalizeNwcWalletError(error);
|
|
878
|
-
this.#log(
|
|
879
|
-
"error",
|
|
880
|
-
"nwc.notifications.subscribe.failed",
|
|
881
|
-
"NWC notification subscription failed.",
|
|
882
|
-
{ error_code: normalized.code, error_message: normalized.message }
|
|
883
|
-
);
|
|
884
|
-
throw normalized;
|
|
885
|
-
}
|
|
886
|
-
this.#log(
|
|
887
|
-
"info",
|
|
888
|
-
"nwc.notifications.subscribe.completed",
|
|
889
|
-
"Subscribed to NWC-02 payment_received notifications; polling reconciliation remains the safety net."
|
|
890
|
-
);
|
|
891
|
-
return async () => {
|
|
892
|
-
await closeNwcNotificationSubscription(subscription);
|
|
893
|
-
this.#log(
|
|
894
|
-
"info",
|
|
895
|
-
"nwc.notifications.unsubscribed",
|
|
896
|
-
"Unsubscribed from NWC-02 wallet notifications."
|
|
897
|
-
);
|
|
898
|
-
};
|
|
899
|
-
}
|
|
900
|
-
async #fetchWalletInfo(source, call) {
|
|
901
|
-
const method = source === "get_info" ? "get_info" : "getWalletServiceInfo";
|
|
902
|
-
const label = source === "get_info" ? "get_info" : "info event (kind 13194)";
|
|
903
|
-
this.#log("debug", `nwc.${source}.requested`, `Fetching NWC wallet ${label}.`, { method });
|
|
904
|
-
const startedAt = Date.now();
|
|
905
|
-
try {
|
|
906
|
-
const raw = await call();
|
|
907
|
-
this.#log("debug", `nwc.${source}.completed`, `NWC wallet ${label} loaded.`, {
|
|
908
|
-
method,
|
|
909
|
-
duration_ms: Date.now() - startedAt
|
|
910
|
-
});
|
|
911
|
-
return raw;
|
|
912
|
-
} catch (error) {
|
|
913
|
-
const normalized = normalizeNwcWalletError(error);
|
|
914
|
-
this.#log("error", `nwc.${source}.failed`, `NWC wallet ${label} fetch failed.`, {
|
|
915
|
-
method,
|
|
916
|
-
duration_ms: Date.now() - startedAt,
|
|
917
|
-
error_code: normalized.code,
|
|
918
|
-
error_message: normalized.message
|
|
919
|
-
});
|
|
920
|
-
throw normalized;
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
async close() {
|
|
924
|
-
if (this.#closed) return;
|
|
925
|
-
this.#closed = true;
|
|
926
|
-
const client = this.#client ?? await this.#clientPromise?.catch(() => void 0);
|
|
927
|
-
this.#client = void 0;
|
|
928
|
-
this.#clientPromise = void 0;
|
|
929
|
-
await client?.close?.();
|
|
930
|
-
}
|
|
931
|
-
async ensurePreflight() {
|
|
932
|
-
if (!this.#requirePreflight || this.#preflightSummary !== void 0) return;
|
|
933
|
-
this.#preflightPromise ??= this.preflight();
|
|
934
|
-
try {
|
|
935
|
-
await this.#preflightPromise;
|
|
936
|
-
} catch (error) {
|
|
937
|
-
this.#preflightPromise = void 0;
|
|
938
|
-
throw error;
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
async getClient() {
|
|
942
|
-
if (this.#closed) {
|
|
943
|
-
throw new Error("OpenReceive NWC receive client is closed; create a new client.");
|
|
944
|
-
}
|
|
945
|
-
if (this.#client !== void 0) return this.#client;
|
|
946
|
-
this.#clientPromise ??= (async () => {
|
|
947
|
-
const client = this.#clientFactory !== void 0 ? await this.#clientFactory(this.#connection) : await createDefaultAlbyNwcClient(this.#connectionString);
|
|
948
|
-
this.#client = client;
|
|
949
|
-
return client;
|
|
950
|
-
})();
|
|
951
|
-
try {
|
|
952
|
-
return await this.#clientPromise;
|
|
953
|
-
} catch (error) {
|
|
954
|
-
this.#clientPromise = void 0;
|
|
955
|
-
throw error;
|
|
956
|
-
}
|
|
957
|
-
}
|
|
958
|
-
};
|
|
959
|
-
function createNwcReceiveClient(options) {
|
|
960
|
-
return new AlbyNwcReceiveClient(options);
|
|
961
|
-
}
|
|
22
|
+
import { OpenReceiveError as OpenReceiveError2 } from "@openreceive/core";
|
|
962
23
|
|
|
963
24
|
// src/service.ts
|
|
964
25
|
import {
|
|
@@ -966,8 +27,8 @@ import {
|
|
|
966
27
|
formatMissingNwcMessage,
|
|
967
28
|
NwcUriParseError,
|
|
968
29
|
OPENRECEIVE_NWC_CODE_HELP_URL,
|
|
969
|
-
OpenReceiveError
|
|
970
|
-
parseNwcUri
|
|
30
|
+
OpenReceiveError,
|
|
31
|
+
parseNwcUri,
|
|
971
32
|
reconcilePaymentAttempts,
|
|
972
33
|
unixSeconds
|
|
973
34
|
} from "@openreceive/core";
|
|
@@ -990,10 +51,10 @@ var ConfigError = class extends Error {
|
|
|
990
51
|
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from "fs";
|
|
991
52
|
import { appendFile } from "fs/promises";
|
|
992
53
|
import path from "path";
|
|
993
|
-
import { compact as
|
|
54
|
+
import { compact as compact2 } from "@openreceive/core";
|
|
994
55
|
|
|
995
56
|
// src/console-logger.ts
|
|
996
|
-
import { compact
|
|
57
|
+
import { compact } from "@openreceive/core";
|
|
997
58
|
|
|
998
59
|
// src/log-level.ts
|
|
999
60
|
var LOG_LEVEL_ORDER = {
|
|
@@ -1045,7 +106,7 @@ function createConsoleLogger(options = {}) {
|
|
|
1045
106
|
event,
|
|
1046
107
|
message
|
|
1047
108
|
}),
|
|
1048
|
-
|
|
109
|
+
compact(fields)
|
|
1049
110
|
);
|
|
1050
111
|
};
|
|
1051
112
|
}
|
|
@@ -1066,7 +127,7 @@ function createAppConsoleLogger(options) {
|
|
|
1066
127
|
event,
|
|
1067
128
|
message
|
|
1068
129
|
}),
|
|
1069
|
-
|
|
130
|
+
compact(fields)
|
|
1070
131
|
);
|
|
1071
132
|
};
|
|
1072
133
|
}
|
|
@@ -1214,7 +275,7 @@ function attachLogging(options) {
|
|
|
1214
275
|
const fileLogger = createFileLoggerFromConfig(options.logging);
|
|
1215
276
|
const consoleEnabled = options.logging?.console ?? options.logger === void 0;
|
|
1216
277
|
const consoleLogger = consoleEnabled ? createConsoleLogger(
|
|
1217
|
-
|
|
278
|
+
compact2({
|
|
1218
279
|
prefix: options.logging?.prefix ?? "openreceive",
|
|
1219
280
|
minLevel: options.logging?.level
|
|
1220
281
|
})
|
|
@@ -1224,10 +285,10 @@ function attachLogging(options) {
|
|
|
1224
285
|
}
|
|
1225
286
|
|
|
1226
287
|
// src/service/checkouts.ts
|
|
1227
|
-
import { OPENRECEIVE_NWC_METADATA_MAX_BYTES
|
|
288
|
+
import { OPENRECEIVE_NWC_METADATA_MAX_BYTES } from "@openreceive/core";
|
|
1228
289
|
|
|
1229
290
|
// src/service/requests.ts
|
|
1230
|
-
import { isBitcoinAmountCurrency, isRecord as isRecord2, nonEmptyString
|
|
291
|
+
import { isBitcoinAmountCurrency, isRecord as isRecord2, nonEmptyString } from "@openreceive/core";
|
|
1231
292
|
|
|
1232
293
|
// src/service/core-utils.ts
|
|
1233
294
|
import { isRecord } from "@openreceive/core";
|
|
@@ -1290,14 +351,14 @@ function normalizeCreateCheckoutRequest(input) {
|
|
|
1290
351
|
throw serviceError(400, "INVALID_REQUEST", `Unexpected create checkout field: ${key}.`);
|
|
1291
352
|
}
|
|
1292
353
|
}
|
|
1293
|
-
const reference =
|
|
354
|
+
const reference = nonEmptyString(body.reference);
|
|
1294
355
|
if (reference === void 0) throw serviceError(400, "INVALID_REQUEST", "reference is required.");
|
|
1295
356
|
if (reference.length > 200) {
|
|
1296
357
|
throw serviceError(400, "INVALID_REQUEST", "reference must be 200 characters or fewer.");
|
|
1297
358
|
}
|
|
1298
359
|
const amount = normalizeCreateCheckoutAmount(body.amount);
|
|
1299
|
-
const memo =
|
|
1300
|
-
const descriptionHash =
|
|
360
|
+
const memo = nonEmptyString(body.memo);
|
|
361
|
+
const descriptionHash = nonEmptyString(body.descriptionHash);
|
|
1301
362
|
getCreateDescriptionFields({ memo, descriptionHash });
|
|
1302
363
|
const metadata = parseOptionalRecord(body.metadata, "metadata");
|
|
1303
364
|
const expirySeconds = body.expirySeconds;
|
|
@@ -1324,8 +385,8 @@ function normalizeCreateCheckoutAmount(value) {
|
|
|
1324
385
|
if (keys.some((key) => key !== "currency" && key !== "value")) {
|
|
1325
386
|
throw serviceError(400, "INVALID_REQUEST", "amount contains unsupported fields.");
|
|
1326
387
|
}
|
|
1327
|
-
const currency =
|
|
1328
|
-
const amountValue =
|
|
388
|
+
const currency = nonEmptyString(value.currency);
|
|
389
|
+
const amountValue = nonEmptyString(value.value);
|
|
1329
390
|
if (currency === void 0 || amountValue === void 0) {
|
|
1330
391
|
throw serviceError(400, "INVALID_REQUEST", "amount must be { sats } or { currency, value }.");
|
|
1331
392
|
}
|
|
@@ -1347,8 +408,8 @@ function normalizeSatsValue(value) {
|
|
|
1347
408
|
throw serviceError(400, "INVALID_REQUEST", "sats must be a positive integer.");
|
|
1348
409
|
}
|
|
1349
410
|
function getCreateDescriptionFields(input) {
|
|
1350
|
-
const memo =
|
|
1351
|
-
const descriptionHash =
|
|
411
|
+
const memo = nonEmptyString(input.memo);
|
|
412
|
+
const descriptionHash = nonEmptyString(input.descriptionHash);
|
|
1352
413
|
if (memo !== void 0 && memo.length > 500) {
|
|
1353
414
|
throw serviceError(400, "INVALID_REQUEST", "memo must be 500 characters or fewer.");
|
|
1354
415
|
}
|
|
@@ -1371,7 +432,7 @@ import {
|
|
|
1371
432
|
isResolvedPriceProvider,
|
|
1372
433
|
OPENRECEIVE_PRICE_FEED_FALLBACK_URL_ENV,
|
|
1373
434
|
OPENRECEIVE_PRICE_FEED_PRIMARY_URL_ENV,
|
|
1374
|
-
nonEmptyString as
|
|
435
|
+
nonEmptyString as nonEmptyString2,
|
|
1375
436
|
quoteBitcoinAmountToMsats,
|
|
1376
437
|
quoteFiatToMsatsWithPrice
|
|
1377
438
|
} from "@openreceive/core";
|
|
@@ -1578,8 +639,8 @@ function parseFiatAmount(value) {
|
|
|
1578
639
|
if (record === void 0) {
|
|
1579
640
|
throw serviceError(400, "INVALID_REQUEST", "fiat must be a JSON object.");
|
|
1580
641
|
}
|
|
1581
|
-
const currency =
|
|
1582
|
-
const amountValue =
|
|
642
|
+
const currency = nonEmptyString2(record.currency);
|
|
643
|
+
const amountValue = nonEmptyString2(record.value);
|
|
1583
644
|
if (currency === void 0 || !/^[A-Z]{3}$/.test(currency)) {
|
|
1584
645
|
throw serviceError(400, "INVALID_REQUEST", "fiat.currency must be an ISO 4217 uppercase code");
|
|
1585
646
|
}
|
|
@@ -1596,8 +657,8 @@ function parseBitcoinAmount(value) {
|
|
|
1596
657
|
if (record === void 0) {
|
|
1597
658
|
throw serviceError(400, "INVALID_REQUEST", "amount must be a JSON object.");
|
|
1598
659
|
}
|
|
1599
|
-
const currency =
|
|
1600
|
-
const amountValue =
|
|
660
|
+
const currency = nonEmptyString2(record.currency);
|
|
661
|
+
const amountValue = nonEmptyString2(record.value);
|
|
1601
662
|
if (currency === void 0 || !["BTC", "SAT", "SATS"].includes(currency)) {
|
|
1602
663
|
throw serviceError(
|
|
1603
664
|
400,
|
|
@@ -1643,7 +704,7 @@ async function createCheckout(context, request) {
|
|
|
1643
704
|
...input.metadata ?? {},
|
|
1644
705
|
reference: input.reference
|
|
1645
706
|
};
|
|
1646
|
-
if (Buffer.byteLength(JSON.stringify(metadata), "utf8") >
|
|
707
|
+
if (Buffer.byteLength(JSON.stringify(metadata), "utf8") > OPENRECEIVE_NWC_METADATA_MAX_BYTES) {
|
|
1647
708
|
throw serviceError(400, "INVALID_REQUEST", "metadata is too large for NIP-47.");
|
|
1648
709
|
}
|
|
1649
710
|
const walletInvoice = await context.options.client.makeInvoice({
|
|
@@ -1791,7 +852,7 @@ var OPENRECEIVE_SWAP_STATES = {
|
|
|
1791
852
|
};
|
|
1792
853
|
|
|
1793
854
|
// src/swap/weight-budget.ts
|
|
1794
|
-
import { compact as
|
|
855
|
+
import { compact as compact3 } from "@openreceive/core";
|
|
1795
856
|
var SWAP_PROVIDER_WEIGHT_WINDOW_SECONDS = 60;
|
|
1796
857
|
var SWAP_PROVIDER_WEIGHT_SOFT_CAP = 200;
|
|
1797
858
|
var SWAP_PROVIDER_CREATE_WEIGHT_GATE = 150;
|
|
@@ -1861,7 +922,7 @@ var SwapProviderWeightBudget = class {
|
|
|
1861
922
|
#deny(path2, reason, cost, gate, message) {
|
|
1862
923
|
throw weightBudgetError(
|
|
1863
924
|
message,
|
|
1864
|
-
|
|
925
|
+
compact3({
|
|
1865
926
|
provider: this.providerId,
|
|
1866
927
|
path: path2,
|
|
1867
928
|
reason,
|
|
@@ -2356,7 +1417,7 @@ async function createOpenReceive(supplied = {}) {
|
|
|
2356
1417
|
subscribeWalletNotifications: async (handler) => {
|
|
2357
1418
|
const subscribe = client.subscribeNotifications;
|
|
2358
1419
|
if (typeof subscribe !== "function") {
|
|
2359
|
-
throw new
|
|
1420
|
+
throw new OpenReceiveError({
|
|
2360
1421
|
code: "UNSUPPORTED_METHOD",
|
|
2361
1422
|
message: "NWC wallet client does not support NWC notifications (subscribeNotifications). Keep polling reconciliation.",
|
|
2362
1423
|
retryable: false
|
|
@@ -2426,7 +1487,7 @@ function requireNwc(value, { subject }) {
|
|
|
2426
1487
|
});
|
|
2427
1488
|
}
|
|
2428
1489
|
try {
|
|
2429
|
-
|
|
1490
|
+
parseNwcUri(value.trim());
|
|
2430
1491
|
} catch (error) {
|
|
2431
1492
|
const reason = error instanceof NwcUriParseError ? error.description : "Invalid NWC URI.";
|
|
2432
1493
|
throw new ConfigError({
|
|
@@ -2474,7 +1535,7 @@ import {
|
|
|
2474
1535
|
formatInvalidNwcMessage as formatInvalidNwcMessage2,
|
|
2475
1536
|
formatMissingNwcMessage as formatMissingNwcMessage2,
|
|
2476
1537
|
NwcUriParseError as NwcUriParseError2,
|
|
2477
|
-
parseNwcUri as
|
|
1538
|
+
parseNwcUri as parseNwcUri2
|
|
2478
1539
|
} from "@openreceive/core";
|
|
2479
1540
|
function readNwcFromEnvironment(options = {}) {
|
|
2480
1541
|
const subject = options.subject ?? "OpenReceive";
|
|
@@ -2483,7 +1544,7 @@ function readNwcFromEnvironment(options = {}) {
|
|
|
2483
1544
|
throw new Error(formatMissingNwcMessage2({ subject }));
|
|
2484
1545
|
}
|
|
2485
1546
|
try {
|
|
2486
|
-
|
|
1547
|
+
parseNwcUri2(value);
|
|
2487
1548
|
} catch (error) {
|
|
2488
1549
|
const reason = error instanceof NwcUriParseError2 ? error.description : "Invalid NWC URI.";
|
|
2489
1550
|
throw new Error(formatInvalidNwcMessage2({ reason }));
|
|
@@ -2494,7 +1555,7 @@ export {
|
|
|
2494
1555
|
ConfigError,
|
|
2495
1556
|
OPENRECEIVE_SWAP_PAY_IN_ASSETS,
|
|
2496
1557
|
OPENRECEIVE_SWAP_STATES,
|
|
2497
|
-
|
|
1558
|
+
OpenReceiveError2 as OpenReceiveError,
|
|
2498
1559
|
ReceiveCheckoutValidationError,
|
|
2499
1560
|
ServiceError,
|
|
2500
1561
|
WalletPreflightError,
|