bip-321 0.0.3 → 0.0.5

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.
@@ -0,0 +1,94 @@
1
+ import tseslint from "@typescript-eslint/eslint-plugin";
2
+ import tsparser from "@typescript-eslint/parser";
3
+ import importPlugin from "eslint-plugin-import";
4
+
5
+ export default [
6
+ {
7
+ ignores: ["**/node_modules/**", "**/dist/**", "**/build/**", "**/*.min.js"],
8
+ },
9
+ {
10
+ files: ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
11
+ languageOptions: {
12
+ parser: tsparser,
13
+ parserOptions: {
14
+ ecmaVersion: "latest",
15
+ sourceType: "module",
16
+ project: "./tsconfig.json",
17
+ },
18
+ },
19
+ plugins: {
20
+ "@typescript-eslint": tseslint,
21
+ import: importPlugin,
22
+ },
23
+ rules: {
24
+ "@typescript-eslint/no-explicit-any": "error",
25
+ "@typescript-eslint/no-non-null-assertion": "error",
26
+ "@typescript-eslint/no-unused-vars": [
27
+ "error",
28
+ { argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
29
+ ],
30
+ "@typescript-eslint/no-floating-promises": "error",
31
+ "@typescript-eslint/no-misused-promises": "error",
32
+ "@typescript-eslint/await-thenable": "error",
33
+ "@typescript-eslint/no-unnecessary-condition": "warn",
34
+ "@typescript-eslint/prefer-nullish-coalescing": "error",
35
+ "@typescript-eslint/prefer-optional-chain": "error",
36
+ "@typescript-eslint/no-unsafe-assignment": "error",
37
+ "@typescript-eslint/no-unsafe-member-access": "error",
38
+ "@typescript-eslint/no-unsafe-call": "error",
39
+ "@typescript-eslint/no-unsafe-return": "error",
40
+ "@typescript-eslint/no-unsafe-argument": "error",
41
+ "@typescript-eslint/restrict-template-expressions": "error",
42
+ "@typescript-eslint/no-base-to-string": "error",
43
+ "@typescript-eslint/require-await": "error",
44
+ "@typescript-eslint/switch-exhaustiveness-check": "error",
45
+
46
+ "no-console": "warn",
47
+ "no-debugger": "error",
48
+ eqeqeq: ["error", "always"],
49
+ "no-var": "error",
50
+ "prefer-const": "error",
51
+ "prefer-arrow-callback": "error",
52
+ "no-throw-literal": "error",
53
+ "no-return-await": "error",
54
+ "require-await": "off",
55
+ "no-async-promise-executor": "error",
56
+ "no-promise-executor-return": "error",
57
+
58
+ "no-eval": "error",
59
+ "no-implied-eval": "error",
60
+ "no-new-func": "error",
61
+ "no-new-wrappers": "error",
62
+ "no-useless-concat": "error",
63
+ "prefer-template": "error",
64
+ "no-lonely-if": "error",
65
+ "no-else-return": "error",
66
+ "no-unneeded-ternary": "error",
67
+ "prefer-destructuring": [
68
+ "warn",
69
+ { array: false, object: true },
70
+ { enforceForRenamedProperties: false },
71
+ ],
72
+
73
+ "import/no-unresolved": "off",
74
+ "import/named": "error",
75
+ "import/no-duplicates": "error",
76
+ "import/no-cycle": "error",
77
+ "import/no-self-import": "error",
78
+
79
+ "no-await-in-loop": "warn",
80
+ },
81
+ },
82
+ {
83
+ files: ["example.ts", "example.js"],
84
+ rules: {
85
+ "no-console": "off",
86
+ },
87
+ },
88
+ {
89
+ files: ["*.test.ts", "*.test.js", "*.spec.ts", "*.spec.js"],
90
+ rules: {
91
+ "@typescript-eslint/no-non-null-assertion": "off",
92
+ },
93
+ },
94
+ ];
package/example.ts CHANGED
@@ -47,7 +47,7 @@ const example4 = parseBIP321(
47
47
  "bitcoin:?lightning=lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
48
48
  );
49
49
  console.log(`Valid: ${example4.valid}`);
50
- console.log(`Address in path: ${example4.address || "none"}`);
50
+ console.log(`Address in path: ${example4.address ?? "none"}`);
51
51
  console.log(`Payment type: ${example4.paymentMethods[0]?.type}`);
52
52
  console.log(`Network: ${example4.paymentMethods[0]?.network}`);
53
53
  console.log();
@@ -71,8 +71,8 @@ const example6 = parseBIP321(
71
71
  );
72
72
  const byNetwork = getPaymentMethodsByNetwork(example6);
73
73
  console.log(`Valid: ${example6.valid}`);
74
- console.log(`Mainnet methods: ${byNetwork.mainnet?.length || 0}`);
75
- console.log(`Testnet methods: ${byNetwork.testnet?.length || 0}`);
74
+ console.log(`Mainnet methods: ${byNetwork.mainnet?.length ?? 0}`);
75
+ console.log(`Testnet methods: ${byNetwork.testnet?.length ?? 0}`);
76
76
  console.log();
77
77
 
78
78
  // Example 7: Testnet lightning invoice
package/index.test.ts CHANGED
@@ -6,30 +6,70 @@ import {
6
6
  formatPaymentMethodsSummary,
7
7
  } from "./index";
8
8
 
9
+ const TEST_DATA = {
10
+ addresses: {
11
+ mainnet: {
12
+ p2pkh: "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
13
+ bech32: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
14
+ taproot: "bc1pdyp8m5mhurxa9mf822jegnhu49g2zcchgcq8jzrjxg58u2lvudyqftt43a",
15
+ },
16
+ testnet: {
17
+ bech32: "tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
18
+ },
19
+ regtest: {
20
+ bech32: "bcrt1qw508d6qejxtdg4y5r3zarvary0c5xw7kygt080",
21
+ },
22
+ },
23
+ lightning: {
24
+ mainnet:
25
+ "lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
26
+ testnet:
27
+ "lntb2500n1pwxlkl5pp5g8hz28tlf950ps942lu3dknfete8yax2ctywpwjs872x9kngvvuqdqage5hyum5yp6x2um5yp5kuan0d93k2cqzyskdc5s2ltgm9kklz42x3e4tggdd9lcep2s9t2yk54gnfxg48wxushayrt52zjmua43gdnxmuc5s0c8g29ja9vnxs6x3kxgsha07htcacpmdyl64",
28
+ regtest:
29
+ "lnbcrt50u1p5s6w2zpp5juf0r9zutj4zv00kpuuqmgn246azqaq0u5kksx93p46ue94gpmrsdqqcqzzsxqyz5vqsp57u7clsm57nas7c0r2p4ujxr8whla6gxmwf44yqt9f862evjzd3ds9qxpqysgqrwvspjd8g3cfrkg2mrmxfdjcwk5nenw2qnmrys0rvkdmxes6jf5xfykunl5g9hnnahsnz0c90u7k42hmr7w90c0qkw3lllwy40mmqgsqjtyzpd",
30
+ signet:
31
+ "lntbs10u1p5s6wgtsp5d8a763exauvdk6s5gwvl8zmuapmgjq05fdv6trasjd4slvgkvzzqpp56vxdyl24hmkpz0tvqq84xdpqqeql3x7kh8tey4uum2cu8jny6djqdq4g9exkgznw3hhyefqyvenyxqzjccqp2rzjqdwy5et9ygczjl2jqmr9e5xm28u3gksjfrf0pht04uwz2lt9d59cypqelcqqq8gqqqqqqqqpqqqqqzsqqc9qxpqysgq0x0pg2s65rnp2cr35td5tq0vwgmnrghkpzt93eypqvvfu5m40pcjl9k2x2m4kqgvz2ez8tzxqgw0nyeg2w60nfky579uakd4mhr3ncgp0xwars",
32
+ },
33
+ ark: {
34
+ mainnet:
35
+ "ark1pwh9vsmezqqpjy9akejayl2vvcse6he97rn40g84xrlvrlnhayuuyefrp9nse2yspqqjl5wpy",
36
+ testnet:
37
+ "tark1pm6sr0fpzqqpnzzwxf209kju4qavs4gtumxk30yv2u5ncrvtp72z34axcvrydtdqpqq5838km",
38
+ },
39
+ silentPayment: {
40
+ mainnet:
41
+ "sp1qqvgwll3hawztz50nx5mcs70ytam4z068c2cw0z37zcg5yj23h65kcqamhqcxal0gerzly0jnkv7x0ar3sjhmh0n5yugyj3kd7ahzfsdw5590ajuk",
42
+ testnet:
43
+ "tsp1qq2svvt45f2rzmfr4vwhgvjfjgna92h09g9a9ttpvmz5x5wmscsepyqhkk6tjxzr6v0vj3q87gcrqjq73z6ljylgk4m6vphvkpg4afzwp4ve0nr78",
44
+ },
45
+ } as const;
46
+
9
47
  describe("BIP-321 Parser", () => {
10
48
  describe("Basic Address Parsing", () => {
11
49
  test("parses simple mainnet address", () => {
12
- const result = parseBIP321("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
50
+ const result = parseBIP321(
51
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}`,
52
+ );
13
53
  expect(result.valid).toBe(true);
14
- expect(result.address).toBe("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
54
+ expect(result.address).toBe(TEST_DATA.addresses.mainnet.p2pkh);
15
55
  expect(result.network).toBe("mainnet");
16
56
  expect(result.paymentMethods.length).toBe(1);
17
- expect(result.paymentMethods[0]!.type).toBe("onchain");
18
- expect(result.paymentMethods[0]!.valid).toBe(true);
57
+ expect(result.paymentMethods[0]?.type).toBe("onchain");
58
+ expect(result.paymentMethods[0]?.valid).toBe(true);
19
59
  });
20
60
 
21
61
  test("parses bech32 mainnet address", () => {
22
62
  const result = parseBIP321(
23
- "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
63
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}`,
24
64
  );
25
65
  expect(result.valid).toBe(true);
26
66
  expect(result.network).toBe("mainnet");
27
- expect(result.paymentMethods[0]!.valid).toBe(true);
67
+ expect(result.paymentMethods[0]?.valid).toBe(true);
28
68
  });
29
69
 
30
70
  test("parses testnet address", () => {
31
71
  const result = parseBIP321(
32
- "bitcoin:tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
72
+ `bitcoin:${TEST_DATA.addresses.testnet.bech32}`,
33
73
  );
34
74
  expect(result.valid).toBe(true);
35
75
  expect(result.network).toBe("testnet");
@@ -37,7 +77,7 @@ describe("BIP-321 Parser", () => {
37
77
 
38
78
  test("parses uppercase URI", () => {
39
79
  const result = parseBIP321(
40
- "BITCOIN:BC1QAR0SRRR7XFKVY5L643LYDNW9RE59GTZZWF5MDQ",
80
+ `BITCOIN:${TEST_DATA.addresses.mainnet.bech32.toUpperCase()}`,
41
81
  );
42
82
  expect(result.valid).toBe(true);
43
83
  expect(result.network).toBe("mainnet");
@@ -47,7 +87,7 @@ describe("BIP-321 Parser", () => {
47
87
  describe("Query Parameters", () => {
48
88
  test("parses label parameter", () => {
49
89
  const result = parseBIP321(
50
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?label=Luke-Jr",
90
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?label=Luke-Jr`,
51
91
  );
52
92
  expect(result.valid).toBe(true);
53
93
  expect(result.label).toBe("Luke-Jr");
@@ -55,7 +95,7 @@ describe("BIP-321 Parser", () => {
55
95
 
56
96
  test("parses amount parameter", () => {
57
97
  const result = parseBIP321(
58
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=20.3",
98
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?amount=20.3`,
59
99
  );
60
100
  expect(result.valid).toBe(true);
61
101
  expect(result.amount).toBe(20.3);
@@ -63,7 +103,7 @@ describe("BIP-321 Parser", () => {
63
103
 
64
104
  test("parses message parameter", () => {
65
105
  const result = parseBIP321(
66
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?message=Donation%20for%20project%20xyz",
106
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?message=Donation%20for%20project%20xyz`,
67
107
  );
68
108
  expect(result.valid).toBe(true);
69
109
  expect(result.message).toBe("Donation for project xyz");
@@ -71,7 +111,7 @@ describe("BIP-321 Parser", () => {
71
111
 
72
112
  test("parses multiple parameters", () => {
73
113
  const result = parseBIP321(
74
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz",
114
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz`,
75
115
  );
76
116
  expect(result.valid).toBe(true);
77
117
  expect(result.amount).toBe(50);
@@ -81,7 +121,7 @@ describe("BIP-321 Parser", () => {
81
121
 
82
122
  test("rejects invalid amount with comma", () => {
83
123
  const result = parseBIP321(
84
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=50,000.00",
124
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?amount=50,000.00`,
85
125
  );
86
126
  expect(result.valid).toBe(false);
87
127
  expect(result.errors).toContain("Invalid amount format");
@@ -89,7 +129,7 @@ describe("BIP-321 Parser", () => {
89
129
 
90
130
  test("rejects multiple label parameters", () => {
91
131
  const result = parseBIP321(
92
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?label=Luke-Jr&label=Matt",
132
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?label=Luke-Jr&label=Matt`,
93
133
  );
94
134
  expect(result.valid).toBe(false);
95
135
  expect(result.errors).toContain("Multiple label parameters not allowed");
@@ -97,7 +137,7 @@ describe("BIP-321 Parser", () => {
97
137
 
98
138
  test("rejects multiple amount parameters", () => {
99
139
  const result = parseBIP321(
100
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=42&amount=10",
140
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?amount=42&amount=10`,
101
141
  );
102
142
  expect(result.valid).toBe(false);
103
143
  expect(result.errors).toContain("Multiple amount parameters not allowed");
@@ -107,7 +147,7 @@ describe("BIP-321 Parser", () => {
107
147
  describe("Lightning Invoice", () => {
108
148
  test("parses lightning invoice with fallback", () => {
109
149
  const result = parseBIP321(
110
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?lightning=lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
150
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?lightning=${TEST_DATA.lightning.mainnet}`,
111
151
  );
112
152
  expect(result.valid).toBe(true);
113
153
  expect(result.paymentMethods.length).toBe(2);
@@ -121,7 +161,7 @@ describe("BIP-321 Parser", () => {
121
161
 
122
162
  test("parses lightning invoice without fallback", () => {
123
163
  const result = parseBIP321(
124
- "bitcoin:?lightning=lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
164
+ `bitcoin:?lightning=${TEST_DATA.lightning.mainnet}`,
125
165
  );
126
166
  expect(result.valid).toBe(true);
127
167
  expect(result.address).toBeUndefined();
@@ -131,7 +171,7 @@ describe("BIP-321 Parser", () => {
131
171
 
132
172
  test("detects signet lightning invoice", () => {
133
173
  const result = parseBIP321(
134
- "bitcoin:?lightning=lntbs10u1p5s6wgtsp5d8a763exauvdk6s5gwvl8zmuapmgjq05fdv6trasjd4slvgkvzzqpp56vxdyl24hmkpz0tvqq84xdpqqeql3x7kh8tey4uum2cu8jny6djqdq4g9exkgznw3hhyefqyvenyxqzjccqp2rzjqdwy5et9ygczjl2jqmr9e5xm28u3gksjfrf0pht04uwz2lt9d59cypqelcqqq8gqqqqqqqqpqqqqqzsqqc9qxpqysgq0x0pg2s65rnp2cr35td5tq0vwgmnrghkpzt93eypqvvfu5m40pcjl9k2x2m4kqgvz2ez8tzxqgw0nyeg2w60nfky579uakd4mhr3ncgp0xwars",
174
+ `bitcoin:?lightning=${TEST_DATA.lightning.signet}`,
135
175
  );
136
176
  expect(result.valid).toBe(true);
137
177
  expect(result.paymentMethods[0]!.network).toBe("signet");
@@ -139,7 +179,7 @@ describe("BIP-321 Parser", () => {
139
179
 
140
180
  test("detects regtest lightning invoice", () => {
141
181
  const result = parseBIP321(
142
- "bitcoin:?lightning=lnbcrt50u1p5s6w2zpp5juf0r9zutj4zv00kpuuqmgn246azqaq0u5kksx93p46ue94gpmrsdqqcqzzsxqyz5vqsp57u7clsm57nas7c0r2p4ujxr8whla6gxmwf44yqt9f862evjzd3ds9qxpqysgqrwvspjd8g3cfrkg2mrmxfdjcwk5nenw2qnmrys0rvkdmxes6jf5xfykunl5g9hnnahsnz0c90u7k42hmr7w90c0qkw3lllwy40mmqgsqjtyzpd",
182
+ `bitcoin:?lightning=${TEST_DATA.lightning.regtest}`,
143
183
  );
144
184
  expect(result.valid).toBe(true);
145
185
  expect(result.paymentMethods[0]!.network).toBe("regtest");
@@ -147,7 +187,7 @@ describe("BIP-321 Parser", () => {
147
187
 
148
188
  test("detects testnet lightning invoice", () => {
149
189
  const result = parseBIP321(
150
- "bitcoin:?lightning=lntb2500n1pwxlkl5pp5g8hz28tlf950ps942lu3dknfete8yax2ctywpwjs872x9kngvvuqdqage5hyum5yp6x2um5yp5kuan0d93k2cqzyskdc5s2ltgm9kklz42x3e4tggdd9lcep2s9t2yk54gnfxg48wxushayrt52zjmua43gdnxmuc5s0c8g29ja9vnxs6x3kxgsha07htcacpmdyl64",
190
+ `bitcoin:?lightning=${TEST_DATA.lightning.testnet}`,
151
191
  );
152
192
  expect(result.valid).toBe(true);
153
193
  expect(result.paymentMethods[0]!.network).toBe("testnet");
@@ -155,7 +195,7 @@ describe("BIP-321 Parser", () => {
155
195
 
156
196
  test("rejects testnet address in bc parameter", () => {
157
197
  const result = parseBIP321(
158
- "bitcoin:?bc=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
198
+ `bitcoin:?bc=${TEST_DATA.addresses.testnet.bech32}`,
159
199
  );
160
200
  expect(result.valid).toBe(false);
161
201
  expect(result.errors.some((e) => e.includes("network mismatch"))).toBe(
@@ -165,33 +205,117 @@ describe("BIP-321 Parser", () => {
165
205
  });
166
206
 
167
207
  describe("Alternative Payment Methods", () => {
168
- test("parses BOLT12 offer", () => {
208
+ test("parses valid BOLT12 offer", () => {
209
+ const result = parseBIP321("bitcoin:?lno=lno1qqqq02k20d");
210
+ expect(result.valid).toBe(true);
211
+ expect(result.paymentMethods.length).toBe(1);
212
+ expect(result.paymentMethods[0]!.type).toBe("offer");
213
+ expect(result.paymentMethods[0]!.valid).toBe(true);
214
+ });
215
+
216
+ test("rejects invalid BOLT12 offer", () => {
169
217
  const result = parseBIP321("bitcoin:?lno=lno1bogusoffer");
218
+ expect(result.paymentMethods.length).toBe(1);
219
+ expect(result.paymentMethods[0]!.type).toBe("offer");
220
+ expect(result.paymentMethods[0]!.valid).toBe(false);
221
+ expect(result.errors.some((e) => e.includes("BOLT12 offer"))).toBe(true);
222
+ });
223
+
224
+ test("parses mainnet silent payment address", () => {
225
+ const result = parseBIP321(
226
+ `bitcoin:?sp=${TEST_DATA.silentPayment.mainnet}`,
227
+ );
170
228
  expect(result.valid).toBe(true);
171
229
  expect(result.paymentMethods.length).toBe(1);
172
- expect(result.paymentMethods[0]!.type).toBe("lno");
230
+ expect(result.paymentMethods[0]!.type).toBe("silent-payment");
231
+ expect(result.paymentMethods[0]!.network).toBe("mainnet");
232
+ expect(result.paymentMethods[0]!.valid).toBe(true);
173
233
  });
174
234
 
175
- test("parses silent payment address", () => {
176
- const result = parseBIP321("bitcoin:?sp=sp1qsilentpayment");
235
+ test("parses testnet silent payment address", () => {
236
+ const result = parseBIP321(
237
+ `bitcoin:?sp=${TEST_DATA.silentPayment.testnet}`,
238
+ );
177
239
  expect(result.valid).toBe(true);
178
240
  expect(result.paymentMethods.length).toBe(1);
179
241
  expect(result.paymentMethods[0]!.type).toBe("silent-payment");
242
+ expect(result.paymentMethods[0]!.network).toBe("testnet");
243
+ expect(result.paymentMethods[0]!.valid).toBe(true);
244
+ });
245
+
246
+ test("rejects invalid silent payment address", () => {
247
+ const result = parseBIP321("bitcoin:?sp=sp1qinvalid");
248
+ expect(result.paymentMethods.length).toBe(1);
249
+ expect(result.paymentMethods[0]!.type).toBe("silent-payment");
250
+ expect(result.paymentMethods[0]!.valid).toBe(false);
251
+ expect(result.errors.some((e) => e.includes("silent payment"))).toBe(
252
+ true,
253
+ );
180
254
  });
181
255
 
182
256
  test("parses multiple payment methods", () => {
183
257
  const result = parseBIP321(
184
- "bitcoin:?lno=lno1bogusoffer&sp=sp1qsilentpayment",
258
+ `bitcoin:?lno=lno1qqqq02k20d&sp=${TEST_DATA.silentPayment.mainnet}`,
185
259
  );
186
260
  expect(result.valid).toBe(true);
187
261
  expect(result.paymentMethods.length).toBe(2);
188
262
  });
189
263
  });
190
264
 
265
+ describe("Ark Addresses", () => {
266
+ test("parses mainnet Ark address", () => {
267
+ const result = parseBIP321(`bitcoin:?ark=${TEST_DATA.ark.mainnet}`);
268
+ expect(result.valid).toBe(true);
269
+ expect(result.paymentMethods.length).toBe(1);
270
+ expect(result.paymentMethods[0]!.type).toBe("ark");
271
+ expect(result.paymentMethods[0]!.network).toBe("mainnet");
272
+ expect(result.paymentMethods[0]!.valid).toBe(true);
273
+ });
274
+
275
+ test("parses testnet Ark address", () => {
276
+ const result = parseBIP321(`bitcoin:?ark=${TEST_DATA.ark.testnet}`);
277
+ expect(result.valid).toBe(true);
278
+ expect(result.paymentMethods.length).toBe(1);
279
+ expect(result.paymentMethods[0]!.type).toBe("ark");
280
+ expect(result.paymentMethods[0]!.network).toBe("testnet");
281
+ expect(result.paymentMethods[0]!.valid).toBe(true);
282
+ });
283
+
284
+ test("rejects invalid Ark address", () => {
285
+ const result = parseBIP321("bitcoin:?ark=invalid_ark_address");
286
+ expect(result.paymentMethods.length).toBe(1);
287
+ expect(result.paymentMethods[0]!.valid).toBe(false);
288
+ expect(result.errors.some((e) => e.includes("Ark address"))).toBe(true);
289
+ });
290
+
291
+ test("parses Ark with Bitcoin address", () => {
292
+ const result = parseBIP321(
293
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?ark=${TEST_DATA.ark.mainnet}`,
294
+ );
295
+ expect(result.valid).toBe(true);
296
+ expect(result.paymentMethods.length).toBe(2);
297
+ expect(result.paymentMethods[0]!.type).toBe("onchain");
298
+ expect(result.paymentMethods[1]!.type).toBe("ark");
299
+ expect(result.paymentMethods[0]!.network).toBe("mainnet");
300
+ expect(result.paymentMethods[1]!.network).toBe("mainnet");
301
+ });
302
+
303
+ test("validates Ark network matches expected network", () => {
304
+ const result = parseBIP321(
305
+ `bitcoin:?ark=${TEST_DATA.ark.mainnet}`,
306
+ "testnet",
307
+ );
308
+ expect(result.valid).toBe(false);
309
+ expect(result.errors.some((e) => e.includes("network mismatch"))).toBe(
310
+ true,
311
+ );
312
+ });
313
+ });
314
+
191
315
  describe("Network-specific Parameters", () => {
192
316
  test("parses bc parameter for mainnet", () => {
193
317
  const result = parseBIP321(
194
- "bitcoin:?bc=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
318
+ `bitcoin:?bc=${TEST_DATA.addresses.mainnet.bech32}`,
195
319
  );
196
320
  expect(result.valid).toBe(true);
197
321
  expect(result.paymentMethods[0]!.network).toBe("mainnet");
@@ -199,7 +323,7 @@ describe("BIP-321 Parser", () => {
199
323
 
200
324
  test("parses tb parameter for testnet", () => {
201
325
  const result = parseBIP321(
202
- "bitcoin:?tb=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
326
+ `bitcoin:?tb=${TEST_DATA.addresses.testnet.bech32}`,
203
327
  );
204
328
  expect(result.valid).toBe(true);
205
329
  expect(result.paymentMethods[0]!.network).toBe("testnet");
@@ -207,7 +331,7 @@ describe("BIP-321 Parser", () => {
207
331
 
208
332
  test("rejects testnet address in bc parameter", () => {
209
333
  const result = parseBIP321(
210
- "bitcoin:?bc=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
334
+ `bitcoin:?bc=${TEST_DATA.addresses.testnet.bech32}`,
211
335
  );
212
336
  expect(result.valid).toBe(false);
213
337
  expect(result.errors.some((e) => e.includes("network mismatch"))).toBe(
@@ -217,7 +341,7 @@ describe("BIP-321 Parser", () => {
217
341
 
218
342
  test("parses multiple segwit versions", () => {
219
343
  const result = parseBIP321(
220
- "bitcoin:?bc=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq&bc=bc1pdyp8m5mhurxa9mf822jegnhu49g2zcchgcq8jzrjxg58u2lvudyqftt43a",
344
+ `bitcoin:?bc=${TEST_DATA.addresses.mainnet.bech32}&bc=${TEST_DATA.addresses.mainnet.taproot}`,
221
345
  );
222
346
  expect(result.valid).toBe(true);
223
347
  expect(result.paymentMethods.length).toBe(2);
@@ -229,7 +353,7 @@ describe("BIP-321 Parser", () => {
229
353
  describe("Proof of Payment", () => {
230
354
  test("parses pop parameter", () => {
231
355
  const result = parseBIP321(
232
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?pop=initiatingapp%3a",
356
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?pop=initiatingapp%3a`,
233
357
  );
234
358
  expect(result.valid).toBe(true);
235
359
  expect(result.pop).toBe("initiatingapp%3a");
@@ -238,7 +362,7 @@ describe("BIP-321 Parser", () => {
238
362
 
239
363
  test("parses req-pop parameter", () => {
240
364
  const result = parseBIP321(
241
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?req-pop=callbackuri%3a",
365
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?req-pop=callbackuri%3a`,
242
366
  );
243
367
  expect(result.valid).toBe(true);
244
368
  expect(result.pop).toBe("callbackuri%3a");
@@ -247,7 +371,7 @@ describe("BIP-321 Parser", () => {
247
371
 
248
372
  test("rejects forbidden http scheme in pop", () => {
249
373
  const result = parseBIP321(
250
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?pop=https%3aiwantyouripaddress.com",
374
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?pop=https%3aiwantyouripaddress.com`,
251
375
  );
252
376
  expect(
253
377
  result.errors.some((e) => e.includes("Forbidden pop scheme")),
@@ -256,14 +380,14 @@ describe("BIP-321 Parser", () => {
256
380
 
257
381
  test("rejects payment when req-pop uses forbidden scheme", () => {
258
382
  const result = parseBIP321(
259
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?req-pop=https%3aevilwebsite.com",
383
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?req-pop=https%3aevilwebsite.com`,
260
384
  );
261
385
  expect(result.valid).toBe(false);
262
386
  });
263
387
 
264
388
  test("rejects multiple pop parameters", () => {
265
389
  const result = parseBIP321(
266
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?pop=callback%3a&req-pop=callback%3a",
390
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?pop=callback%3a&req-pop=callback%3a`,
267
391
  );
268
392
  expect(result.valid).toBe(false);
269
393
  expect(result.errors.some((e) => e.includes("Multiple pop"))).toBe(true);
@@ -273,7 +397,7 @@ describe("BIP-321 Parser", () => {
273
397
  describe("Required Parameters", () => {
274
398
  test("rejects unknown required parameters", () => {
275
399
  const result = parseBIP321(
276
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?req-somethingyoudontunderstand=50",
400
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?req-somethingyoudontunderstand=50`,
277
401
  );
278
402
  expect(result.valid).toBe(false);
279
403
  expect(result.requiredParams.length).toBeGreaterThan(0);
@@ -281,7 +405,7 @@ describe("BIP-321 Parser", () => {
281
405
 
282
406
  test("accepts unknown optional parameters", () => {
283
407
  const result = parseBIP321(
284
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?somethingyoudontunderstand=50",
408
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?somethingyoudontunderstand=50`,
285
409
  );
286
410
  expect(result.valid).toBe(true);
287
411
  expect(result.optionalParams.somethingyoudontunderstand).toEqual(["50"]);
@@ -315,7 +439,7 @@ describe("BIP-321 Parser", () => {
315
439
  describe("Helper Functions", () => {
316
440
  test("getPaymentMethodsByNetwork groups correctly", () => {
317
441
  const result = parseBIP321(
318
- "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?tb=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
442
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}?tb=${TEST_DATA.addresses.testnet.bech32}`,
319
443
  );
320
444
  const byNetwork = getPaymentMethodsByNetwork(result);
321
445
  expect(byNetwork.mainnet!.length).toBe(1);
@@ -324,7 +448,7 @@ describe("BIP-321 Parser", () => {
324
448
 
325
449
  test("getValidPaymentMethods filters correctly", () => {
326
450
  const result = parseBIP321(
327
- "bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?lightning=invalidinvoice",
451
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}?lightning=invalidinvoice`,
328
452
  );
329
453
  const valid = getValidPaymentMethods(result);
330
454
  expect(valid.length).toBe(1);
@@ -333,7 +457,7 @@ describe("BIP-321 Parser", () => {
333
457
 
334
458
  test("formatPaymentMethodsSummary produces output", () => {
335
459
  const result = parseBIP321(
336
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1.5&label=Test",
460
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?amount=1.5&label=Test`,
337
461
  );
338
462
  const summary = formatPaymentMethodsSummary(result);
339
463
  expect(summary).toContain("Valid: true");
@@ -345,11 +469,93 @@ describe("BIP-321 Parser", () => {
345
469
  describe("Case Insensitivity", () => {
346
470
  test("handles mixed case in parameters", () => {
347
471
  const result = parseBIP321(
348
- "bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?AMOUNT=1.5&Label=Test",
472
+ `bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}?AMOUNT=1.5&Label=Test`,
349
473
  );
350
474
  expect(result.valid).toBe(true);
351
475
  expect(result.amount).toBe(1.5);
352
476
  expect(result.label).toBe("Test");
353
477
  });
354
478
  });
479
+
480
+ describe("Network Validation", () => {
481
+ test("accepts mainnet address when expecting mainnet", () => {
482
+ const result = parseBIP321(
483
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}`,
484
+ "mainnet",
485
+ );
486
+ expect(result.valid).toBe(true);
487
+ expect(result.paymentMethods[0]!.network).toBe("mainnet");
488
+ expect(result.paymentMethods[0]!.valid).toBe(true);
489
+ });
490
+
491
+ test("rejects testnet address when expecting mainnet", () => {
492
+ const result = parseBIP321(
493
+ `bitcoin:${TEST_DATA.addresses.testnet.bech32}`,
494
+ "mainnet",
495
+ );
496
+ expect(result.valid).toBe(false);
497
+ expect(result.errors.some((e) => e.includes("network mismatch"))).toBe(
498
+ true,
499
+ );
500
+ expect(result.paymentMethods[0]!.valid).toBe(false);
501
+ });
502
+
503
+ test("accepts testnet lightning invoice when expecting testnet", () => {
504
+ const result = parseBIP321(
505
+ `bitcoin:?lightning=${TEST_DATA.lightning.testnet}`,
506
+ "testnet",
507
+ );
508
+ expect(result.valid).toBe(true);
509
+ expect(result.paymentMethods[0]!.network).toBe("testnet");
510
+ });
511
+
512
+ test("rejects mainnet lightning invoice when expecting testnet", () => {
513
+ const result = parseBIP321(
514
+ `bitcoin:?lightning=${TEST_DATA.lightning.mainnet}`,
515
+ "testnet",
516
+ );
517
+ expect(result.valid).toBe(false);
518
+ expect(result.errors.some((e) => e.includes("expected testnet"))).toBe(
519
+ true,
520
+ );
521
+ });
522
+
523
+ test("rejects mixed networks when expecting specific network", () => {
524
+ const result = parseBIP321(
525
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}?tb=${TEST_DATA.addresses.testnet.bech32}`,
526
+ "mainnet",
527
+ );
528
+ expect(result.valid).toBe(false);
529
+ expect(result.paymentMethods[0]!.valid).toBe(true);
530
+ expect(result.paymentMethods[1]!.valid).toBe(false);
531
+ });
532
+
533
+ test("accepts regtest address when expecting regtest", () => {
534
+ const result = parseBIP321(
535
+ `bitcoin:${TEST_DATA.addresses.regtest.bech32}`,
536
+ "regtest",
537
+ );
538
+ expect(result.valid).toBe(true);
539
+ expect(result.paymentMethods[0]!.network).toBe("regtest");
540
+ });
541
+
542
+ test("works without network parameter (no validation)", () => {
543
+ const result = parseBIP321(
544
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}?tb=${TEST_DATA.addresses.testnet.bech32}`,
545
+ );
546
+ expect(result.valid).toBe(true);
547
+ expect(result.paymentMethods[0]!.network).toBe("mainnet");
548
+ expect(result.paymentMethods[1]!.network).toBe("testnet");
549
+ });
550
+
551
+ test("validates all payment methods against expected network", () => {
552
+ const result = parseBIP321(
553
+ `bitcoin:${TEST_DATA.addresses.mainnet.bech32}?lightning=${TEST_DATA.lightning.mainnet}`,
554
+ "mainnet",
555
+ );
556
+ expect(result.valid).toBe(true);
557
+ expect(result.paymentMethods.length).toBe(2);
558
+ expect(result.paymentMethods.every((pm) => pm.valid)).toBe(true);
559
+ });
560
+ });
355
561
  });