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.
- package/LICENSE +21 -0
- package/README.md +77 -12
- package/bun.lock +467 -11
- package/eslint.config.js +94 -0
- package/example.ts +3 -3
- package/index.test.ts +247 -41
- package/index.ts +189 -23
- package/package.json +8 -4
- package/oxlintrc.json +0 -23
package/eslint.config.js
ADDED
|
@@ -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
|
|
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
|
|
75
|
-
console.log(`Testnet methods: ${byNetwork.testnet?.length
|
|
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(
|
|
50
|
+
const result = parseBIP321(
|
|
51
|
+
`bitcoin:${TEST_DATA.addresses.mainnet.p2pkh}`,
|
|
52
|
+
);
|
|
13
53
|
expect(result.valid).toBe(true);
|
|
14
|
-
expect(result.address).toBe(
|
|
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]
|
|
18
|
-
expect(result.paymentMethods[0]
|
|
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
|
-
|
|
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]
|
|
67
|
+
expect(result.paymentMethods[0]?.valid).toBe(true);
|
|
28
68
|
});
|
|
29
69
|
|
|
30
70
|
test("parses testnet address", () => {
|
|
31
71
|
const result = parseBIP321(
|
|
32
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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("
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
});
|