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.test.ts
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { test, expect, describe } from "bun:test";
|
|
2
|
+
import {
|
|
3
|
+
parseBIP321,
|
|
4
|
+
getPaymentMethodsByNetwork,
|
|
5
|
+
getValidPaymentMethods,
|
|
6
|
+
formatPaymentMethodsSummary,
|
|
7
|
+
} from "./index";
|
|
8
|
+
|
|
9
|
+
describe("BIP-321 Parser", () => {
|
|
10
|
+
describe("Basic Address Parsing", () => {
|
|
11
|
+
test("parses simple mainnet address", () => {
|
|
12
|
+
const result = parseBIP321("bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
|
|
13
|
+
expect(result.valid).toBe(true);
|
|
14
|
+
expect(result.address).toBe("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa");
|
|
15
|
+
expect(result.network).toBe("mainnet");
|
|
16
|
+
expect(result.paymentMethods.length).toBe(1);
|
|
17
|
+
expect(result.paymentMethods[0]!.type).toBe("onchain");
|
|
18
|
+
expect(result.paymentMethods[0]!.valid).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("parses bech32 mainnet address", () => {
|
|
22
|
+
const result = parseBIP321(
|
|
23
|
+
"bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
|
|
24
|
+
);
|
|
25
|
+
expect(result.valid).toBe(true);
|
|
26
|
+
expect(result.network).toBe("mainnet");
|
|
27
|
+
expect(result.paymentMethods[0]!.valid).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("parses testnet address", () => {
|
|
31
|
+
const result = parseBIP321(
|
|
32
|
+
"bitcoin:tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
|
|
33
|
+
);
|
|
34
|
+
expect(result.valid).toBe(true);
|
|
35
|
+
expect(result.network).toBe("testnet");
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("parses uppercase URI", () => {
|
|
39
|
+
const result = parseBIP321(
|
|
40
|
+
"BITCOIN:BC1QAR0SRRR7XFKVY5L643LYDNW9RE59GTZZWF5MDQ",
|
|
41
|
+
);
|
|
42
|
+
expect(result.valid).toBe(true);
|
|
43
|
+
expect(result.network).toBe("mainnet");
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe("Query Parameters", () => {
|
|
48
|
+
test("parses label parameter", () => {
|
|
49
|
+
const result = parseBIP321(
|
|
50
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?label=Luke-Jr",
|
|
51
|
+
);
|
|
52
|
+
expect(result.valid).toBe(true);
|
|
53
|
+
expect(result.label).toBe("Luke-Jr");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("parses amount parameter", () => {
|
|
57
|
+
const result = parseBIP321(
|
|
58
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=20.3",
|
|
59
|
+
);
|
|
60
|
+
expect(result.valid).toBe(true);
|
|
61
|
+
expect(result.amount).toBe(20.3);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("parses message parameter", () => {
|
|
65
|
+
const result = parseBIP321(
|
|
66
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?message=Donation%20for%20project%20xyz",
|
|
67
|
+
);
|
|
68
|
+
expect(result.valid).toBe(true);
|
|
69
|
+
expect(result.message).toBe("Donation for project xyz");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("parses multiple parameters", () => {
|
|
73
|
+
const result = parseBIP321(
|
|
74
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=50&label=Luke-Jr&message=Donation%20for%20project%20xyz",
|
|
75
|
+
);
|
|
76
|
+
expect(result.valid).toBe(true);
|
|
77
|
+
expect(result.amount).toBe(50);
|
|
78
|
+
expect(result.label).toBe("Luke-Jr");
|
|
79
|
+
expect(result.message).toBe("Donation for project xyz");
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("rejects invalid amount with comma", () => {
|
|
83
|
+
const result = parseBIP321(
|
|
84
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=50,000.00",
|
|
85
|
+
);
|
|
86
|
+
expect(result.valid).toBe(false);
|
|
87
|
+
expect(result.errors).toContain("Invalid amount format");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("rejects multiple label parameters", () => {
|
|
91
|
+
const result = parseBIP321(
|
|
92
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?label=Luke-Jr&label=Matt",
|
|
93
|
+
);
|
|
94
|
+
expect(result.valid).toBe(false);
|
|
95
|
+
expect(result.errors).toContain("Multiple label parameters not allowed");
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("rejects multiple amount parameters", () => {
|
|
99
|
+
const result = parseBIP321(
|
|
100
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=42&amount=10",
|
|
101
|
+
);
|
|
102
|
+
expect(result.valid).toBe(false);
|
|
103
|
+
expect(result.errors).toContain("Multiple amount parameters not allowed");
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe("Lightning Invoice", () => {
|
|
108
|
+
test("parses lightning invoice with fallback", () => {
|
|
109
|
+
const result = parseBIP321(
|
|
110
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?lightning=lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
|
|
111
|
+
);
|
|
112
|
+
expect(result.valid).toBe(true);
|
|
113
|
+
expect(result.paymentMethods.length).toBe(2);
|
|
114
|
+
const lightning = result.paymentMethods.find(
|
|
115
|
+
(pm) => pm.type === "lightning",
|
|
116
|
+
);
|
|
117
|
+
expect(lightning).toBeDefined();
|
|
118
|
+
expect(lightning?.valid).toBe(true);
|
|
119
|
+
expect(lightning?.network).toBe("mainnet");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("parses lightning invoice without fallback", () => {
|
|
123
|
+
const result = parseBIP321(
|
|
124
|
+
"bitcoin:?lightning=lnbc15u1p3xnhl2pp5jptserfk3zk4qy42tlucycrfwxhydvlemu9pqr93tuzlv9cc7g3sdqsvfhkcap3xyhx7un8cqzpgxqzjcsp5f8c52y2stc300gl6s4xswtjpc37hrnnr3c9wvtgjfuvqmpm35evq9qyyssqy4lgd8tj637qcjp05rdpxxykjenthxftej7a2zzmwrmrl70fyj9hvj0rewhzj7jfyuwkwcg9g2jpwtk3wkjtwnkdks84hsnu8xps5vsq4gj5hs",
|
|
125
|
+
);
|
|
126
|
+
expect(result.valid).toBe(true);
|
|
127
|
+
expect(result.address).toBeUndefined();
|
|
128
|
+
expect(result.paymentMethods.length).toBe(1);
|
|
129
|
+
expect(result.paymentMethods[0]!.type).toBe("lightning");
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("detects signet lightning invoice", () => {
|
|
133
|
+
const result = parseBIP321(
|
|
134
|
+
"bitcoin:?lightning=lntbs10u1p5s6wgtsp5d8a763exauvdk6s5gwvl8zmuapmgjq05fdv6trasjd4slvgkvzzqpp56vxdyl24hmkpz0tvqq84xdpqqeql3x7kh8tey4uum2cu8jny6djqdq4g9exkgznw3hhyefqyvenyxqzjccqp2rzjqdwy5et9ygczjl2jqmr9e5xm28u3gksjfrf0pht04uwz2lt9d59cypqelcqqq8gqqqqqqqqpqqqqqzsqqc9qxpqysgq0x0pg2s65rnp2cr35td5tq0vwgmnrghkpzt93eypqvvfu5m40pcjl9k2x2m4kqgvz2ez8tzxqgw0nyeg2w60nfky579uakd4mhr3ncgp0xwars",
|
|
135
|
+
);
|
|
136
|
+
expect(result.valid).toBe(true);
|
|
137
|
+
expect(result.paymentMethods[0]!.network).toBe("signet");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("detects regtest lightning invoice", () => {
|
|
141
|
+
const result = parseBIP321(
|
|
142
|
+
"bitcoin:?lightning=lnbcrt50u1p5s6w2zpp5juf0r9zutj4zv00kpuuqmgn246azqaq0u5kksx93p46ue94gpmrsdqqcqzzsxqyz5vqsp57u7clsm57nas7c0r2p4ujxr8whla6gxmwf44yqt9f862evjzd3ds9qxpqysgqrwvspjd8g3cfrkg2mrmxfdjcwk5nenw2qnmrys0rvkdmxes6jf5xfykunl5g9hnnahsnz0c90u7k42hmr7w90c0qkw3lllwy40mmqgsqjtyzpd",
|
|
143
|
+
);
|
|
144
|
+
expect(result.valid).toBe(true);
|
|
145
|
+
expect(result.paymentMethods[0]!.network).toBe("regtest");
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("detects testnet lightning invoice", () => {
|
|
149
|
+
const result = parseBIP321(
|
|
150
|
+
"bitcoin:?lightning=lntb2500n1pwxlkl5pp5g8hz28tlf950ps942lu3dknfete8yax2ctywpwjs872x9kngvvuqdqage5hyum5yp6x2um5yp5kuan0d93k2cqzyskdc5s2ltgm9kklz42x3e4tggdd9lcep2s9t2yk54gnfxg48wxushayrt52zjmua43gdnxmuc5s0c8g29ja9vnxs6x3kxgsha07htcacpmdyl64",
|
|
151
|
+
);
|
|
152
|
+
expect(result.valid).toBe(true);
|
|
153
|
+
expect(result.paymentMethods[0]!.network).toBe("testnet");
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("rejects testnet address in bc parameter", () => {
|
|
157
|
+
const result = parseBIP321(
|
|
158
|
+
"bitcoin:?bc=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
|
|
159
|
+
);
|
|
160
|
+
expect(result.valid).toBe(false);
|
|
161
|
+
expect(result.errors.some((e) => e.includes("network mismatch"))).toBe(
|
|
162
|
+
true,
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
describe("Alternative Payment Methods", () => {
|
|
168
|
+
test("parses BOLT12 offer", () => {
|
|
169
|
+
const result = parseBIP321("bitcoin:?lno=lno1bogusoffer");
|
|
170
|
+
expect(result.valid).toBe(true);
|
|
171
|
+
expect(result.paymentMethods.length).toBe(1);
|
|
172
|
+
expect(result.paymentMethods[0]!.type).toBe("lno");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("parses silent payment address", () => {
|
|
176
|
+
const result = parseBIP321("bitcoin:?sp=sp1qsilentpayment");
|
|
177
|
+
expect(result.valid).toBe(true);
|
|
178
|
+
expect(result.paymentMethods.length).toBe(1);
|
|
179
|
+
expect(result.paymentMethods[0]!.type).toBe("silent-payment");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("parses multiple payment methods", () => {
|
|
183
|
+
const result = parseBIP321(
|
|
184
|
+
"bitcoin:?lno=lno1bogusoffer&sp=sp1qsilentpayment",
|
|
185
|
+
);
|
|
186
|
+
expect(result.valid).toBe(true);
|
|
187
|
+
expect(result.paymentMethods.length).toBe(2);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("parses private payment address", () => {
|
|
191
|
+
const result = parseBIP321("bitcoin:?pay=bip351address");
|
|
192
|
+
expect(result.valid).toBe(true);
|
|
193
|
+
expect(result.paymentMethods[0]!.type).toBe("private-payment");
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
describe("Network-specific Parameters", () => {
|
|
198
|
+
test("parses bc parameter for mainnet", () => {
|
|
199
|
+
const result = parseBIP321(
|
|
200
|
+
"bitcoin:?bc=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
|
|
201
|
+
);
|
|
202
|
+
expect(result.valid).toBe(true);
|
|
203
|
+
expect(result.paymentMethods[0]!.network).toBe("mainnet");
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("parses tb parameter for testnet", () => {
|
|
207
|
+
const result = parseBIP321(
|
|
208
|
+
"bitcoin:?tb=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
|
|
209
|
+
);
|
|
210
|
+
expect(result.valid).toBe(true);
|
|
211
|
+
expect(result.paymentMethods[0]!.network).toBe("testnet");
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("rejects testnet address in bc parameter", () => {
|
|
215
|
+
const result = parseBIP321(
|
|
216
|
+
"bitcoin:?bc=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
|
|
217
|
+
);
|
|
218
|
+
expect(result.valid).toBe(false);
|
|
219
|
+
expect(result.errors.some((e) => e.includes("network mismatch"))).toBe(
|
|
220
|
+
true,
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("parses multiple segwit versions", () => {
|
|
225
|
+
const result = parseBIP321(
|
|
226
|
+
"bitcoin:?bc=bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq&bc=bc1pdyp8m5mhurxa9mf822jegnhu49g2zcchgcq8jzrjxg58u2lvudyqftt43a",
|
|
227
|
+
);
|
|
228
|
+
expect(result.valid).toBe(true);
|
|
229
|
+
expect(result.paymentMethods.length).toBe(2);
|
|
230
|
+
expect(result.paymentMethods[0]!.network).toBe("mainnet");
|
|
231
|
+
expect(result.paymentMethods[1]!.network).toBe("mainnet");
|
|
232
|
+
});
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
describe("Proof of Payment", () => {
|
|
236
|
+
test("parses pop parameter", () => {
|
|
237
|
+
const result = parseBIP321(
|
|
238
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?pop=initiatingapp%3a",
|
|
239
|
+
);
|
|
240
|
+
expect(result.valid).toBe(true);
|
|
241
|
+
expect(result.pop).toBe("initiatingapp%3a");
|
|
242
|
+
expect(result.popRequired).toBe(false);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test("parses req-pop parameter", () => {
|
|
246
|
+
const result = parseBIP321(
|
|
247
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?req-pop=callbackuri%3a",
|
|
248
|
+
);
|
|
249
|
+
expect(result.valid).toBe(true);
|
|
250
|
+
expect(result.pop).toBe("callbackuri%3a");
|
|
251
|
+
expect(result.popRequired).toBe(true);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test("rejects forbidden http scheme in pop", () => {
|
|
255
|
+
const result = parseBIP321(
|
|
256
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?pop=https%3aiwantyouripaddress.com",
|
|
257
|
+
);
|
|
258
|
+
expect(
|
|
259
|
+
result.errors.some((e) => e.includes("Forbidden pop scheme")),
|
|
260
|
+
).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test("rejects payment when req-pop uses forbidden scheme", () => {
|
|
264
|
+
const result = parseBIP321(
|
|
265
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?req-pop=https%3aevilwebsite.com",
|
|
266
|
+
);
|
|
267
|
+
expect(result.valid).toBe(false);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("rejects multiple pop parameters", () => {
|
|
271
|
+
const result = parseBIP321(
|
|
272
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?pop=callback%3a&req-pop=callback%3a",
|
|
273
|
+
);
|
|
274
|
+
expect(result.valid).toBe(false);
|
|
275
|
+
expect(result.errors.some((e) => e.includes("Multiple pop"))).toBe(true);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
describe("Required Parameters", () => {
|
|
280
|
+
test("rejects unknown required parameters", () => {
|
|
281
|
+
const result = parseBIP321(
|
|
282
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?req-somethingyoudontunderstand=50",
|
|
283
|
+
);
|
|
284
|
+
expect(result.valid).toBe(false);
|
|
285
|
+
expect(result.requiredParams.length).toBeGreaterThan(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("accepts unknown optional parameters", () => {
|
|
289
|
+
const result = parseBIP321(
|
|
290
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?somethingyoudontunderstand=50",
|
|
291
|
+
);
|
|
292
|
+
expect(result.valid).toBe(true);
|
|
293
|
+
expect(result.optionalParams.somethingyoudontunderstand).toEqual(["50"]);
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("Invalid URIs", () => {
|
|
298
|
+
test("rejects non-bitcoin URI", () => {
|
|
299
|
+
const result = parseBIP321("ethereum:0x123");
|
|
300
|
+
expect(result.valid).toBe(false);
|
|
301
|
+
expect(result.errors).toContain("Invalid URI: must start with bitcoin:");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("rejects empty URI", () => {
|
|
305
|
+
const result = parseBIP321("");
|
|
306
|
+
expect(result.valid).toBe(false);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
test("rejects invalid bitcoin address", () => {
|
|
310
|
+
const result = parseBIP321("bitcoin:invalidaddress123");
|
|
311
|
+
expect(result.valid).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
test("rejects URI with no payment methods", () => {
|
|
315
|
+
const result = parseBIP321("bitcoin:?label=test");
|
|
316
|
+
expect(result.valid).toBe(false);
|
|
317
|
+
expect(result.errors).toContain("No valid payment methods found");
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
describe("Helper Functions", () => {
|
|
322
|
+
test("getPaymentMethodsByNetwork groups correctly", () => {
|
|
323
|
+
const result = parseBIP321(
|
|
324
|
+
"bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?tb=tb1qghfhmd4zh7ncpmxl3qzhmq566jk8ckq4gafnmg",
|
|
325
|
+
);
|
|
326
|
+
const byNetwork = getPaymentMethodsByNetwork(result);
|
|
327
|
+
expect(byNetwork.mainnet!.length).toBe(1);
|
|
328
|
+
expect(byNetwork.testnet!.length).toBe(1);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
test("getValidPaymentMethods filters correctly", () => {
|
|
332
|
+
const result = parseBIP321(
|
|
333
|
+
"bitcoin:bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq?lightning=invalidinvoice",
|
|
334
|
+
);
|
|
335
|
+
const valid = getValidPaymentMethods(result);
|
|
336
|
+
expect(valid.length).toBe(1);
|
|
337
|
+
expect(valid[0]!.type).toBe("onchain");
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("formatPaymentMethodsSummary produces output", () => {
|
|
341
|
+
const result = parseBIP321(
|
|
342
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1.5&label=Test",
|
|
343
|
+
);
|
|
344
|
+
const summary = formatPaymentMethodsSummary(result);
|
|
345
|
+
expect(summary).toContain("Valid: true");
|
|
346
|
+
expect(summary).toContain("Amount: 1.5 BTC");
|
|
347
|
+
expect(summary).toContain("Label: Test");
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
describe("Case Insensitivity", () => {
|
|
352
|
+
test("handles mixed case in parameters", () => {
|
|
353
|
+
const result = parseBIP321(
|
|
354
|
+
"bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?AMOUNT=1.5&Label=Test",
|
|
355
|
+
);
|
|
356
|
+
expect(result.valid).toBe(true);
|
|
357
|
+
expect(result.amount).toBe(1.5);
|
|
358
|
+
expect(result.label).toBe("Test");
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
});
|