bip-321 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +107 -0
- package/README.md +336 -0
- package/bun.lock +63 -0
- package/example.ts +161 -0
- package/index.test.ts +361 -0
- package/index.ts +471 -0
- package/package.json +65 -0
- package/tsconfig.json +29 -0
package/index.ts
ADDED
|
@@ -0,0 +1,471 @@
|
|
|
1
|
+
import * as bitcoin from "bitcoinjs-lib";
|
|
2
|
+
import { decode as decodeLightning } from "light-bolt11-decoder";
|
|
3
|
+
import bs58check from "bs58check";
|
|
4
|
+
import { bech32, bech32m } from "@scure/base";
|
|
5
|
+
|
|
6
|
+
export interface PaymentMethod {
|
|
7
|
+
type:
|
|
8
|
+
| "onchain"
|
|
9
|
+
| "lightning"
|
|
10
|
+
| "lno"
|
|
11
|
+
| "silent-payment"
|
|
12
|
+
| "private-payment"
|
|
13
|
+
| "other";
|
|
14
|
+
value: string;
|
|
15
|
+
network?: "mainnet" | "testnet" | "regtest" | "signet";
|
|
16
|
+
valid: boolean;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface BIP321ParseResult {
|
|
21
|
+
address?: string;
|
|
22
|
+
network?: "mainnet" | "testnet" | "regtest" | "signet";
|
|
23
|
+
amount?: number;
|
|
24
|
+
label?: string;
|
|
25
|
+
message?: string;
|
|
26
|
+
pop?: string;
|
|
27
|
+
popRequired?: boolean;
|
|
28
|
+
paymentMethods: PaymentMethod[];
|
|
29
|
+
requiredParams: string[];
|
|
30
|
+
optionalParams: Record<string, string[]>;
|
|
31
|
+
valid: boolean;
|
|
32
|
+
errors: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const FORBIDDEN_POP_SCHEMES = ["http", "https", "file", "javascript", "mailto"];
|
|
36
|
+
|
|
37
|
+
function detectAddressNetwork(
|
|
38
|
+
address: string,
|
|
39
|
+
): "mainnet" | "testnet" | "regtest" | "signet" | undefined {
|
|
40
|
+
try {
|
|
41
|
+
const lowerAddress = address.toLowerCase();
|
|
42
|
+
|
|
43
|
+
// Bech32/Bech32m addresses
|
|
44
|
+
if (lowerAddress.startsWith("bc1")) {
|
|
45
|
+
try {
|
|
46
|
+
// Try using bitcoinjs-lib first (works for non-taproot)
|
|
47
|
+
bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin);
|
|
48
|
+
return "mainnet";
|
|
49
|
+
} catch (e) {
|
|
50
|
+
// Fallback to manual bech32/bech32m validation for taproot
|
|
51
|
+
try {
|
|
52
|
+
const decoded = lowerAddress.startsWith("bc1p")
|
|
53
|
+
? bech32m.decode(address as `${string}1${string}`, 90)
|
|
54
|
+
: bech32.decode(address as `${string}1${string}`, 90);
|
|
55
|
+
if (decoded.prefix === "bc") {
|
|
56
|
+
return "mainnet";
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} else if (lowerAddress.startsWith("tb1")) {
|
|
63
|
+
try {
|
|
64
|
+
bitcoin.address.toOutputScript(address, bitcoin.networks.testnet);
|
|
65
|
+
return "testnet";
|
|
66
|
+
} catch (e) {
|
|
67
|
+
try {
|
|
68
|
+
const decoded = lowerAddress.startsWith("tb1p")
|
|
69
|
+
? bech32m.decode(address as `${string}1${string}`, 90)
|
|
70
|
+
: bech32.decode(address as `${string}1${string}`, 90);
|
|
71
|
+
if (decoded.prefix === "tb") {
|
|
72
|
+
return "testnet";
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
} else if (lowerAddress.startsWith("bcrt1")) {
|
|
79
|
+
try {
|
|
80
|
+
bitcoin.address.toOutputScript(address, bitcoin.networks.regtest);
|
|
81
|
+
return "regtest";
|
|
82
|
+
} catch (e) {
|
|
83
|
+
try {
|
|
84
|
+
const decoded = lowerAddress.startsWith("bcrt1p")
|
|
85
|
+
? bech32m.decode(address as `${string}1${string}`, 90)
|
|
86
|
+
: bech32.decode(address as `${string}1${string}`, 90);
|
|
87
|
+
if (decoded.prefix === "bcrt") {
|
|
88
|
+
return "regtest";
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Base58 addresses (P2PKH, P2SH)
|
|
97
|
+
const decoded = bs58check.decode(address);
|
|
98
|
+
const version = decoded[0];
|
|
99
|
+
|
|
100
|
+
// Mainnet: P2PKH (0x00), P2SH (0x05)
|
|
101
|
+
if (version === 0x00 || version === 0x05) {
|
|
102
|
+
return "mainnet";
|
|
103
|
+
}
|
|
104
|
+
// Testnet: P2PKH (0x6f), P2SH (0xc4)
|
|
105
|
+
else if (version === 0x6f || version === 0xc4) {
|
|
106
|
+
return "testnet";
|
|
107
|
+
}
|
|
108
|
+
} catch (e) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validateBitcoinAddress(address: string): {
|
|
115
|
+
valid: boolean;
|
|
116
|
+
network?: "mainnet" | "testnet" | "regtest" | "signet";
|
|
117
|
+
error?: string;
|
|
118
|
+
} {
|
|
119
|
+
if (!address) {
|
|
120
|
+
return { valid: false, error: "Empty address" };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const network = detectAddressNetwork(address);
|
|
124
|
+
if (!network) {
|
|
125
|
+
return { valid: false, error: "Invalid bitcoin address" };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { valid: true, network };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function validateLightningInvoice(invoice: string): {
|
|
132
|
+
valid: boolean;
|
|
133
|
+
network?: "mainnet" | "testnet" | "regtest" | "signet";
|
|
134
|
+
error?: string;
|
|
135
|
+
} {
|
|
136
|
+
try {
|
|
137
|
+
const decoded = decodeLightning(invoice);
|
|
138
|
+
let network: "mainnet" | "testnet" | "regtest" | "signet" | undefined;
|
|
139
|
+
|
|
140
|
+
const lowerInvoice = invoice.toLowerCase();
|
|
141
|
+
// Check order matters - lnbcrt before lnbc, lntbs before lntb
|
|
142
|
+
if (lowerInvoice.startsWith("lnbcrt")) {
|
|
143
|
+
network = "regtest";
|
|
144
|
+
} else if (lowerInvoice.startsWith("lnbc")) {
|
|
145
|
+
network = "mainnet";
|
|
146
|
+
} else if (lowerInvoice.startsWith("lntbs")) {
|
|
147
|
+
network = "signet";
|
|
148
|
+
} else if (
|
|
149
|
+
lowerInvoice.startsWith("lntb") ||
|
|
150
|
+
lowerInvoice.startsWith("lnbt")
|
|
151
|
+
) {
|
|
152
|
+
network = "testnet";
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return { valid: true, network };
|
|
156
|
+
} catch (e) {
|
|
157
|
+
return {
|
|
158
|
+
valid: false,
|
|
159
|
+
error: `Invalid lightning invoice: ${e instanceof Error ? e.message : String(e)}`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function validatePopUri(popUri: string): { valid: boolean; error?: string } {
|
|
165
|
+
try {
|
|
166
|
+
const decoded = decodeURIComponent(popUri);
|
|
167
|
+
const schemeMatch = decoded.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):?/);
|
|
168
|
+
|
|
169
|
+
if (schemeMatch && schemeMatch[1]) {
|
|
170
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
171
|
+
if (FORBIDDEN_POP_SCHEMES.includes(scheme)) {
|
|
172
|
+
return { valid: false, error: `Forbidden pop scheme: ${scheme}` };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { valid: true };
|
|
177
|
+
} catch (e) {
|
|
178
|
+
return { valid: false, error: "Invalid pop URI encoding" };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function parseBIP321(uri: string): BIP321ParseResult {
|
|
183
|
+
const result: BIP321ParseResult = {
|
|
184
|
+
paymentMethods: [],
|
|
185
|
+
requiredParams: [],
|
|
186
|
+
optionalParams: {},
|
|
187
|
+
valid: true,
|
|
188
|
+
errors: [],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (!uri || typeof uri !== "string") {
|
|
192
|
+
result.valid = false;
|
|
193
|
+
result.errors.push("Invalid URI: must be a non-empty string");
|
|
194
|
+
return result;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const lowerUri = uri.toLowerCase();
|
|
198
|
+
if (!lowerUri.startsWith("bitcoin:")) {
|
|
199
|
+
result.valid = false;
|
|
200
|
+
result.errors.push("Invalid URI: must start with bitcoin:");
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const withoutScheme = uri.substring(8);
|
|
205
|
+
const questionMarkIndex = withoutScheme.indexOf("?");
|
|
206
|
+
|
|
207
|
+
let address = "";
|
|
208
|
+
let queryString = "";
|
|
209
|
+
|
|
210
|
+
if (questionMarkIndex === -1) {
|
|
211
|
+
address = withoutScheme;
|
|
212
|
+
} else {
|
|
213
|
+
address = withoutScheme.substring(0, questionMarkIndex);
|
|
214
|
+
queryString = withoutScheme.substring(questionMarkIndex + 1);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (address) {
|
|
218
|
+
const validation = validateBitcoinAddress(address);
|
|
219
|
+
if (validation.valid) {
|
|
220
|
+
result.address = address;
|
|
221
|
+
result.network = validation.network;
|
|
222
|
+
result.paymentMethods.push({
|
|
223
|
+
type: "onchain",
|
|
224
|
+
value: address,
|
|
225
|
+
network: validation.network,
|
|
226
|
+
valid: true,
|
|
227
|
+
});
|
|
228
|
+
} else {
|
|
229
|
+
result.errors.push(`Invalid address: ${validation.error}`);
|
|
230
|
+
result.valid = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (queryString) {
|
|
235
|
+
// Parse manually to preserve encoded values for pop parameter
|
|
236
|
+
const paramPairs = queryString.split("&");
|
|
237
|
+
const seenKeys = new Map<string, number>();
|
|
238
|
+
|
|
239
|
+
for (const pair of paramPairs) {
|
|
240
|
+
const eqIndex = pair.indexOf("=");
|
|
241
|
+
let key: string;
|
|
242
|
+
let value: string;
|
|
243
|
+
|
|
244
|
+
if (eqIndex === -1) {
|
|
245
|
+
key = decodeURIComponent(pair);
|
|
246
|
+
value = "";
|
|
247
|
+
} else {
|
|
248
|
+
key = decodeURIComponent(pair.substring(0, eqIndex));
|
|
249
|
+
// For pop parameters, keep encoded; for others, decode
|
|
250
|
+
const rawValue = pair.substring(eqIndex + 1);
|
|
251
|
+
value = rawValue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const lowerKey = key.toLowerCase();
|
|
255
|
+
const count = (seenKeys.get(lowerKey) || 0) + 1;
|
|
256
|
+
seenKeys.set(lowerKey, count);
|
|
257
|
+
|
|
258
|
+
if (lowerKey === "label") {
|
|
259
|
+
if (count > 1) {
|
|
260
|
+
result.errors.push("Multiple label parameters not allowed");
|
|
261
|
+
result.valid = false;
|
|
262
|
+
} else {
|
|
263
|
+
result.label = decodeURIComponent(value);
|
|
264
|
+
}
|
|
265
|
+
} else if (lowerKey === "message") {
|
|
266
|
+
if (count > 1) {
|
|
267
|
+
result.errors.push("Multiple message parameters not allowed");
|
|
268
|
+
result.valid = false;
|
|
269
|
+
} else {
|
|
270
|
+
result.message = decodeURIComponent(value);
|
|
271
|
+
}
|
|
272
|
+
} else if (lowerKey === "amount") {
|
|
273
|
+
if (count > 1) {
|
|
274
|
+
result.errors.push("Multiple amount parameters not allowed");
|
|
275
|
+
result.valid = false;
|
|
276
|
+
} else {
|
|
277
|
+
const decodedValue = decodeURIComponent(value);
|
|
278
|
+
const amount = parseFloat(decodedValue);
|
|
279
|
+
if (isNaN(amount) || amount < 0 || decodedValue.includes(",")) {
|
|
280
|
+
result.errors.push("Invalid amount format");
|
|
281
|
+
result.valid = false;
|
|
282
|
+
} else {
|
|
283
|
+
result.amount = amount;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} else if (lowerKey === "pop" || lowerKey === "req-pop") {
|
|
287
|
+
const popKey = lowerKey === "req-pop" ? "req-pop" : "pop";
|
|
288
|
+
if (result.pop !== undefined || result.popRequired !== undefined) {
|
|
289
|
+
result.errors.push("Multiple pop/req-pop parameters not allowed");
|
|
290
|
+
result.valid = false;
|
|
291
|
+
} else {
|
|
292
|
+
// Keep pop value encoded as per spec
|
|
293
|
+
const validation = validatePopUri(value);
|
|
294
|
+
if (!validation.valid) {
|
|
295
|
+
result.errors.push(validation.error || "Invalid pop URI");
|
|
296
|
+
if (lowerKey === "req-pop") {
|
|
297
|
+
result.valid = false;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
result.pop = value;
|
|
301
|
+
result.popRequired = lowerKey === "req-pop";
|
|
302
|
+
}
|
|
303
|
+
} else if (lowerKey === "lightning") {
|
|
304
|
+
const decodedValue = decodeURIComponent(value);
|
|
305
|
+
const validation = validateLightningInvoice(decodedValue);
|
|
306
|
+
result.paymentMethods.push({
|
|
307
|
+
type: "lightning",
|
|
308
|
+
value: decodedValue,
|
|
309
|
+
network: validation.network,
|
|
310
|
+
valid: validation.valid,
|
|
311
|
+
error: validation.error,
|
|
312
|
+
});
|
|
313
|
+
if (!validation.valid) {
|
|
314
|
+
result.errors.push(validation.error || "Invalid lightning invoice");
|
|
315
|
+
}
|
|
316
|
+
} else if (lowerKey === "lno") {
|
|
317
|
+
const decodedValue = decodeURIComponent(value);
|
|
318
|
+
result.paymentMethods.push({
|
|
319
|
+
type: "lno",
|
|
320
|
+
value: decodedValue,
|
|
321
|
+
valid: true,
|
|
322
|
+
});
|
|
323
|
+
} else if (lowerKey === "sp") {
|
|
324
|
+
const decodedValue = decodeURIComponent(value);
|
|
325
|
+
const isSilentPayment = decodedValue.toLowerCase().startsWith("sp1");
|
|
326
|
+
result.paymentMethods.push({
|
|
327
|
+
type: "silent-payment",
|
|
328
|
+
value: decodedValue,
|
|
329
|
+
valid: isSilentPayment,
|
|
330
|
+
error: isSilentPayment
|
|
331
|
+
? undefined
|
|
332
|
+
: "Invalid silent payment address format",
|
|
333
|
+
});
|
|
334
|
+
if (!isSilentPayment) {
|
|
335
|
+
result.errors.push("Invalid silent payment address format");
|
|
336
|
+
}
|
|
337
|
+
} else if (lowerKey === "pay") {
|
|
338
|
+
const decodedValue = decodeURIComponent(value);
|
|
339
|
+
result.paymentMethods.push({
|
|
340
|
+
type: "private-payment",
|
|
341
|
+
value: decodedValue,
|
|
342
|
+
valid: true,
|
|
343
|
+
});
|
|
344
|
+
} else if (
|
|
345
|
+
lowerKey === "bc" ||
|
|
346
|
+
lowerKey === "tb" ||
|
|
347
|
+
lowerKey === "bcrt" ||
|
|
348
|
+
lowerKey === "tbs"
|
|
349
|
+
) {
|
|
350
|
+
let expectedNetwork: "mainnet" | "testnet" | "regtest" | "signet";
|
|
351
|
+
if (lowerKey === "bc") expectedNetwork = "mainnet";
|
|
352
|
+
else if (lowerKey === "tb") expectedNetwork = "testnet";
|
|
353
|
+
else if (lowerKey === "tbs") expectedNetwork = "signet";
|
|
354
|
+
else expectedNetwork = "regtest";
|
|
355
|
+
|
|
356
|
+
const decodedValue = decodeURIComponent(value);
|
|
357
|
+
const validation = validateBitcoinAddress(decodedValue);
|
|
358
|
+
const networkMatches =
|
|
359
|
+
validation.valid && validation.network === expectedNetwork;
|
|
360
|
+
|
|
361
|
+
result.paymentMethods.push({
|
|
362
|
+
type: "onchain",
|
|
363
|
+
value: decodedValue,
|
|
364
|
+
network: validation.network,
|
|
365
|
+
valid: validation.valid && networkMatches,
|
|
366
|
+
error: !validation.valid
|
|
367
|
+
? validation.error
|
|
368
|
+
: !networkMatches
|
|
369
|
+
? `Address network mismatch: expected ${expectedNetwork}`
|
|
370
|
+
: undefined,
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
if (!validation.valid || !networkMatches) {
|
|
374
|
+
result.errors.push(
|
|
375
|
+
!validation.valid
|
|
376
|
+
? validation.error!
|
|
377
|
+
: `Address network mismatch for ${lowerKey} parameter`,
|
|
378
|
+
);
|
|
379
|
+
result.valid = false;
|
|
380
|
+
}
|
|
381
|
+
} else if (lowerKey.startsWith("req-")) {
|
|
382
|
+
result.requiredParams.push(key);
|
|
383
|
+
result.errors.push(`Unknown required parameter: ${key}`);
|
|
384
|
+
result.valid = false;
|
|
385
|
+
} else {
|
|
386
|
+
if (!result.optionalParams[lowerKey]) {
|
|
387
|
+
result.optionalParams[lowerKey] = [];
|
|
388
|
+
}
|
|
389
|
+
result.optionalParams[lowerKey].push(decodeURIComponent(value));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (result.paymentMethods.length === 0) {
|
|
395
|
+
result.errors.push("No valid payment methods found");
|
|
396
|
+
result.valid = false;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (result.popRequired && result.pop) {
|
|
400
|
+
const hasValidPaymentMethod = result.paymentMethods.some((pm) => pm.valid);
|
|
401
|
+
if (!hasValidPaymentMethod) {
|
|
402
|
+
result.errors.push(
|
|
403
|
+
"req-pop specified but no valid payment method available",
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return result;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
export function getPaymentMethodsByNetwork(
|
|
412
|
+
result: BIP321ParseResult,
|
|
413
|
+
): Record<string, PaymentMethod[]> {
|
|
414
|
+
const byNetwork: Record<string, PaymentMethod[]> = {
|
|
415
|
+
mainnet: [],
|
|
416
|
+
testnet: [],
|
|
417
|
+
regtest: [],
|
|
418
|
+
signet: [],
|
|
419
|
+
unknown: [],
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
for (const method of result.paymentMethods) {
|
|
423
|
+
if (method.network && byNetwork[method.network]) {
|
|
424
|
+
byNetwork[method.network]!.push(method);
|
|
425
|
+
} else {
|
|
426
|
+
byNetwork.unknown!.push(method);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return byNetwork;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export function getValidPaymentMethods(
|
|
434
|
+
result: BIP321ParseResult,
|
|
435
|
+
): PaymentMethod[] {
|
|
436
|
+
return result.paymentMethods.filter((pm) => pm.valid);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function formatPaymentMethodsSummary(result: BIP321ParseResult): string {
|
|
440
|
+
const lines: string[] = [];
|
|
441
|
+
|
|
442
|
+
lines.push(`Valid: ${result.valid}`);
|
|
443
|
+
if (result.errors.length > 0) {
|
|
444
|
+
lines.push(`Errors: ${result.errors.join(", ")}`);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (result.address) {
|
|
448
|
+
lines.push(`Address: ${result.address} (${result.network})`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (result.amount !== undefined) {
|
|
452
|
+
lines.push(`Amount: ${result.amount} BTC`);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (result.label) {
|
|
456
|
+
lines.push(`Label: ${result.label}`);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if (result.message) {
|
|
460
|
+
lines.push(`Message: ${result.message}`);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
lines.push(`Payment Methods: ${result.paymentMethods.length}`);
|
|
464
|
+
for (const method of result.paymentMethods) {
|
|
465
|
+
const status = method.valid ? "✓" : "✗";
|
|
466
|
+
const network = method.network ? ` (${method.network})` : "";
|
|
467
|
+
lines.push(` ${status} ${method.type}${network}`);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
return lines.join("\n");
|
|
471
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bip-321",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A TypeScript/JavaScript library for parsing BIP-321 Bitcoin URI scheme with support for multiple payment methods",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"module": "./index.ts",
|
|
8
|
+
"types": "./index.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./index.ts",
|
|
12
|
+
"types": "./index.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "bun test",
|
|
17
|
+
"example": "bun example.ts"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"bitcoin",
|
|
21
|
+
"bip-321",
|
|
22
|
+
"bip321",
|
|
23
|
+
"uri",
|
|
24
|
+
"lightning",
|
|
25
|
+
"bolt11",
|
|
26
|
+
"bolt12",
|
|
27
|
+
"silent-payments",
|
|
28
|
+
"payment",
|
|
29
|
+
"cryptocurrency",
|
|
30
|
+
"parser",
|
|
31
|
+
"validator",
|
|
32
|
+
"bitcoin-uri",
|
|
33
|
+
"payment-request",
|
|
34
|
+
"taproot",
|
|
35
|
+
"segwit",
|
|
36
|
+
"mainnet",
|
|
37
|
+
"testnet",
|
|
38
|
+
"regtest"
|
|
39
|
+
],
|
|
40
|
+
"author": "",
|
|
41
|
+
"license": "BSD-2-Clause",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@scure/base": "^2.0.0",
|
|
44
|
+
"bitcoinjs-lib": "^7.0.0",
|
|
45
|
+
"bs58check": "^4.0.0",
|
|
46
|
+
"light-bolt11-decoder": "^3.2.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/bun": "latest"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"typescript": "^5"
|
|
53
|
+
},
|
|
54
|
+
"repository": {
|
|
55
|
+
"type": "git",
|
|
56
|
+
"url": "https://github.com/yourusername/bip-321.git"
|
|
57
|
+
},
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/yourusername/bip-321/issues"
|
|
60
|
+
},
|
|
61
|
+
"homepage": "https://github.com/yourusername/bip-321#readme",
|
|
62
|
+
"engines": {
|
|
63
|
+
"node": ">=16.0.0"
|
|
64
|
+
}
|
|
65
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
// Environment setup & latest features
|
|
4
|
+
"lib": ["ESNext"],
|
|
5
|
+
"target": "ESNext",
|
|
6
|
+
"module": "Preserve",
|
|
7
|
+
"moduleDetection": "force",
|
|
8
|
+
"jsx": "react-jsx",
|
|
9
|
+
"allowJs": true,
|
|
10
|
+
|
|
11
|
+
// Bundler mode
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": true,
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
|
|
17
|
+
// Best practices
|
|
18
|
+
"strict": true,
|
|
19
|
+
"skipLibCheck": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noImplicitOverride": true,
|
|
23
|
+
|
|
24
|
+
// Some stricter flags (disabled by default)
|
|
25
|
+
"noUnusedLocals": false,
|
|
26
|
+
"noUnusedParameters": false,
|
|
27
|
+
"noPropertyAccessFromIndexSignature": false
|
|
28
|
+
}
|
|
29
|
+
}
|