mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-05 13:25:48 +00:00
Compare commits
2673 Commits
0.9.2-rc6
...
ripple-key
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f96f38d0d9 | ||
|
|
81dbc2ed87 | ||
|
|
7cb38591ac | ||
|
|
be99161e89 | ||
|
|
afd6aadaf1 | ||
|
|
3b523d7e37 | ||
|
|
1902a2d885 | ||
|
|
f1c5f1d10d | ||
|
|
b90710ffa5 | ||
|
|
0d6e723a7e | ||
|
|
b5beeb6668 | ||
|
|
9f19d771d2 | ||
|
|
43b55ab598 | ||
|
|
99c817ca8a | ||
|
|
7dd842f995 | ||
|
|
9647145150 | ||
|
|
e813ec4bd3 | ||
|
|
1106ad1405 | ||
|
|
3973d0f5c5 | ||
|
|
14395ab58f | ||
|
|
910a5999c9 | ||
|
|
d1b34bb458 | ||
|
|
217b94e2fa | ||
|
|
73bc7152ca | ||
|
|
10c309ba70 | ||
|
|
f29e79eb68 | ||
|
|
4110c18301 | ||
|
|
63a5885e88 | ||
|
|
a97b42d18a | ||
|
|
8feafb216d | ||
|
|
bd154fd6e8 | ||
|
|
a7c9a88bcf | ||
|
|
dd7c2fba8e | ||
|
|
38dd5d1d7b | ||
|
|
0eadbfb790 | ||
|
|
d502d4aac7 | ||
|
|
6bbd593ed7 | ||
|
|
3503ed0f78 | ||
|
|
61b2cb7fb6 | ||
|
|
42ff3ec6d0 | ||
|
|
f55556c2a6 | ||
|
|
906ee4c55e | ||
|
|
7c0ac278b1 | ||
|
|
e2141da07c | ||
|
|
555b3fe44d | ||
|
|
7195947d53 | ||
|
|
f1e6858ec8 | ||
|
|
7064c1f7a9 | ||
|
|
294995c018 | ||
|
|
9221352c3c | ||
|
|
10c53d0d3a | ||
|
|
09a0f2bbcb | ||
|
|
a2f7fe6e23 | ||
|
|
29f077612b | ||
|
|
9338f8479f | ||
|
|
6b7ef86ae8 | ||
|
|
17d75fc44a | ||
|
|
3d1c445eb8 | ||
|
|
da28f57819 | ||
|
|
65dca89fb2 | ||
|
|
01ad95ad3f | ||
|
|
6e57b4e2ee | ||
|
|
b71147416c | ||
|
|
017db295e3 | ||
|
|
8b8c081bcd | ||
|
|
c107d4a716 | ||
|
|
c59ca2bda8 | ||
|
|
93252130fd | ||
|
|
5eb3db5395 | ||
|
|
943c043c0b | ||
|
|
d12c1bac9d | ||
|
|
2d5755fc88 | ||
|
|
f743e374eb | ||
|
|
ede1500afa | ||
|
|
1a8bbfa43e | ||
|
|
75e56ca927 | ||
|
|
615504db22 | ||
|
|
df4c9dc2fd | ||
|
|
24308eb3ae | ||
|
|
356b285681 | ||
|
|
8556f78f41 | ||
|
|
f93b1f241e | ||
|
|
9ad2b28172 | ||
|
|
cb55d2eacb | ||
|
|
43c71f4d9b | ||
|
|
aeb5770f61 | ||
|
|
7e70142044 | ||
|
|
0674d21134 | ||
|
|
5e515a9105 | ||
|
|
e5f6e4a825 | ||
|
|
23c5938591 | ||
|
|
8d0cc9c76f | ||
|
|
925edbeb1c | ||
|
|
f51f049c66 | ||
|
|
5251e10781 | ||
|
|
3c2e9f60da | ||
|
|
e79508ceda | ||
|
|
c9e00f8b09 | ||
|
|
f89490ad4e | ||
|
|
36a4c267b6 | ||
|
|
90dae91fb9 | ||
|
|
537401e161 | ||
|
|
403b0c402a | ||
|
|
0644e0467b | ||
|
|
578f35dd35 | ||
|
|
a1dce6b1e0 | ||
|
|
8df224232c | ||
|
|
c3e68cc83f | ||
|
|
02fd0ce323 | ||
|
|
5b9e3dbdc4 | ||
|
|
e65d686cc7 | ||
|
|
b77e2590b3 | ||
|
|
9e3654d7d6 | ||
|
|
edcdd3a0fc | ||
|
|
f5dee87ca7 | ||
|
|
b522e703b4 | ||
|
|
0be819cf37 | ||
|
|
afe06451ac | ||
|
|
da92bb7f1a | ||
|
|
0996dc13b7 | ||
|
|
b3a72588ea | ||
|
|
1747b31a02 | ||
|
|
b7c4b16a8d | ||
|
|
bfeb737ad7 | ||
|
|
cc0b5a4ac9 | ||
|
|
dcfc31a036 | ||
|
|
955e21717a | ||
|
|
5200682915 | ||
|
|
297dee9b8f | ||
|
|
5d67f14ea3 | ||
|
|
564001515d | ||
|
|
b5b2909fed | ||
|
|
2a93f85f41 | ||
|
|
dd068c2710 | ||
|
|
f2216446e5 | ||
|
|
98c9b9bc14 | ||
|
|
57a6586898 | ||
|
|
c5f47cbffc | ||
|
|
6dba1b142c | ||
|
|
d734d886c4 | ||
|
|
8e1ab6c32b | ||
|
|
dac8c27b49 | ||
|
|
6eec7b0b77 | ||
|
|
94a8c65200 | ||
|
|
6fac420d9f | ||
|
|
05e1d4d3c5 | ||
|
|
e53df109b0 | ||
|
|
dbb134839a | ||
|
|
13c52859eb | ||
|
|
eb0445817e | ||
|
|
10445cff01 | ||
|
|
3cbdbac4f9 | ||
|
|
fd78d1edcd | ||
|
|
603b7ae85c | ||
|
|
fcc8977623 | ||
|
|
b9b32eb3d2 | ||
|
|
1037e0da88 | ||
|
|
29c0b84ad5 | ||
|
|
4f1e1d653c | ||
|
|
fe919315d4 | ||
|
|
3f29e5781d | ||
|
|
0dc1e08350 | ||
|
|
148cac6f3f | ||
|
|
0d32071e0e | ||
|
|
e7e0ece78b | ||
|
|
dc6baf7f39 | ||
|
|
851b352f32 | ||
|
|
633032ddd8 | ||
|
|
33f83947f1 | ||
|
|
af7b187dc7 | ||
|
|
1115f17a3e | ||
|
|
ac66c2174a | ||
|
|
eb56eb181a | ||
|
|
c401f703c7 | ||
|
|
04dd65af38 | ||
|
|
949cc031ee | ||
|
|
c8a2a6690b | ||
|
|
a996fafe79 | ||
|
|
e200de3073 | ||
|
|
8c5bc22317 | ||
|
|
aa6cef520c | ||
|
|
2ca164311b | ||
|
|
3f365d8591 | ||
|
|
e0f4d99d86 | ||
|
|
62538a75b1 | ||
|
|
91cc9e0461 | ||
|
|
b53bc2bc97 | ||
|
|
75f0bb4617 | ||
|
|
6268b9ea26 | ||
|
|
43802f9e22 | ||
|
|
b8be6c2f1b | ||
|
|
759e075e54 | ||
|
|
8b95ee5fab | ||
|
|
12cfed5c17 | ||
|
|
09ef8595e7 | ||
|
|
f9fe5936b1 | ||
|
|
da9feffada | ||
|
|
8e52854773 | ||
|
|
5120b0fc83 | ||
|
|
52f1789ecd | ||
|
|
0b08de5956 | ||
|
|
59396c3f8f | ||
|
|
f49b9d4b0e | ||
|
|
478e147ae0 | ||
|
|
d324056203 | ||
|
|
db8f7c1bcb | ||
|
|
4469d1cbf8 | ||
|
|
05365a8690 | ||
|
|
f6b9878334 | ||
|
|
f68eb37565 | ||
|
|
f95ffee0b0 | ||
|
|
d5d996a92e | ||
|
|
2ff0dde91d | ||
|
|
bec487cf71 | ||
|
|
c1edab547a | ||
|
|
fef5f858fd | ||
|
|
72f34d9388 | ||
|
|
930d214107 | ||
|
|
57c4d8be39 | ||
|
|
73109295b4 | ||
|
|
6d08b9e12c | ||
|
|
293f09d409 | ||
|
|
f9bce29174 | ||
|
|
7fde5a2658 | ||
|
|
d438430100 | ||
|
|
c431e70900 | ||
|
|
51b4ff7a2c | ||
|
|
ec54841617 | ||
|
|
2166d8349e | ||
|
|
1ecd6a7e2a | ||
|
|
a0907472c9 | ||
|
|
57112c086c | ||
|
|
b7f39e7225 | ||
|
|
f16876014c | ||
|
|
68ac32fc06 | ||
|
|
0886af33fd | ||
|
|
f5f1534f12 | ||
|
|
6e4868e6c7 | ||
|
|
718b0ac5a5 | ||
|
|
6e2db6db20 | ||
|
|
d08a62b9a1 | ||
|
|
7669304fbe | ||
|
|
575892d044 | ||
|
|
f58a938bb8 | ||
|
|
83d04bea77 | ||
|
|
43dbaf6b61 | ||
|
|
845d70fefc | ||
|
|
8a815e17ca | ||
|
|
0b1064fead | ||
|
|
9d77ea0161 | ||
|
|
283c2edb10 | ||
|
|
c41f4520b3 | ||
|
|
569c75103c | ||
|
|
25d5d408d8 | ||
|
|
0845ff786b | ||
|
|
ecdeaf700e | ||
|
|
4413f78d22 | ||
|
|
d3c6d775af | ||
|
|
49092d5dc1 | ||
|
|
3e79959e11 | ||
|
|
7c528f7ac0 | ||
|
|
e5a2096c57 | ||
|
|
79a2c8f8f3 | ||
|
|
bf84b66b72 | ||
|
|
af95916e79 | ||
|
|
6acbf3ef43 | ||
|
|
418f80bf6d | ||
|
|
ec121ee62d | ||
|
|
decc395e5b | ||
|
|
8b79fe6ba1 | ||
|
|
5f0c38531c | ||
|
|
69113de552 | ||
|
|
b81c1dab35 | ||
|
|
20ed4640a7 | ||
|
|
42464b84de | ||
|
|
76780c8a8e | ||
|
|
25a2bcd3be | ||
|
|
d9a42c8669 | ||
|
|
6e0fff2ad6 | ||
|
|
c93617defd | ||
|
|
5436b55db0 | ||
|
|
af176c54e6 | ||
|
|
2ffb774eb2 | ||
|
|
4e49b6a99c | ||
|
|
10efd5eedb | ||
|
|
de293de1e6 | ||
|
|
5df32e2e6e | ||
|
|
31e012bde5 | ||
|
|
1e66f40348 | ||
|
|
9adfd404e5 | ||
|
|
d941653477 | ||
|
|
cdf9e33ad2 | ||
|
|
b7de3b0ea9 | ||
|
|
aa081a4348 | ||
|
|
e3752c9057 | ||
|
|
be1267fb14 | ||
|
|
dd30e33e8e | ||
|
|
a973266434 | ||
|
|
0ac1061d26 | ||
|
|
6938773a22 | ||
|
|
81668a8282 | ||
|
|
22a77653af | ||
|
|
6e1e2081c9 | ||
|
|
97cc99a1c0 | ||
|
|
fee832be3a | ||
|
|
e35aca7a40 | ||
|
|
53f35b5a99 | ||
|
|
44d91d8d96 | ||
|
|
14d47f62e8 | ||
|
|
be04427145 | ||
|
|
d0229d2c9d | ||
|
|
250d0763d7 | ||
|
|
3db42279e1 | ||
|
|
ba7c589545 | ||
|
|
eaf8593960 | ||
|
|
ee30927304 | ||
|
|
b58ef11e57 | ||
|
|
c726a5eab8 | ||
|
|
1e86ba0aa0 | ||
|
|
cabd4cc88d | ||
|
|
3700004d1f | ||
|
|
aa9dc9e992 | ||
|
|
c1af170a18 | ||
|
|
41256eedeb | ||
|
|
ce332e14a0 | ||
|
|
00991caf43 | ||
|
|
534772b9b0 | ||
|
|
fc0b39022e | ||
|
|
b3bcfeee20 | ||
|
|
28d2fc2a3a | ||
|
|
42db44fb59 | ||
|
|
ad0165cf22 | ||
|
|
708d8c0bb5 | ||
|
|
5074441b3b | ||
|
|
a1edc0b2d5 | ||
|
|
3afdc1fca0 | ||
|
|
6b7cdfc413 | ||
|
|
95e1314eb9 | ||
|
|
ad0cc209af | ||
|
|
dd11ce480d | ||
|
|
10ace18d27 | ||
|
|
e8ca25f792 | ||
|
|
95d626f69e | ||
|
|
3fdc56ab90 | ||
|
|
bb44598e32 | ||
|
|
3c86b04fee | ||
|
|
7ee87f2625 | ||
|
|
2f03347dda | ||
|
|
bbb19dce9f | ||
|
|
c8b0035897 | ||
|
|
67d1b5278d | ||
|
|
43e444b47d | ||
|
|
8797ee2f65 | ||
|
|
23dd7b16f4 | ||
|
|
6e9e2e390f | ||
|
|
7c6791e55f | ||
|
|
f3fd468566 | ||
|
|
97d4f1f2ce | ||
|
|
fbecaf2ddc | ||
|
|
978a5bcd99 | ||
|
|
650d722609 | ||
|
|
5a2225751a | ||
|
|
ed6ca70053 | ||
|
|
a47e5794ae | ||
|
|
bd2e4629ca | ||
|
|
b57104adc1 | ||
|
|
bc19366328 | ||
|
|
ef1f8752d9 | ||
|
|
6fd0b3a5f1 | ||
|
|
362bb13da1 | ||
|
|
d76a5fc87a | ||
|
|
824f87a692 | ||
|
|
fa2385ee1c | ||
|
|
2be7a8fa94 | ||
|
|
7d6a3a7518 | ||
|
|
61692a5e34 | ||
|
|
e82c585955 | ||
|
|
bc50603111 | ||
|
|
debb9cb3a5 | ||
|
|
4fd74b3671 | ||
|
|
eb4ac74ce6 | ||
|
|
cf4d2b2c1a | ||
|
|
c79b044aaa | ||
|
|
32718d583b | ||
|
|
c3965f325b | ||
|
|
7b77177962 | ||
|
|
3781b798a1 | ||
|
|
31314212a5 | ||
|
|
7d36cfa068 | ||
|
|
848c179fd4 | ||
|
|
c81ed7af2f | ||
|
|
3c09e442f2 | ||
|
|
e2cecd07e4 | ||
|
|
937dfa9664 | ||
|
|
8983782562 | ||
|
|
37e95b8394 | ||
|
|
08b76fba22 | ||
|
|
2642589ea4 | ||
|
|
3d3f50ad05 | ||
|
|
70e6976d7a | ||
|
|
4b228842df | ||
|
|
9fafdf1449 | ||
|
|
c32afaf587 | ||
|
|
ef50cb8933 | ||
|
|
440c12acc7 | ||
|
|
430b799de5 | ||
|
|
965dc4bd87 | ||
|
|
c9689ec2a8 | ||
|
|
2aa6551693 | ||
|
|
a6d1151290 | ||
|
|
d115aa357b | ||
|
|
6e3321cfef | ||
|
|
0cbca4b876 | ||
|
|
ef31c1882c | ||
|
|
1db47d3da3 | ||
|
|
9a3ef216f9 | ||
|
|
e8817d9e0b | ||
|
|
abb1d23ee2 | ||
|
|
9636849c63 | ||
|
|
4aa76b38f9 | ||
|
|
b7dcb4202e | ||
|
|
6879af37db | ||
|
|
50a3f5a064 | ||
|
|
42aa9a53fa | ||
|
|
70266cb05c | ||
|
|
8a57b616c8 | ||
|
|
2b60c27653 | ||
|
|
03655b4ed2 | ||
|
|
f1ec45769b | ||
|
|
5db1f5668c | ||
|
|
dd4c6a0353 | ||
|
|
95a2655501 | ||
|
|
2ba31c5b75 | ||
|
|
33fb35138f | ||
|
|
ed26f9a763 | ||
|
|
415a61f06a | ||
|
|
4eb64b5e72 | ||
|
|
9e96fa3473 | ||
|
|
1c1a3fa583 | ||
|
|
c985838cdd | ||
|
|
81dfd99642 | ||
|
|
9f6fa6a4fd | ||
|
|
6b4fa159ea | ||
|
|
782787a5b9 | ||
|
|
fd0f64fe54 | ||
|
|
c7e08378ac | ||
|
|
382cf4cb1f | ||
|
|
be71af5c55 | ||
|
|
97f9812876 | ||
|
|
dcc50e1b36 | ||
|
|
ae8824fb11 | ||
|
|
337ab2993b | ||
|
|
a4782764dd | ||
|
|
0a4c28f799 | ||
|
|
1470a0d234 | ||
|
|
bc19db9ddd | ||
|
|
2f45bac39a | ||
|
|
745afc6fef | ||
|
|
12a7213ebb | ||
|
|
d96b0d3986 | ||
|
|
a5c35586f7 | ||
|
|
a54655c0ff | ||
|
|
9a51f76a24 | ||
|
|
504c968d6d | ||
|
|
044a46b433 | ||
|
|
0accba9a55 | ||
|
|
29eeb7ee4e | ||
|
|
cf1f15b300 | ||
|
|
68761d4fbd | ||
|
|
7f353f6da5 | ||
|
|
a735899d56 | ||
|
|
0bcc478444 | ||
|
|
cee2dfdcaa | ||
|
|
2aae25b458 | ||
|
|
5249e5a418 | ||
|
|
3f834d5d98 | ||
|
|
4a59644b74 | ||
|
|
2681e81d6b | ||
|
|
2a0234e5ce | ||
|
|
6b326a6efd | ||
|
|
8cd87dd804 | ||
|
|
e6f9e7d51c | ||
|
|
13f249a236 | ||
|
|
e7c02d584e | ||
|
|
48d2bf849f | ||
|
|
544ec4f80d | ||
|
|
70a9998e1d | ||
|
|
c28a3940e5 | ||
|
|
bbaed08e91 | ||
|
|
c8b8e89e20 | ||
|
|
02c6ae28dc | ||
|
|
3f703db480 | ||
|
|
e7dc49e222 | ||
|
|
9f3a286639 | ||
|
|
4ab808b6de | ||
|
|
e5496e84a6 | ||
|
|
83b5c7f678 | ||
|
|
1063ff024d | ||
|
|
5e4a685f2c | ||
|
|
e6c9617e01 | ||
|
|
6c5fcc3dc6 | ||
|
|
2931bb2863 | ||
|
|
d745b128e3 | ||
|
|
73b952ec0d | ||
|
|
076d02cceb | ||
|
|
36105d9dc5 | ||
|
|
df4c413d83 | ||
|
|
63f4aa2b8d | ||
|
|
186968b5b5 | ||
|
|
ed3995abf1 | ||
|
|
26c33d614f | ||
|
|
8cbb09b5fe | ||
|
|
004db42d98 | ||
|
|
ccad0c66e5 | ||
|
|
6cccbb434e | ||
|
|
be961fb9a7 | ||
|
|
851d84bde8 | ||
|
|
854c4ebfdd | ||
|
|
a77448f7c0 | ||
|
|
0dc33f3d88 | ||
|
|
aaff0257b0 | ||
|
|
3557a57bbd | ||
|
|
051d23edff | ||
|
|
ccb91c1268 | ||
|
|
6c1c0eee59 | ||
|
|
f1c1c7033a | ||
|
|
bc352c4cf0 | ||
|
|
1980fa9fa4 | ||
|
|
6cabb2e935 | ||
|
|
05411527ee | ||
|
|
3c13da66b3 | ||
|
|
5d6af09508 | ||
|
|
15bf721d24 | ||
|
|
14351c9512 | ||
|
|
3b13de5310 | ||
|
|
f92eff2df8 | ||
|
|
a65ac5f8f0 | ||
|
|
0e36a1c505 | ||
|
|
f5bed635e0 | ||
|
|
905ab9f2e4 | ||
|
|
547b63b891 | ||
|
|
c26ddb497e | ||
|
|
2e81cfb56f | ||
|
|
337cb6574a | ||
|
|
a8db527131 | ||
|
|
070e9afc3d | ||
|
|
abcb6bfecb | ||
|
|
797fda3363 | ||
|
|
bd920ee5bb | ||
|
|
f34991c550 | ||
|
|
336829efe4 | ||
|
|
3828e22906 | ||
|
|
c0d2a9a9e3 | ||
|
|
030b9213a7 | ||
|
|
2720970e1f | ||
|
|
b7a12d4bbb | ||
|
|
b4f6135b96 | ||
|
|
7d65bf4641 | ||
|
|
656c81a72c | ||
|
|
bfd0374ef6 | ||
|
|
bf1a772e40 | ||
|
|
8ede100594 | ||
|
|
927f1f6d9a | ||
|
|
1aca11d4c1 | ||
|
|
0be4c6f233 | ||
|
|
912eea5037 | ||
|
|
06a029b89c | ||
|
|
19f9054590 | ||
|
|
df708a77d2 | ||
|
|
31232ad50c | ||
|
|
7f4ababba1 | ||
|
|
6d8c2db253 | ||
|
|
7abaf61e11 | ||
|
|
903a6e31b8 | ||
|
|
bb91364c03 | ||
|
|
b3023292c5 | ||
|
|
2eb5898e8b | ||
|
|
dc2bc0291b | ||
|
|
1357f7eeb4 | ||
|
|
b0cb0a759b | ||
|
|
97413d0d1b | ||
|
|
7d2e957bb5 | ||
|
|
19d0ca6bfc | ||
|
|
17f88fed3b | ||
|
|
af69197e53 | ||
|
|
dd9a8e4b25 | ||
|
|
f138a07436 | ||
|
|
26d03fe2a5 | ||
|
|
dfa61df40a | ||
|
|
7a7450d02f | ||
|
|
74e0e9540c | ||
|
|
7469836815 | ||
|
|
6a8bf232a1 | ||
|
|
d154cced14 | ||
|
|
80b96d9bc9 | ||
|
|
8fa30f71eb | ||
|
|
804094b1ce | ||
|
|
9caf077b58 | ||
|
|
1a5ba06ca3 | ||
|
|
657cad9ffd | ||
|
|
a338a936db | ||
|
|
9f09a4615e | ||
|
|
a2200a1d96 | ||
|
|
34dab51e2f | ||
|
|
d3218906a3 | ||
|
|
476401adea | ||
|
|
226e10bca2 | ||
|
|
3a5a989011 | ||
|
|
01e94ad4d6 | ||
|
|
4d0c280996 | ||
|
|
c9720ef061 | ||
|
|
b6927f178f | ||
|
|
adaa75d180 | ||
|
|
e199878ea4 | ||
|
|
11ff184438 | ||
|
|
4d3b5a294c | ||
|
|
edd35ea9e2 | ||
|
|
6b40e4fe9d | ||
|
|
59ec56db4c | ||
|
|
a8119d678a | ||
|
|
5ec22b4bc8 | ||
|
|
8e38e313b2 | ||
|
|
b7b75d78ae | ||
|
|
824efb6b59 | ||
|
|
9b298e0037 | ||
|
|
963017ab78 | ||
|
|
c151ca202c | ||
|
|
b9a64c92e7 | ||
|
|
bcaa06721a | ||
|
|
06227ef12b | ||
|
|
2c9da17fa9 | ||
|
|
c17827e030 | ||
|
|
97ca0f0b21 | ||
|
|
e4e6419e50 | ||
|
|
550102ac26 | ||
|
|
0382e75f82 | ||
|
|
5bca020945 | ||
|
|
3595f92dfc | ||
|
|
d97e15f5c3 | ||
|
|
41d9b0b660 | ||
|
|
d2859382fe | ||
|
|
fb3f42751c | ||
|
|
45911b7190 | ||
|
|
2af4340392 | ||
|
|
a7bfc2f4ae | ||
|
|
f8e80f7132 | ||
|
|
03b68e8a53 | ||
|
|
65ff85ade1 | ||
|
|
fd8c883cf4 | ||
|
|
f1a4af4170 | ||
|
|
92d6da321c | ||
|
|
a2280e74e1 | ||
|
|
dee3a24739 | ||
|
|
47b0b1de8d | ||
|
|
5b8109b5f9 | ||
|
|
6a110fe1b8 | ||
|
|
6b66a59673 | ||
|
|
46177338c2 | ||
|
|
6fcff9b106 | ||
|
|
208f5f6c5c | ||
|
|
de3e2a9867 | ||
|
|
a6b4308a49 | ||
|
|
40eea3c659 | ||
|
|
2f17688fa5 | ||
|
|
4f9b6b9186 | ||
|
|
4a848ec527 | ||
|
|
10414e169c | ||
|
|
1a6c68d028 | ||
|
|
9156734ced | ||
|
|
0dfe3ff4ac | ||
|
|
3a8c7f02cc | ||
|
|
e03b192fcc | ||
|
|
42fe725da6 | ||
|
|
994fce1c35 | ||
|
|
53e332f42e | ||
|
|
cf40bd2c30 | ||
|
|
4082e88416 | ||
|
|
5229342fc4 | ||
|
|
27abc10d93 | ||
|
|
d6474d71f2 | ||
|
|
c3dadd0fbb | ||
|
|
d57603e854 | ||
|
|
ac92584678 | ||
|
|
c234be0a8c | ||
|
|
7c6b8398cf | ||
|
|
7de677c953 | ||
|
|
aa23f44555 | ||
|
|
9dba3a275f | ||
|
|
bc4ae8742c | ||
|
|
901d75a1eb | ||
|
|
aa95286810 | ||
|
|
f6b3f661d6 | ||
|
|
0850d85791 | ||
|
|
c564400ac4 | ||
|
|
94ab545ffe | ||
|
|
037d30f3b3 | ||
|
|
e10df203b7 | ||
|
|
07775656a9 | ||
|
|
eea20a6eab | ||
|
|
5f208801ee | ||
|
|
0a22697e5d | ||
|
|
30cf4f0b00 | ||
|
|
e4bb88a725 | ||
|
|
e3822e6bc3 | ||
|
|
20d3be0d1d | ||
|
|
1785863686 | ||
|
|
ea4ced3cc1 | ||
|
|
149008d18b | ||
|
|
55a21d2eec | ||
|
|
c7491e631a | ||
|
|
468a205e36 | ||
|
|
bebe951a57 | ||
|
|
85a8ab32ef | ||
|
|
34ddbe170c | ||
|
|
e9846eb249 | ||
|
|
7cc418ac93 | ||
|
|
69532a4f23 | ||
|
|
f59419d96f | ||
|
|
7f288d0555 | ||
|
|
53afa8c276 | ||
|
|
22f4dd2f75 | ||
|
|
0989152024 | ||
|
|
f74809d361 | ||
|
|
9724cf7776 | ||
|
|
909e5438a8 | ||
|
|
3c534d87c0 | ||
|
|
4022a59705 | ||
|
|
138e7942da | ||
|
|
d7d26a3ae1 | ||
|
|
23504821cf | ||
|
|
b09da3e8f1 | ||
|
|
f3dd2fec99 | ||
|
|
462e375800 | ||
|
|
ca8c881375 | ||
|
|
96605a57d4 | ||
|
|
491ce40081 | ||
|
|
f33eb07bdd | ||
|
|
8bb1dc9b47 | ||
|
|
78b50472da | ||
|
|
e0259b37ed | ||
|
|
bf863a2594 | ||
|
|
edd174881e | ||
|
|
0bf747f6fc | ||
|
|
ab4d2b5d58 | ||
|
|
1b81280358 | ||
|
|
32f4eea3b8 | ||
|
|
1a3a49decb | ||
|
|
416717aff6 | ||
|
|
769d955a40 | ||
|
|
6de85b841d | ||
|
|
66db127245 | ||
|
|
b4c6af29e4 | ||
|
|
7192606e21 | ||
|
|
932be02e9e | ||
|
|
fc524894c6 | ||
|
|
f5196389e8 | ||
|
|
27be06c5c9 | ||
|
|
1d3ddb5e85 | ||
|
|
2145c104fd | ||
|
|
64e0d098e7 | ||
|
|
50d8cbb0ee | ||
|
|
9580397558 | ||
|
|
cf544b74f5 | ||
|
|
312f831efb | ||
|
|
f5bad5d28e | ||
|
|
9b7d255200 | ||
|
|
7a027bdd93 | ||
|
|
439a611a9e | ||
|
|
5f92b230aa | ||
|
|
29bc5303ae | ||
|
|
5314e5e7e9 | ||
|
|
c88462c99b | ||
|
|
0b552f1a7e | ||
|
|
552635a3c7 | ||
|
|
ca769bee39 | ||
|
|
b7ca0a0a14 | ||
|
|
84097a3179 | ||
|
|
8ba36b2588 | ||
|
|
e1d4ebc5f6 | ||
|
|
9e712d6089 | ||
|
|
90bea3dc6b | ||
|
|
bf480bb971 | ||
|
|
a94b48be50 | ||
|
|
abed42d848 | ||
|
|
3d6e795ca5 | ||
|
|
a3cbe8e9d4 | ||
|
|
0a2000098a | ||
|
|
a2348b5133 | ||
|
|
20d2f9d894 | ||
|
|
321f908e76 | ||
|
|
49875cb0e5 | ||
|
|
fa6a2c5bbb | ||
|
|
1074c00b60 | ||
|
|
8a8b10541e | ||
|
|
46cf4d677c | ||
|
|
94587d7515 | ||
|
|
14ec58ef9a | ||
|
|
8f4f6f3de0 | ||
|
|
f8c0ac3ce0 | ||
|
|
8ebad98912 | ||
|
|
08429b6110 | ||
|
|
0e128e15f1 | ||
|
|
b77a12fd0d | ||
|
|
a98526b398 | ||
|
|
5639bf9d48 | ||
|
|
c626685103 | ||
|
|
e233d15fbb | ||
|
|
a5d83900d9 | ||
|
|
d8dbeedcc2 | ||
|
|
56b67d62a3 | ||
|
|
dc084b4bd9 | ||
|
|
1bc0eab7ae | ||
|
|
ca14d1b108 | ||
|
|
d6757aced2 | ||
|
|
5c84eed292 | ||
|
|
b648387a57 | ||
|
|
5232f95c3f | ||
|
|
0742960ec4 | ||
|
|
4c41b7f8df | ||
|
|
c5d0c24237 | ||
|
|
8b5c51ceaa | ||
|
|
c09bceb66a | ||
|
|
e7afd3ec76 | ||
|
|
3d30be3472 | ||
|
|
e08367365f | ||
|
|
6692fbeed4 | ||
|
|
36a9e7a7cf | ||
|
|
49b5ff5fd9 | ||
|
|
1bde56a11a | ||
|
|
f47d7b6935 | ||
|
|
2dbad40a34 | ||
|
|
39f6a51794 | ||
|
|
7ec128c2e4 | ||
|
|
b55f0e849e | ||
|
|
f158390ba1 | ||
|
|
e4b245104a | ||
|
|
789497b07e | ||
|
|
5cf01ba099 | ||
|
|
e8669891f8 | ||
|
|
ac0f265a5b | ||
|
|
fcd6b430e1 | ||
|
|
f3ad8a9b80 | ||
|
|
43ff824da1 | ||
|
|
b8022610ca | ||
|
|
03defe203a | ||
|
|
aedcbe56b3 | ||
|
|
c365db460a | ||
|
|
cfdc4752d0 | ||
|
|
e1964ac5ed | ||
|
|
e17ab9cd8f | ||
|
|
edc15b8727 | ||
|
|
034f8d41fc | ||
|
|
0fa70db1e1 | ||
|
|
fa7ba9b72b | ||
|
|
3a20123e0f | ||
|
|
03510d1bc4 | ||
|
|
8b116f637a | ||
|
|
9c49de6552 | ||
|
|
842347bcab | ||
|
|
628b9c4853 | ||
|
|
d60b6ee33f | ||
|
|
4f4fcbbc70 | ||
|
|
cc896670dc | ||
|
|
eb2a497dee | ||
|
|
968caa1f1f | ||
|
|
b9fd996547 | ||
|
|
e5fad4a521 | ||
|
|
2fe8162c26 | ||
|
|
7cfce53423 | ||
|
|
c3d5d95488 | ||
|
|
0cf5ce1416 | ||
|
|
988381d584 | ||
|
|
0b163eae23 | ||
|
|
3a3ff8a65e | ||
|
|
9f183a6dfc | ||
|
|
fadfd4e06c | ||
|
|
eb521faa8d | ||
|
|
6b572ca862 | ||
|
|
d075ec6716 | ||
|
|
14e6bf5ef9 | ||
|
|
b6bddd3b0e | ||
|
|
d5ed9b6cf5 | ||
|
|
7a9912d4e0 | ||
|
|
4d2ddceb4e | ||
|
|
ae2aed675a | ||
|
|
1d7cb41218 | ||
|
|
76db002eb5 | ||
|
|
ebb64ba177 | ||
|
|
1a685e2b68 | ||
|
|
9c561885a1 | ||
|
|
612e98b198 | ||
|
|
0a41d5ccf1 | ||
|
|
ba8fe1f32c | ||
|
|
0f840876a5 | ||
|
|
ac3900b6f4 | ||
|
|
5e138b9937 | ||
|
|
82d50cd903 | ||
|
|
c5b1d4daac | ||
|
|
b4a30d49d8 | ||
|
|
229360d1b9 | ||
|
|
d627d362af | ||
|
|
7ca7a07942 | ||
|
|
09f81fa3cd | ||
|
|
b03bf9c7f1 | ||
|
|
4d696369fe | ||
|
|
bcb80ea5f5 | ||
|
|
1be2ee5875 | ||
|
|
3554e807df | ||
|
|
5ffbd6e86d | ||
|
|
e2572c61cf | ||
|
|
7df2b74d43 | ||
|
|
f51a69f080 | ||
|
|
c0a2d9fc2b | ||
|
|
51dad19f88 | ||
|
|
3d1a530796 | ||
|
|
f6a22d2121 | ||
|
|
6f2d448059 | ||
|
|
0d5103a3f3 | ||
|
|
4da80028bf | ||
|
|
bb40dbde9d | ||
|
|
50fc36ec3e | ||
|
|
1abcef73a0 | ||
|
|
0d7879b25c | ||
|
|
8278dc5b5b | ||
|
|
c90d486454 | ||
|
|
35f9b7ec8d | ||
|
|
69e621af86 | ||
|
|
0e92e696d4 | ||
|
|
0b1445bfe9 | ||
|
|
db2d7ba1f5 | ||
|
|
d82703f41b | ||
|
|
8213861ab7 | ||
|
|
a842c380cf | ||
|
|
5bf6f1849a | ||
|
|
bfe4877f73 | ||
|
|
63dcddf6f4 | ||
|
|
68735ddb35 | ||
|
|
1fd9ca7ef2 | ||
|
|
2445004333 | ||
|
|
e9a3e1ab35 | ||
|
|
dc148bf954 | ||
|
|
f3c34bd75a | ||
|
|
5419e67dbc | ||
|
|
8d37da0952 | ||
|
|
a8075d98df | ||
|
|
fcc205b85a | ||
|
|
0f5056221f | ||
|
|
8384ace746 | ||
|
|
040cabece0 | ||
|
|
319a8d6ab2 | ||
|
|
50a7320886 | ||
|
|
418987476e | ||
|
|
89e4ff328c | ||
|
|
04bf49cb43 | ||
|
|
1c017df2a3 | ||
|
|
765bee5f81 | ||
|
|
bb9870271d | ||
|
|
5fed1f08e4 | ||
|
|
82c349c8c4 | ||
|
|
0f7af6a4e9 | ||
|
|
8db1791ed2 | ||
|
|
a29ec1b8c7 | ||
|
|
232a760a58 | ||
|
|
f4ad04d334 | ||
|
|
8dfec20871 | ||
|
|
64745017e9 | ||
|
|
37edede728 | ||
|
|
f785605db8 | ||
|
|
6bbf610b91 | ||
|
|
01ad30ab07 | ||
|
|
e08d507462 | ||
|
|
4c23bd5ad3 | ||
|
|
b15abd5376 | ||
|
|
79971f906b | ||
|
|
9eec98778f | ||
|
|
dcd0e14142 | ||
|
|
cfcbc9aab7 | ||
|
|
a80de5658a | ||
|
|
34215eb309 | ||
|
|
9458005d7f | ||
|
|
5f36df0172 | ||
|
|
3ff4929a49 | ||
|
|
4bea69d647 | ||
|
|
09541dae86 | ||
|
|
bcbcc53c87 | ||
|
|
76f120bec9 | ||
|
|
f8bf28876d | ||
|
|
b03795df09 | ||
|
|
dbe20d6574 | ||
|
|
0550fab73e | ||
|
|
3f2d9d198e | ||
|
|
b9c953fce6 | ||
|
|
181cfd69de | ||
|
|
569766b8f8 | ||
|
|
53a232ebdc | ||
|
|
4c9a2ff538 | ||
|
|
dc623cd049 | ||
|
|
7cd517268b | ||
|
|
2438295e70 | ||
|
|
f5e1a4a588 | ||
|
|
9e9a0a7d9b | ||
|
|
1c68283d1e | ||
|
|
28796d37cb | ||
|
|
067bc48d4e | ||
|
|
b94698df0b | ||
|
|
4f40b5cb6d | ||
|
|
14704eee6b | ||
|
|
860f6a6cd8 | ||
|
|
7a928804ec | ||
|
|
14444bea3f | ||
|
|
47a139fdff | ||
|
|
4e30b9b2fa | ||
|
|
2112d4c0b3 | ||
|
|
1d1132b7fa | ||
|
|
e07fa11923 | ||
|
|
7c92adbf45 | ||
|
|
d55aa2339f | ||
|
|
95e39153da | ||
|
|
65d8260908 | ||
|
|
1aa9feda71 | ||
|
|
2e5b435b11 | ||
|
|
54f12862dc | ||
|
|
226ef862ae | ||
|
|
4a0d675726 | ||
|
|
b2b6715ac0 | ||
|
|
823d93b86c | ||
|
|
7a42db99c9 | ||
|
|
f28ec27614 | ||
|
|
a36e23ebfa | ||
|
|
e978ef1888 | ||
|
|
9af3968508 | ||
|
|
86ce7b707c | ||
|
|
e0eb27ed8e | ||
|
|
2570e2a6d8 | ||
|
|
c71540e77a | ||
|
|
8eba827d11 | ||
|
|
90be539b09 | ||
|
|
43c08e5ea7 | ||
|
|
3f22b12216 | ||
|
|
a72041a321 | ||
|
|
71a0c16fec | ||
|
|
27ab98160a | ||
|
|
4eaaa8188d | ||
|
|
187154a2b0 | ||
|
|
c175e3f58e | ||
|
|
2ea22a099e | ||
|
|
c3aa062edd | ||
|
|
0ad8c577c0 | ||
|
|
facc513a52 | ||
|
|
df711ecb06 | ||
|
|
144be08af0 | ||
|
|
c53db4844a | ||
|
|
68b34f7532 | ||
|
|
3fe5d715a5 | ||
|
|
ca74cf1028 | ||
|
|
5b51ff6071 | ||
|
|
3cad4f4aa3 | ||
|
|
365de6d18a | ||
|
|
4a21360e37 | ||
|
|
43a3cfa010 | ||
|
|
b89eae5cb1 | ||
|
|
e311b74dac | ||
|
|
e3748e070b | ||
|
|
0c318816cc | ||
|
|
55e6801f4a | ||
|
|
4f60fc301f | ||
|
|
139159bf1a | ||
|
|
3ebbca0083 | ||
|
|
51aaa75a05 | ||
|
|
01a25f55f2 | ||
|
|
94196ab268 | ||
|
|
dc39343899 | ||
|
|
272eabd1cd | ||
|
|
7a653e6e63 | ||
|
|
994a6d5e76 | ||
|
|
3db72716b4 | ||
|
|
6d39e072f8 | ||
|
|
b7d636d157 | ||
|
|
e8ac4f3d40 | ||
|
|
ae2ccd9887 | ||
|
|
4fff85096c | ||
|
|
b4d8684740 | ||
|
|
d3fb4b6260 | ||
|
|
19eaedf50d | ||
|
|
c56e6a6c08 | ||
|
|
01ecfb0a07 | ||
|
|
e1630e1693 | ||
|
|
526d87c1fc | ||
|
|
8204f6c648 | ||
|
|
5979ff6197 | ||
|
|
d22828b222 | ||
|
|
7e5b9948a8 | ||
|
|
e18c33d532 | ||
|
|
7ece43e2e2 | ||
|
|
f90617eac4 | ||
|
|
2469fb2307 | ||
|
|
a051e6fd71 | ||
|
|
cabe029861 | ||
|
|
1ac4ad2076 | ||
|
|
44119ddf2d | ||
|
|
567036f382 | ||
|
|
27aa0247e7 | ||
|
|
0fc10b302f | ||
|
|
d22ca7f263 | ||
|
|
7304ee46d6 | ||
|
|
0bc51a869d | ||
|
|
a3dff0dcf7 | ||
|
|
048fc7ce71 | ||
|
|
6265e81afb | ||
|
|
38ca96a938 | ||
|
|
8ed5f764fa | ||
|
|
227ea82a29 | ||
|
|
0e2a5e36c6 | ||
|
|
332b656860 | ||
|
|
0230b963c1 | ||
|
|
48e2ea9ff2 | ||
|
|
b6f1394c61 | ||
|
|
560e43b1a6 | ||
|
|
cc1ab7ae97 | ||
|
|
4886561f7d | ||
|
|
658d8a182e | ||
|
|
2a90f9b134 | ||
|
|
f42dafd304 | ||
|
|
ec2100094a | ||
|
|
ecd6347859 | ||
|
|
ab17b19ff0 | ||
|
|
119dfa8d9f | ||
|
|
30400e6e92 | ||
|
|
a9e5ac6290 | ||
|
|
4974f73f26 | ||
|
|
e6d71471e2 | ||
|
|
50db68dff7 | ||
|
|
c8eb5b6a70 | ||
|
|
c61a15e4f3 | ||
|
|
c151ff2d66 | ||
|
|
393b2f9d78 | ||
|
|
bf36cf03d6 | ||
|
|
d3332e0db9 | ||
|
|
ed62b20645 | ||
|
|
5f2c5e27f1 | ||
|
|
ff702b85e2 | ||
|
|
0c98082b25 | ||
|
|
f91eb68556 | ||
|
|
469fefee92 | ||
|
|
de47263087 | ||
|
|
3b9679a673 | ||
|
|
f175966816 | ||
|
|
f1f5364d07 | ||
|
|
1b8b0c76ac | ||
|
|
1a8096ab18 | ||
|
|
f177771c67 | ||
|
|
081fcbfa32 | ||
|
|
0a5e4e2155 | ||
|
|
19eb88a00e | ||
|
|
c8f2967de0 | ||
|
|
b5564330f6 | ||
|
|
43ccb83f73 | ||
|
|
e2d92126c0 | ||
|
|
8c8eef45d5 | ||
|
|
a574e1158a | ||
|
|
af687a6aed | ||
|
|
016e82ab5d | ||
|
|
f300a412d7 | ||
|
|
75e2249cf3 | ||
|
|
1942bcb971 | ||
|
|
9393adf66f | ||
|
|
917aae9bf3 | ||
|
|
da36457d5c | ||
|
|
15a0ededc8 | ||
|
|
68d7864f93 | ||
|
|
1eddbf995f | ||
|
|
592385ac73 | ||
|
|
56d626f5b1 | ||
|
|
7a14300409 | ||
|
|
30fa8d658e | ||
|
|
c0101cb5e7 | ||
|
|
fd640cd65e | ||
|
|
11528eff92 | ||
|
|
3c9175459d | ||
|
|
bd4e0e01e2 | ||
|
|
cfcf6e473c | ||
|
|
50c6af3158 | ||
|
|
1d4310cd3a | ||
|
|
2191596e68 | ||
|
|
acf8f87a88 | ||
|
|
cfac146620 | ||
|
|
f3234ad853 | ||
|
|
b65fee3d85 | ||
|
|
82b294bc7d | ||
|
|
2a58573823 | ||
|
|
a96f71b7fd | ||
|
|
077f4a4c79 | ||
|
|
9a495467fb | ||
|
|
db2e62b219 | ||
|
|
2fafa493a2 | ||
|
|
7617c3005c | ||
|
|
5cdbb71277 | ||
|
|
f9339c36bf | ||
|
|
67dc57e9d0 | ||
|
|
757f3190d1 | ||
|
|
0d94a15ee7 | ||
|
|
7f1c80da1b | ||
|
|
f74e11bce0 | ||
|
|
d4c843e8e3 | ||
|
|
bae190b282 | ||
|
|
d2cbd70da8 | ||
|
|
5da78ce583 | ||
|
|
14bbe3e30b | ||
|
|
e52e2bbc68 | ||
|
|
b56752e45b | ||
|
|
4632f511ab | ||
|
|
e17b6f172d | ||
|
|
eb04e878ba | ||
|
|
c71febd116 | ||
|
|
69c1ccbb6b | ||
|
|
499b8c8d8b | ||
|
|
ea009f9a84 | ||
|
|
dc784d4567 | ||
|
|
9ffc8a2c0b | ||
|
|
5b20fe573e | ||
|
|
7e466bb80f | ||
|
|
1f8418b447 | ||
|
|
ccfc57fc62 | ||
|
|
1d31fccd72 | ||
|
|
9ac1a89e48 | ||
|
|
bfc0696324 | ||
|
|
e33e782f9e | ||
|
|
f2b591d1b2 | ||
|
|
fe9af5153d | ||
|
|
0dfdd0a601 | ||
|
|
4acc42e1b6 | ||
|
|
7c9a179865 | ||
|
|
c6296a4918 | ||
|
|
cc399f1164 | ||
|
|
e4ffb96646 | ||
|
|
8d34428dac | ||
|
|
353637a0c0 | ||
|
|
00713d8ec1 | ||
|
|
d949881e9f | ||
|
|
5075441a69 | ||
|
|
94a852cb8b | ||
|
|
06f847c2d0 | ||
|
|
0c2f9d0e62 | ||
|
|
11ed6b124f | ||
|
|
8767fc0068 | ||
|
|
66b07623b0 | ||
|
|
4f3635eef0 | ||
|
|
f638833759 | ||
|
|
ab9d1936d9 | ||
|
|
0fefb2bd2c | ||
|
|
6740eee495 | ||
|
|
aa6020e00d | ||
|
|
7bfe4a6cd8 | ||
|
|
aa467681e4 | ||
|
|
6b8cd6151d | ||
|
|
0d6aaee12a | ||
|
|
dc03c6e0ac | ||
|
|
0f4d957d14 | ||
|
|
71a13224a1 | ||
|
|
8097ed60ba | ||
|
|
408bb74214 | ||
|
|
9433b43873 | ||
|
|
896bf48c79 | ||
|
|
3dd21a7e11 | ||
|
|
ed79a04018 | ||
|
|
ebfe20defb | ||
|
|
28b148348d | ||
|
|
fe099f2c8b | ||
|
|
107c8c9f0f | ||
|
|
39e818b3e5 | ||
|
|
588ffa3d5c | ||
|
|
691e4dd114 | ||
|
|
55bc42725f | ||
|
|
cce55b9361 | ||
|
|
c23c6e4fc9 | ||
|
|
afdd60efe8 | ||
|
|
8f6ea573ff | ||
|
|
3271b544ef | ||
|
|
6e83130754 | ||
|
|
f6ebe32519 | ||
|
|
3caed3c761 | ||
|
|
ce1c55427a | ||
|
|
9cd72595f0 | ||
|
|
ad1d3e135f | ||
|
|
76866ab901 | ||
|
|
20b647dfbf | ||
|
|
99d08065e4 | ||
|
|
261fba3d21 | ||
|
|
e1d9de7b1f | ||
|
|
391b2f3622 | ||
|
|
86ff315ef2 | ||
|
|
8d8a850864 | ||
|
|
7bf2da6014 | ||
|
|
7eae3ce709 | ||
|
|
5f5e48e414 | ||
|
|
4f6a37f7b1 | ||
|
|
82613e7e8b | ||
|
|
588aa382a1 | ||
|
|
809d981987 | ||
|
|
cfc21fde8c | ||
|
|
5c06ef547b | ||
|
|
0990ad4a6f | ||
|
|
d8f967d2b8 | ||
|
|
fe1c3e7130 | ||
|
|
062148674c | ||
|
|
11320693fd | ||
|
|
7af7eaccb4 | ||
|
|
5d5cf868a2 | ||
|
|
ddf8fe5b1a | ||
|
|
dc24f6afe0 | ||
|
|
f7dac6ab25 | ||
|
|
4b4fc36ebd | ||
|
|
7626ea5ed8 | ||
|
|
7061e9afe4 | ||
|
|
a124635c2c | ||
|
|
c9704137b7 | ||
|
|
ab8d75d3cc | ||
|
|
5e720891f5 | ||
|
|
27ed1aadd5 | ||
|
|
001f203983 | ||
|
|
515047d2dc | ||
|
|
995606b1e6 | ||
|
|
b5081344da | ||
|
|
4f86691fb8 | ||
|
|
45aca016d4 | ||
|
|
7f33d8a71e | ||
|
|
af620755c5 | ||
|
|
a76b554cad | ||
|
|
ef1e9e1b70 | ||
|
|
fdbac63f46 | ||
|
|
377f1dbfa1 | ||
|
|
14b840f3fe | ||
|
|
beb1cc0cde | ||
|
|
ef2515507d | ||
|
|
a271b9e816 | ||
|
|
8a3d4a64db | ||
|
|
9cd8beb778 | ||
|
|
aaa165a0f3 | ||
|
|
7514213918 | ||
|
|
849ba999cb | ||
|
|
10eb08095a | ||
|
|
23eb4c90fd | ||
|
|
12e5765c64 | ||
|
|
8a53abc32f | ||
|
|
25d1ac0c5f | ||
|
|
f534bafb79 | ||
|
|
f5fa1e6c2a | ||
|
|
60842540cb | ||
|
|
0fb04cdcb4 | ||
|
|
5fb8d9214f | ||
|
|
f7f2de291f | ||
|
|
0c27a13a00 | ||
|
|
02a887776f | ||
|
|
f8c885c0a9 | ||
|
|
327ee2b8dd | ||
|
|
4f90bbd931 | ||
|
|
f196304a56 | ||
|
|
f146ea05c7 | ||
|
|
a602d4d73d | ||
|
|
2422b9a30b | ||
|
|
ef72c9fe02 | ||
|
|
ee12a214d3 | ||
|
|
83a85a4549 | ||
|
|
420413268d | ||
|
|
c1bf1fd211 | ||
|
|
9af20f9176 | ||
|
|
230a80852a | ||
|
|
ff9474b2f9 | ||
|
|
ed8f1e4111 | ||
|
|
c7eb625ac0 | ||
|
|
5a8e4be9df | ||
|
|
e267e4131b | ||
|
|
782adc6a1a | ||
|
|
dce8b2d61a | ||
|
|
8074baa3b5 | ||
|
|
c104a51458 | ||
|
|
6cc8c5a0f4 | ||
|
|
90c928205a | ||
|
|
7c1831ef38 | ||
|
|
f722514ecf | ||
|
|
c7b021c7be | ||
|
|
2aa1695b74 | ||
|
|
7bc242bcd0 | ||
|
|
665f5f9664 | ||
|
|
36806e4724 | ||
|
|
6c2fad508a | ||
|
|
252b99db18 | ||
|
|
c238596a81 | ||
|
|
85c1a3cc42 | ||
|
|
b6e8dc4c8d | ||
|
|
ba95514d8d | ||
|
|
87dac75919 | ||
|
|
37178eeb0b | ||
|
|
97747deed9 | ||
|
|
88b1c7e6eb | ||
|
|
29c933e31b | ||
|
|
b224d4d1c4 | ||
|
|
6db2a72eb8 | ||
|
|
cd5eedff84 | ||
|
|
75a427ab27 | ||
|
|
ae439afaa4 | ||
|
|
1a99a2fddb | ||
|
|
c867f2a29a | ||
|
|
ce65d83cc3 | ||
|
|
38dc1c29d6 | ||
|
|
eeb62ff85e | ||
|
|
1dca40c1c9 | ||
|
|
7e1f2b99b1 | ||
|
|
f65b673451 | ||
|
|
391a9fd260 | ||
|
|
9a759e7ef1 | ||
|
|
3de0030d07 | ||
|
|
cd17d6940f | ||
|
|
5aa212471c | ||
|
|
c324682ca3 | ||
|
|
33565d8b96 | ||
|
|
aeb56dee17 | ||
|
|
98422e4153 | ||
|
|
2c7ce4a107 | ||
|
|
3fbde86548 | ||
|
|
01ecd197ce | ||
|
|
15eb4c290a | ||
|
|
5fa20dc1f7 | ||
|
|
3df64091dd | ||
|
|
de5d9335d1 | ||
|
|
88a65f08d8 | ||
|
|
a505354363 | ||
|
|
837f7e6e9b | ||
|
|
4faa857330 | ||
|
|
9c7b0cb889 | ||
|
|
a11abcc016 | ||
|
|
dd693fdc5f | ||
|
|
5ac2576fcf | ||
|
|
512817a2db | ||
|
|
1f8c8d88fa | ||
|
|
044ed53935 | ||
|
|
d47bb2749a | ||
|
|
0dc000839b | ||
|
|
462e440d5b | ||
|
|
1891fe0afd | ||
|
|
98c42c200b | ||
|
|
8cec60c4b0 | ||
|
|
7419244b39 | ||
|
|
eb9a48d2d6 | ||
|
|
e44d36b4af | ||
|
|
9a5d05f198 | ||
|
|
d7a20a5d53 | ||
|
|
b56680e24e | ||
|
|
886e80ff6d | ||
|
|
142187b024 | ||
|
|
72f3237aba | ||
|
|
a2406ac163 | ||
|
|
91a64137fe | ||
|
|
57ecbc58f8 | ||
|
|
ea4d1007b8 | ||
|
|
16bc7b986b | ||
|
|
115f95fa96 | ||
|
|
b77b76ebb5 | ||
|
|
f516298a84 | ||
|
|
edb31a0c9c | ||
|
|
e99010f363 | ||
|
|
fa865f8409 | ||
|
|
40b613b7a2 | ||
|
|
1b44bb068b | ||
|
|
786c371acd | ||
|
|
c4920f474d | ||
|
|
a79b010572 | ||
|
|
7404795dc6 | ||
|
|
47a9fb5803 | ||
|
|
701d4c5722 | ||
|
|
d8d6f945ec | ||
|
|
a6821bb8ab | ||
|
|
ed40eec711 | ||
|
|
2f163c3b6e | ||
|
|
43488c55f1 | ||
|
|
2f727b553c | ||
|
|
29c37aa6da | ||
|
|
64baef431d | ||
|
|
634fe5683a | ||
|
|
225ca3f852 | ||
|
|
ff2ac6c3cd | ||
|
|
607777f2a3 | ||
|
|
78eeb40322 | ||
|
|
772f79ae21 | ||
|
|
806a4e823f | ||
|
|
323e402e0c | ||
|
|
9ebb59580d | ||
|
|
bdb749d198 | ||
|
|
bafab6eb18 | ||
|
|
35acbb62c3 | ||
|
|
4676ade4ee | ||
|
|
61afca2337 | ||
|
|
47251bd38b | ||
|
|
8edc3b1f36 | ||
|
|
8acfb1a537 | ||
|
|
ac78171099 | ||
|
|
d573c5746b | ||
|
|
51e8f9a87a | ||
|
|
bfe590d96d | ||
|
|
60e2d10775 | ||
|
|
2f432cef62 | ||
|
|
5217b66396 | ||
|
|
b8bb191d24 | ||
|
|
8070a52dc7 | ||
|
|
3205f3cf8c | ||
|
|
e0cdd610dd | ||
|
|
ed3b04ed6f | ||
|
|
62a2d2ae39 | ||
|
|
e2c853e40d | ||
|
|
b9b5a71869 | ||
|
|
87fdbc932f | ||
|
|
84838b2e9f | ||
|
|
c2ca37a790 | ||
|
|
c6805b9f0d | ||
|
|
b1dbdc03dd | ||
|
|
88a3f3d43b | ||
|
|
5f8dcd71a5 | ||
|
|
45db95da79 | ||
|
|
c79b12b27f | ||
|
|
135da6108d | ||
|
|
0d6dda579f | ||
|
|
e641a347db | ||
|
|
3e17d91edf | ||
|
|
bcfea3c920 | ||
|
|
715c648d52 | ||
|
|
d0ebed9822 | ||
|
|
a3775f18ba | ||
|
|
7b5d6e9fc5 | ||
|
|
368ac0b9e0 | ||
|
|
0448696bd8 | ||
|
|
deb75ed0d7 | ||
|
|
fcc9bacb4e | ||
|
|
9a5e8fd2ba | ||
|
|
1c023c4377 | ||
|
|
c213b98329 | ||
|
|
27d2e6e519 | ||
|
|
de8ea104d9 | ||
|
|
7ee368965c | ||
|
|
d8b5b825b3 | ||
|
|
de67570230 | ||
|
|
60c604fbe6 | ||
|
|
2f6d25ed01 | ||
|
|
b134081293 | ||
|
|
a0528d7f9c | ||
|
|
348335ddf0 | ||
|
|
01752e5486 | ||
|
|
3e758e1b86 | ||
|
|
fb0b30a9a7 | ||
|
|
0c9aea454e | ||
|
|
f282585c3f | ||
|
|
ae5ff31c96 | ||
|
|
20fa8bc953 | ||
|
|
5198a98736 | ||
|
|
778f59b4fd | ||
|
|
49623cb4dd | ||
|
|
90b53002aa | ||
|
|
93c12af305 | ||
|
|
60f2419b5c | ||
|
|
80494ad813 | ||
|
|
b43c4a7ad4 | ||
|
|
3c608de5bb | ||
|
|
fe5bc1d215 | ||
|
|
580bf9a755 | ||
|
|
c7df5df163 | ||
|
|
a08c52af55 | ||
|
|
e11db0f0f3 | ||
|
|
c6e0582729 | ||
|
|
6e98629f9b | ||
|
|
2243760442 | ||
|
|
91dd6877aa | ||
|
|
e73bcd8fc1 | ||
|
|
7e886b3260 | ||
|
|
5c9451d3ed | ||
|
|
c6c2dcc6c0 | ||
|
|
0bdd37090e | ||
|
|
c745faaaf0 | ||
|
|
9ad03ca873 | ||
|
|
138914384e | ||
|
|
77068667e4 | ||
|
|
c57cef4a21 | ||
|
|
50acc4c708 | ||
|
|
b5f8ba4817 | ||
|
|
a53249ccd7 | ||
|
|
0c62fa2112 | ||
|
|
806547dd15 | ||
|
|
fb1669b2b3 | ||
|
|
0cda15f2b5 | ||
|
|
b88e9370c6 | ||
|
|
e343f3beb8 | ||
|
|
a13bfae714 | ||
|
|
877c6bbb2a | ||
|
|
30d5134394 | ||
|
|
fae5c74487 | ||
|
|
255332ea2e | ||
|
|
15c0e6db19 | ||
|
|
2b600a1e4e | ||
|
|
297fb2483d | ||
|
|
5049822415 | ||
|
|
e3787e0f4f | ||
|
|
683199044b | ||
|
|
4f3c3e9f66 | ||
|
|
fc0240c06b | ||
|
|
6bfa284bac | ||
|
|
dfee9bc578 | ||
|
|
0838a0e865 | ||
|
|
5f61d80e2d | ||
|
|
c4fa4c237c | ||
|
|
44d00d5ef4 | ||
|
|
d4d3efcb65 | ||
|
|
f23e105240 | ||
|
|
7308a84c52 | ||
|
|
5a396a7060 | ||
|
|
513632299f | ||
|
|
aca6cb96b7 | ||
|
|
0d40558f1e | ||
|
|
1ccbaf6776 | ||
|
|
ba6c703163 | ||
|
|
3b9eb02bbb | ||
|
|
bca7382015 | ||
|
|
67672bd389 | ||
|
|
232017d9a2 | ||
|
|
23653f67f0 | ||
|
|
e3b688d1dd | ||
|
|
a94b21ca3c | ||
|
|
9c9be3e6e4 | ||
|
|
062561686e | ||
|
|
761682c206 | ||
|
|
b79bb53b10 | ||
|
|
3080eef253 | ||
|
|
39c48d631c | ||
|
|
a55d26a726 | ||
|
|
597ae157b3 | ||
|
|
c1c7458914 | ||
|
|
0e97f269ab | ||
|
|
bbe4cd63a1 | ||
|
|
2515d17a85 | ||
|
|
d8e95a3c3b | ||
|
|
98f6bed8c9 | ||
|
|
bd000c2662 | ||
|
|
a46141111a | ||
|
|
f57c89c6e9 | ||
|
|
6d4cac948d | ||
|
|
1f54b3a0cf | ||
|
|
2f8655dc23 | ||
|
|
d624923cd8 | ||
|
|
2180c076dd | ||
|
|
0dbdf0a21a | ||
|
|
5cb63a258c | ||
|
|
39ac6caaef | ||
|
|
de4ef8b2b4 | ||
|
|
99cba09a4a | ||
|
|
300967f0f3 | ||
|
|
52879febb9 | ||
|
|
f077a563c4 | ||
|
|
92fbc61f47 | ||
|
|
5ac1bcc414 | ||
|
|
5837aa23ea | ||
|
|
921e4c1d81 | ||
|
|
6499489bdc | ||
|
|
84bca71328 | ||
|
|
8c431b4ec3 | ||
|
|
25086a7944 | ||
|
|
b0889b4afe | ||
|
|
ed971bc41c | ||
|
|
728595dc96 | ||
|
|
7fc6adb776 | ||
|
|
002102ce62 | ||
|
|
bf9da80d46 | ||
|
|
dda9994869 | ||
|
|
4c76ad159e | ||
|
|
f76a8daca8 | ||
|
|
3263629ebe | ||
|
|
f38011340a | ||
|
|
05240e143b | ||
|
|
fcbe7d3c98 | ||
|
|
a6662ccdff | ||
|
|
854fe85151 | ||
|
|
abe8d54401 | ||
|
|
443d8a3b18 | ||
|
|
8d8ec0010f | ||
|
|
2b2fdf1b11 | ||
|
|
cbe44d6a96 | ||
|
|
420346faea | ||
|
|
6220162852 | ||
|
|
37198bde66 | ||
|
|
281c056f6c | ||
|
|
49a513cd07 | ||
|
|
7a95aabbf4 | ||
|
|
83874ec096 | ||
|
|
fc12114830 | ||
|
|
9270d0a33d | ||
|
|
30295efdb4 | ||
|
|
f1342c1456 | ||
|
|
194b73c293 | ||
|
|
89e5f79bbb | ||
|
|
82d7ce7ac2 | ||
|
|
0cc4c704f8 | ||
|
|
dde762a1d6 | ||
|
|
1c86e246c7 | ||
|
|
2d173c8e69 | ||
|
|
600fd34d30 | ||
|
|
d26bca3208 | ||
|
|
4cb9cf801c | ||
|
|
50fb8789b4 | ||
|
|
fb8dc44ec1 | ||
|
|
0781caa8bc | ||
|
|
2cdb23f0dd | ||
|
|
8e536c00b9 | ||
|
|
8ff154cc2d | ||
|
|
daaae6e01e | ||
|
|
a64a4e697a | ||
|
|
3f51d8cc12 | ||
|
|
9f9e76f8b9 | ||
|
|
bcde54d258 | ||
|
|
828737f3bc | ||
|
|
71d62fd58c | ||
|
|
6bacd66d1a | ||
|
|
1cad004359 | ||
|
|
5b51db158d | ||
|
|
a4d1509448 | ||
|
|
bbd51a03b6 | ||
|
|
b55b82b2fd | ||
|
|
fdb0f101bd | ||
|
|
0afca5633d | ||
|
|
7c0d9a7172 | ||
|
|
5daeecfb0e | ||
|
|
bf64b6fbe0 | ||
|
|
a927f7d38b | ||
|
|
4d1917ebd5 | ||
|
|
09be6e372e | ||
|
|
a68a2e50e6 | ||
|
|
3028379538 | ||
|
|
4b36832b5a | ||
|
|
4ff18d28d8 | ||
|
|
c6c92b18cc | ||
|
|
f6b7e27c67 | ||
|
|
b8624bc55f | ||
|
|
2eec30756d | ||
|
|
6b44ce8973 | ||
|
|
ed0b501716 | ||
|
|
0fd391af72 | ||
|
|
fe9c1ada88 | ||
|
|
4c1f4ef58c | ||
|
|
10afc770ff | ||
|
|
8543e60f86 | ||
|
|
116d7e0f29 | ||
|
|
00191fc5b3 | ||
|
|
be865a09a7 | ||
|
|
a1c6f91cf2 | ||
|
|
9e42c6bdf1 | ||
|
|
68adaec55b | ||
|
|
03640efef5 | ||
|
|
c6f450842e | ||
|
|
e583eb4592 | ||
|
|
7fffbe0c64 | ||
|
|
63e3b71eb5 | ||
|
|
823ef738fe | ||
|
|
0977ef0ec2 | ||
|
|
cecf3f3d22 | ||
|
|
472fbce23a | ||
|
|
e44aea1767 | ||
|
|
d682d90d86 | ||
|
|
141aa17dfc | ||
|
|
0b09e53479 | ||
|
|
528d8bf25d | ||
|
|
03a2109e24 | ||
|
|
b38b9bced6 | ||
|
|
ea063d0c95 | ||
|
|
7f93929014 | ||
|
|
4cd10ecb87 | ||
|
|
6ef30debd2 | ||
|
|
4766bace4e | ||
|
|
261500a3a4 | ||
|
|
831a792ac6 | ||
|
|
fae22b7023 | ||
|
|
4568b39997 | ||
|
|
4a218cacfa | ||
|
|
34a4dd3077 | ||
|
|
a383bd7e52 | ||
|
|
e76e693bdb | ||
|
|
2c52e4aa69 | ||
|
|
13dee36e93 | ||
|
|
6e180439d1 | ||
|
|
e8d0c1ae95 | ||
|
|
068bda0c95 | ||
|
|
ab694381d5 | ||
|
|
dc2a6c75cf | ||
|
|
98dbba8f27 | ||
|
|
9a1b80d77a | ||
|
|
a655be30d6 | ||
|
|
e5aabc3072 | ||
|
|
2cd32d58ad | ||
|
|
0c02b92717 | ||
|
|
c58a077a2f | ||
|
|
6e7dc9d7d3 | ||
|
|
572c945274 | ||
|
|
c605efab61 | ||
|
|
2695f4302a | ||
|
|
a17011243e | ||
|
|
9a533ab807 | ||
|
|
a037952493 | ||
|
|
dc96795a02 | ||
|
|
8d9746d7b1 | ||
|
|
00342c4239 | ||
|
|
e48df2c1fd | ||
|
|
6ade0f6554 | ||
|
|
a88157bb92 | ||
|
|
00f318284f | ||
|
|
2e12dc6d53 | ||
|
|
34435d4d05 | ||
|
|
a99452b773 | ||
|
|
a05cb39ab0 | ||
|
|
0c69f7f10e | ||
|
|
8d50034265 | ||
|
|
64f451e904 | ||
|
|
b1b47d7d91 | ||
|
|
a93c580c92 | ||
|
|
030e2786d1 | ||
|
|
56bbe1d437 | ||
|
|
1fff5ea6dc | ||
|
|
ad9956375f | ||
|
|
72b1ad6670 | ||
|
|
848c65e43a | ||
|
|
4787e5d29a | ||
|
|
194f76d57f | ||
|
|
d2ee5cb0bc | ||
|
|
040298db2c | ||
|
|
b4e6d4b98c | ||
|
|
a2b31e2677 | ||
|
|
e997c44a18 | ||
|
|
b27011fb38 | ||
|
|
b477eb238b | ||
|
|
02bc256225 | ||
|
|
9f7293127c | ||
|
|
61a0ea7eac | ||
|
|
51ef93e23b | ||
|
|
ab11220e27 | ||
|
|
b23d588747 | ||
|
|
a71dc28523 | ||
|
|
2de0e13ec5 | ||
|
|
d0c922ed13 | ||
|
|
8c82ebec79 | ||
|
|
ddd83d10fa | ||
|
|
031375e701 | ||
|
|
4bc285313c | ||
|
|
f42dd69b53 | ||
|
|
1dae06fdd8 | ||
|
|
f43a2e1201 | ||
|
|
7623f3d926 | ||
|
|
205682235a | ||
|
|
7fbd83ceaa | ||
|
|
e6a278f0f6 | ||
|
|
cd14b16997 | ||
|
|
c4701b8ae6 | ||
|
|
ff29247b9e | ||
|
|
d9eca203ed | ||
|
|
4a85182a79 | ||
|
|
055d275f0a | ||
|
|
eb7bbe5715 | ||
|
|
8d5752883f | ||
|
|
2b5f356de8 | ||
|
|
c9610900fd | ||
|
|
79b1a65a7b | ||
|
|
df21b9453f | ||
|
|
fd6df49f55 | ||
|
|
22257bdd2b | ||
|
|
aa3767b180 | ||
|
|
c073c2b7de | ||
|
|
529a55efb0 | ||
|
|
c36567e062 | ||
|
|
f7873f3b41 | ||
|
|
6b54f1c1eb | ||
|
|
1fb1bc7404 | ||
|
|
aa646a3acf | ||
|
|
18ac8a9d03 | ||
|
|
7c357c5d52 | ||
|
|
816db9e0dc | ||
|
|
fa9e1de4df | ||
|
|
137d947606 | ||
|
|
77a05c1881 | ||
|
|
ab13e3fe18 | ||
|
|
fc4b085026 | ||
|
|
67e8f6ab65 | ||
|
|
792b30c8b5 | ||
|
|
3407aadfb8 | ||
|
|
29e59ebfd2 | ||
|
|
286a684963 | ||
|
|
3ef586d2d3 | ||
|
|
5889037a71 | ||
|
|
5e3f3969f5 | ||
|
|
eecf45918b | ||
|
|
6d91e6e6b2 | ||
|
|
7808f87060 | ||
|
|
6c48b22eea | ||
|
|
7344f1178b | ||
|
|
5a6a3ce6e0 | ||
|
|
f3a54bf02a | ||
|
|
766dc5d0ce | ||
|
|
5c8be46b64 | ||
|
|
f8f196903a | ||
|
|
337c41fe10 | ||
|
|
fb8eefd00d | ||
|
|
3082b959f6 | ||
|
|
487371737c | ||
|
|
14ccc9359d | ||
|
|
665b76271c | ||
|
|
11ca00bb45 | ||
|
|
046d397dfb | ||
|
|
58cc01b6d3 | ||
|
|
6a3eb7b9b9 | ||
|
|
935a463d3b | ||
|
|
f2f4173d7b | ||
|
|
941aaf6d8b | ||
|
|
1068b68568 | ||
|
|
e76b9a9051 | ||
|
|
410ac117f6 | ||
|
|
fa89c4dee8 | ||
|
|
a464ca2368 | ||
|
|
befd89c3d7 | ||
|
|
49640cf282 | ||
|
|
2ac4549712 | ||
|
|
6bffe06c3b | ||
|
|
13e9ad45f9 | ||
|
|
193fcc9014 | ||
|
|
30529b7a04 | ||
|
|
38b254e7f0 | ||
|
|
c4b98d2139 | ||
|
|
1309b58592 | ||
|
|
9b9d2dc32b | ||
|
|
73020fb8ae | ||
|
|
8221db833f | ||
|
|
09b10d3f2c | ||
|
|
a898b501b5 | ||
|
|
916bf029e4 | ||
|
|
c31798c7f8 | ||
|
|
064c5e9e50 | ||
|
|
077a53475d | ||
|
|
32ca23a00b | ||
|
|
1a7cdd7d04 | ||
|
|
85befa467b | ||
|
|
9b956e57ac | ||
|
|
93c0c49002 | ||
|
|
44954621e0 | ||
|
|
96e5d484da | ||
|
|
4efe0b920e | ||
|
|
d158cc7fc1 | ||
|
|
034cd4eaf6 | ||
|
|
d5f3d90486 | ||
|
|
99e076b7dd | ||
|
|
8f9aec83f2 | ||
|
|
87a8745a64 | ||
|
|
2ab51d7a8a | ||
|
|
770d292543 | ||
|
|
3e0f43e44e | ||
|
|
6a763fab18 | ||
|
|
3fe6726789 | ||
|
|
5b4deabd90 | ||
|
|
84bc7dd4aa | ||
|
|
3960b4e11f | ||
|
|
141215fc38 | ||
|
|
e2e0be689e | ||
|
|
2446e2f6da | ||
|
|
81a9bc0739 | ||
|
|
64e86f403e | ||
|
|
77f1351e5b | ||
|
|
47a87f3a92 | ||
|
|
d2df75dc25 | ||
|
|
d92fbfb7aa | ||
|
|
1b3be55711 | ||
|
|
8d98e443c5 | ||
|
|
46121edd62 | ||
|
|
1a32536ac8 | ||
|
|
b9c8fefc87 | ||
|
|
85bf6891f9 | ||
|
|
1ccca1c4ec | ||
|
|
22cd70e53f | ||
|
|
d8aad1444e | ||
|
|
621dfd9ca5 | ||
|
|
2ad6a1a77e | ||
|
|
8f37438a08 | ||
|
|
295f99ba75 | ||
|
|
0198b2603f | ||
|
|
869e6e2553 | ||
|
|
ba7594c51f | ||
|
|
bdbbb3e846 | ||
|
|
332102a523 | ||
|
|
7448142ee4 | ||
|
|
a23ba65322 | ||
|
|
5520b81038 | ||
|
|
51bd22e34a | ||
|
|
8cc6f40c6f | ||
|
|
32dcd3070a | ||
|
|
f25aecd853 | ||
|
|
5fb61c470e | ||
|
|
93c1ade74e | ||
|
|
9d7e7a94f5 | ||
|
|
f81fe0a106 | ||
|
|
2dbbf2c74b | ||
|
|
c149ec87ea | ||
|
|
7e7f47cc20 | ||
|
|
c63c235a05 | ||
|
|
1daf1be49b | ||
|
|
c354c392a9 | ||
|
|
761121eba2 | ||
|
|
53c72b7045 | ||
|
|
9bd9ebfc09 | ||
|
|
6c537c0dc3 | ||
|
|
fb52bbb3ed | ||
|
|
b1f78b7bea | ||
|
|
b59c5002ed | ||
|
|
c29de82e86 | ||
|
|
5a711afb7e | ||
|
|
b023e9b35b | ||
|
|
479ade8a76 | ||
|
|
b43e79a9af | ||
|
|
b197897227 | ||
|
|
a5559825ae | ||
|
|
2c99932c3c | ||
|
|
b14f055c6c | ||
|
|
7438640753 | ||
|
|
e2cc829313 | ||
|
|
440e9922d7 | ||
|
|
d1b23a8b2d | ||
|
|
34d2d2c9ed | ||
|
|
e9d5ae0d35 | ||
|
|
0be93e7533 | ||
|
|
e55cdb2c73 | ||
|
|
d3064920f1 | ||
|
|
d136a4e4a8 | ||
|
|
1c273ce427 | ||
|
|
1ca8be159b | ||
|
|
de277dcf73 | ||
|
|
30ff63e653 | ||
|
|
2e6c68ba73 | ||
|
|
383ab88d62 | ||
|
|
2bfb8fc191 | ||
|
|
51ad4e36fc | ||
|
|
ba04ea5f1f | ||
|
|
2b8fba0c8a | ||
|
|
8ac03699aa | ||
|
|
485ec4e924 | ||
|
|
7da60d30b5 | ||
|
|
cd0eb2b623 | ||
|
|
ede5b254e3 | ||
|
|
a930b9413c | ||
|
|
16b1b91a76 | ||
|
|
e9b86ed23a | ||
|
|
1c266c658e | ||
|
|
55f36cf466 | ||
|
|
9479e692f6 | ||
|
|
549a0f6829 | ||
|
|
98c1cddc91 | ||
|
|
3e574909ca | ||
|
|
75e498c151 | ||
|
|
0d8b202a74 | ||
|
|
fbf880b1c4 | ||
|
|
142bc0c3b7 | ||
|
|
3eae92175a | ||
|
|
91e5e67a70 | ||
|
|
4b976bebba | ||
|
|
6ddf2a7bc0 | ||
|
|
53fee90554 | ||
|
|
eb06de5344 | ||
|
|
e1ec2f79e5 | ||
|
|
c6d01e4ce0 | ||
|
|
eec36765f2 | ||
|
|
55aef2e74e | ||
|
|
edc95f78f1 | ||
|
|
63149f4b5d | ||
|
|
0a55ba64fa | ||
|
|
67d00491eb | ||
|
|
bf90bbe595 | ||
|
|
2696a9e138 | ||
|
|
6324c727a9 | ||
|
|
b53f336a2a | ||
|
|
da5edb0b3b | ||
|
|
b775a6f3c8 | ||
|
|
dc4de56a00 | ||
|
|
db91892086 | ||
|
|
d3b29945ea | ||
|
|
a49b31aab9 | ||
|
|
b580450c20 | ||
|
|
9645855105 | ||
|
|
16705c6bb0 | ||
|
|
e99031d6af | ||
|
|
e0a6473814 | ||
|
|
b1eecd7e93 | ||
|
|
cda8197ee6 | ||
|
|
cce552c711 | ||
|
|
b1e900f1ae | ||
|
|
75a117701c | ||
|
|
5672594ab5 | ||
|
|
a963d862e9 | ||
|
|
63a2e9ab67 | ||
|
|
eb7301b4f6 | ||
|
|
91722b18ee | ||
|
|
d528331a37 | ||
|
|
de6e86a3ea | ||
|
|
8d0d772835 | ||
|
|
e0aa8b6db3 | ||
|
|
fd4e2f69e3 | ||
|
|
d14b8100ac | ||
|
|
4359446101 | ||
|
|
9ddb6498b0 | ||
|
|
e897daf443 | ||
|
|
6604c717b7 | ||
|
|
90189a0596 | ||
|
|
5cf4301fd0 | ||
|
|
c516ed4972 | ||
|
|
27e44658c4 | ||
|
|
a1a938e895 | ||
|
|
f9ee5aa029 | ||
|
|
3080121b59 | ||
|
|
bfd022786c | ||
|
|
3680303256 | ||
|
|
51e031323b | ||
|
|
76e18fd30e | ||
|
|
a0776a77b6 | ||
|
|
0d1ae3bfc6 | ||
|
|
5775554d9d | ||
|
|
4dee6a4493 | ||
|
|
4a8313ed1c | ||
|
|
fd75b18582 | ||
|
|
9e3c037491 | ||
|
|
3c3e7ca2eb | ||
|
|
2877d1a280 | ||
|
|
ef0d1f5679 | ||
|
|
77abd3e188 | ||
|
|
ab9ced8344 | ||
|
|
d6805e597b | ||
|
|
8a49bec48b | ||
|
|
65987a83fd | ||
|
|
7fe8cd541a | ||
|
|
bd281f6599 | ||
|
|
aba9bb8c51 | ||
|
|
09fca0f660 | ||
|
|
50cd90c8cf | ||
|
|
457c840abb | ||
|
|
418a791604 | ||
|
|
e6706f59c7 | ||
|
|
42a37a5375 | ||
|
|
ed56963cbc | ||
|
|
dcbcf22535 | ||
|
|
1d7e98fab2 | ||
|
|
2d0a425c7a | ||
|
|
6f75c014f0 | ||
|
|
a6a9fe3300 | ||
|
|
8d11362a06 | ||
|
|
0241c82b34 | ||
|
|
551ff135fd | ||
|
|
6ddfa1739d | ||
|
|
92b0a43595 | ||
|
|
5a020fc40e | ||
|
|
1d23dd9d7b | ||
|
|
dd3bc5a279 | ||
|
|
db47a8221d | ||
|
|
0b7890061d | ||
|
|
42f8ee658b | ||
|
|
38c5452b94 | ||
|
|
4d5d6ce18f | ||
|
|
b99179d12c | ||
|
|
98e427c0f4 | ||
|
|
5bea5bb72f | ||
|
|
fbe75d2694 | ||
|
|
f37a4001e0 | ||
|
|
e884ab2720 | ||
|
|
8a157169c5 | ||
|
|
fa71ed950d | ||
|
|
c44e311843 | ||
|
|
e8d83954df | ||
|
|
31395fd684 | ||
|
|
ac69cc0e5f | ||
|
|
0837e0ecc0 | ||
|
|
1abe7387f2 | ||
|
|
6b807ffab0 | ||
|
|
d94d0962c1 | ||
|
|
cd4230c06a | ||
|
|
e672b877a0 | ||
|
|
4b8b243f21 | ||
|
|
fc3c544268 | ||
|
|
586e689042 | ||
|
|
6cef63f977 | ||
|
|
39a9db1261 | ||
|
|
1151607a7c | ||
|
|
a4bbe92519 | ||
|
|
06043d5bef | ||
|
|
68abb68964 | ||
|
|
28e7c7e594 | ||
|
|
e838f543f6 | ||
|
|
36ad7e9717 | ||
|
|
40e966e9ee | ||
|
|
fb902c3b5b | ||
|
|
5b416a8862 | ||
|
|
1660e819ee | ||
|
|
28072d2a44 | ||
|
|
a0655a6737 | ||
|
|
e5f771fa2c | ||
|
|
0afd6a3628 | ||
|
|
1f7c06e5e4 | ||
|
|
4964927b2d | ||
|
|
106394e94e | ||
|
|
14f9a3952f | ||
|
|
bf2dc45f9e | ||
|
|
10080aa6aa | ||
|
|
9572e5d1a5 | ||
|
|
fcb288ce19 | ||
|
|
90c4d49055 | ||
|
|
e889cc96c3 | ||
|
|
7a310e185b | ||
|
|
df976567bd | ||
|
|
9a091f14b1 | ||
|
|
bef3c6b864 | ||
|
|
824abda14d | ||
|
|
1fe03a5b2c | ||
|
|
00031bdc90 | ||
|
|
4f8dabc52d | ||
|
|
1647fa4744 | ||
|
|
c0a3b97edf | ||
|
|
ae26db0a9d | ||
|
|
79610ab660 | ||
|
|
4889a0305c | ||
|
|
0d75a62551 | ||
|
|
b7b9c7f549 | ||
|
|
a57c71beb8 | ||
|
|
5be66ab646 | ||
|
|
2ea5e6cad3 | ||
|
|
11ad1205fd | ||
|
|
638e6ea96c | ||
|
|
30ab98a03d | ||
|
|
79a0d83e2a | ||
|
|
fdebbcb386 | ||
|
|
90793562fa | ||
|
|
a72adaeb91 | ||
|
|
1949614d86 | ||
|
|
7521ff8b85 | ||
|
|
7f9efe9e71 | ||
|
|
d97f9de122 | ||
|
|
212baebfd0 | ||
|
|
d3f5952b61 | ||
|
|
a97bf0fbef | ||
|
|
2de0e186e8 | ||
|
|
14564051fa | ||
|
|
86d485869a | ||
|
|
558710978f | ||
|
|
f36008425f | ||
|
|
e387261d52 | ||
|
|
0c8d997958 | ||
|
|
1eae464868 | ||
|
|
920b0bf429 | ||
|
|
852465bd85 | ||
|
|
3152d884cc | ||
|
|
a63619b40d | ||
|
|
49d7029f9a | ||
|
|
9bf5bb04e6 | ||
|
|
4597b323d2 | ||
|
|
f8cfded176 | ||
|
|
8a2ce17676 | ||
|
|
f79ca12a12 | ||
|
|
e6972ea7fc | ||
|
|
b03486eb73 | ||
|
|
43b7c376c3 | ||
|
|
4c6f7ae344 | ||
|
|
68a6ebaf12 | ||
|
|
78ae2adc28 | ||
|
|
df2bbcadf3 | ||
|
|
ad45b4d341 | ||
|
|
93236f4795 | ||
|
|
2b1062865d | ||
|
|
71edb1e145 | ||
|
|
78269ed420 | ||
|
|
53b3e6f4ee | ||
|
|
1ad928dfd7 | ||
|
|
f9c51b05a6 | ||
|
|
079484b651 | ||
|
|
21e41622cd | ||
|
|
516e7730c6 | ||
|
|
58bfec05cb | ||
|
|
85eee0e651 | ||
|
|
2b42427634 | ||
|
|
ff6ac0333c | ||
|
|
1b936d2aa2 | ||
|
|
df0cff969c | ||
|
|
46e2598499 | ||
|
|
0b32378ab5 | ||
|
|
bf25eb350f | ||
|
|
97cea2ce4d | ||
|
|
82ed402b16 | ||
|
|
ca7b69a2a0 | ||
|
|
d9c61a9431 | ||
|
|
3b636ce2d1 | ||
|
|
2196352335 | ||
|
|
823d7048ba | ||
|
|
d066e1145d | ||
|
|
fb7021abcc | ||
|
|
9c14fb2379 | ||
|
|
a114bf42c4 | ||
|
|
87b6c09de3 | ||
|
|
f1c95112bd | ||
|
|
76d8c8b061 | ||
|
|
7cbcb9a220 | ||
|
|
4433ac57bd | ||
|
|
5379da4874 | ||
|
|
d488ce55b3 | ||
|
|
908e306f04 | ||
|
|
62b5953abe | ||
|
|
35ae968d9f | ||
|
|
22dc39b920 | ||
|
|
278331cc4a | ||
|
|
16e3541a10 | ||
|
|
7d7970d318 | ||
|
|
f4fa10b9c0 | ||
|
|
e68096bd27 | ||
|
|
50cda426eb | ||
|
|
1038421428 | ||
|
|
a5046ab086 | ||
|
|
ebbec1954e | ||
|
|
4021018931 | ||
|
|
5824c3cb7c | ||
|
|
45a1b9471e | ||
|
|
70bc819665 | ||
|
|
e05f3e9b9c | ||
|
|
e93f1ab6f4 | ||
|
|
0878a8ecf0 | ||
|
|
6ea07139dc | ||
|
|
55fca2d7d5 | ||
|
|
8596dcef21 | ||
|
|
bca84d5508 | ||
|
|
65f7485497 | ||
|
|
457b02c781 | ||
|
|
c8e0fa85f3 | ||
|
|
5e714f6143 | ||
|
|
4ecbf31898 | ||
|
|
ea24bf0415 | ||
|
|
171f8349cb | ||
|
|
398f8d001f | ||
|
|
e66978fb48 | ||
|
|
c57d528db7 | ||
|
|
0c47310063 | ||
|
|
cb4f6e37a8 | ||
|
|
ab943f36c3 | ||
|
|
de7fc78ef0 | ||
|
|
9a502580fd | ||
|
|
d56e70b995 | ||
|
|
e9aaf50d59 | ||
|
|
ac0a4f521a | ||
|
|
a3380c5cdd | ||
|
|
6a6d2a0787 | ||
|
|
64809d9ae2 | ||
|
|
d14b38bc91 | ||
|
|
440dfb5785 | ||
|
|
d4a4b5f4fb | ||
|
|
0c000a7fee | ||
|
|
c655c2a20e | ||
|
|
3ba5a18b91 | ||
|
|
bdb3415855 | ||
|
|
5ef5bdd9d9 | ||
|
|
c7bbce8371 | ||
|
|
5e2c26a4a2 | ||
|
|
631faa20ec | ||
|
|
2db17ba67c | ||
|
|
50eca42e35 | ||
|
|
f327487157 | ||
|
|
85b64b7ac3 | ||
|
|
cf17a9e8d6 | ||
|
|
71a1282b89 | ||
|
|
85e1f2f47d | ||
|
|
1f68eba146 | ||
|
|
d71873442f | ||
|
|
10ca2da2d6 | ||
|
|
c7ba822320 | ||
|
|
ef3ce46d00 | ||
|
|
c4595e03ce | ||
|
|
26a7eb456b | ||
|
|
142a85d6a7 | ||
|
|
ef51490a1a | ||
|
|
c40d643238 | ||
|
|
6f23c88567 | ||
|
|
56958a6242 | ||
|
|
74dac97b36 | ||
|
|
2f2e41c781 | ||
|
|
8c872f71c6 | ||
|
|
b40b496866 | ||
|
|
569fec296e | ||
|
|
56d8aa797a | ||
|
|
fe7e30b737 | ||
|
|
a114281c60 | ||
|
|
d09548d04d | ||
|
|
a02b8e3e5c | ||
|
|
2c3f9ca202 | ||
|
|
587782820d | ||
|
|
8fad048569 | ||
|
|
f7c35b118e | ||
|
|
65a669bbb2 | ||
|
|
9985acc539 | ||
|
|
f1f0a43f21 | ||
|
|
6b856c3cc5 | ||
|
|
d92888ed73 | ||
|
|
0357840654 | ||
|
|
53cae3a66d | ||
|
|
949a1ca4ae | ||
|
|
e667536a5b | ||
|
|
dde000a4bb | ||
|
|
aa1f5a8e7d | ||
|
|
bfbfcc2894 | ||
|
|
6abfa759aa | ||
|
|
7cbac2e757 | ||
|
|
1012381d3d | ||
|
|
6de96f62df | ||
|
|
e2ed2bdbf6 | ||
|
|
e248c54aa5 | ||
|
|
1c9635edad | ||
|
|
25cf6c52e4 | ||
|
|
7859ef6145 | ||
|
|
6efaa4ac7e | ||
|
|
19e17a8431 | ||
|
|
c865ae9734 | ||
|
|
6959f74073 | ||
|
|
9f4d21e976 | ||
|
|
719f39c01c | ||
|
|
25bb9c7320 | ||
|
|
a160e16abd | ||
|
|
ec31841aa5 | ||
|
|
3e249902c4 | ||
|
|
21bb766f06 | ||
|
|
a883151400 | ||
|
|
3c7fe82cbd | ||
|
|
899fc09704 | ||
|
|
daa45a44b9 | ||
|
|
52494628c3 | ||
|
|
dbf5d21b72 | ||
|
|
441bd4dfbf | ||
|
|
8452f05dda | ||
|
|
0d2325e646 | ||
|
|
90329d3d73 | ||
|
|
ca83a142f8 | ||
|
|
d3b2d3d5c5 | ||
|
|
255177487c | ||
|
|
ed0b75bcde | ||
|
|
06500a7909 | ||
|
|
6e16bf68ae | ||
|
|
ad22480117 | ||
|
|
2fcd09072f | ||
|
|
f0c785b196 | ||
|
|
84fe76bada | ||
|
|
b5ed8f59a7 | ||
|
|
52526f90d7 | ||
|
|
99e6e81e65 | ||
|
|
5af824f5cf | ||
|
|
2166bb2e88 | ||
|
|
ae884c0200 | ||
|
|
423ec7d08a | ||
|
|
914cd6ecb2 | ||
|
|
f221c82859 | ||
|
|
d57be723e6 | ||
|
|
777554809a | ||
|
|
f2b63fa4a8 | ||
|
|
4d06ce7454 | ||
|
|
8da6ec5fa3 | ||
|
|
2a5a8b498d | ||
|
|
a9b7d7d793 | ||
|
|
6578cf5dd7 | ||
|
|
2e21e8a43c | ||
|
|
176e1fd9d4 | ||
|
|
c3b274b18f | ||
|
|
8e134918fb | ||
|
|
2b531d2a1f | ||
|
|
87317dd54a | ||
|
|
618548c88d | ||
|
|
b62f42006c | ||
|
|
c275174f27 | ||
|
|
af4ed295e0 | ||
|
|
7614a03ea8 | ||
|
|
d9527726b6 | ||
|
|
05f4099709 | ||
|
|
a20a649013 | ||
|
|
0e3e64105c | ||
|
|
b2cdb1a6ae | ||
|
|
812432db96 | ||
|
|
5b2c4aef2d | ||
|
|
b7ccf424f4 | ||
|
|
77d5db168b | ||
|
|
e80cd1ff55 | ||
|
|
4ff25a21f6 | ||
|
|
f184a71360 | ||
|
|
fc38a9853d | ||
|
|
6023efed41 | ||
|
|
2abac6ce5c | ||
|
|
53c7705c36 | ||
|
|
7059ab65d6 | ||
|
|
e133988b36 | ||
|
|
9af27e7964 | ||
|
|
a57b3835fb | ||
|
|
451cbb809e | ||
|
|
fd1b64393d | ||
|
|
ed875a35b4 | ||
|
|
e85b0c2122 | ||
|
|
f5b192f55f | ||
|
|
ff86d5381d | ||
|
|
b63ac4addb | ||
|
|
3e1a66d617 | ||
|
|
93ed5a8cae | ||
|
|
2e6e8807be | ||
|
|
1ed36fabdb | ||
|
|
8dc40ee379 | ||
|
|
db4c7c89e3 | ||
|
|
f9bc7cc746 | ||
|
|
8f87ed65f9 | ||
|
|
39c37631f3 | ||
|
|
d0fb291c4e | ||
|
|
793523cbe9 | ||
|
|
6da4dd9ecc | ||
|
|
79892af8f8 | ||
|
|
b86790c854 | ||
|
|
c8f18c8c85 | ||
|
|
b19ecb4482 | ||
|
|
ba9af55aca | ||
|
|
35d76b3520 | ||
|
|
a5a0326092 | ||
|
|
a05833f845 | ||
|
|
c5deb60510 | ||
|
|
ff2ff89e3e | ||
|
|
35a346a674 | ||
|
|
1217a95c52 | ||
|
|
d025b4a0c3 | ||
|
|
2cab50f920 | ||
|
|
b049278dde | ||
|
|
93335e74cb | ||
|
|
2833a7b66e | ||
|
|
0d05b960f7 | ||
|
|
9fd64a9209 | ||
|
|
1637d26de3 | ||
|
|
525ff9b75e | ||
|
|
42e7932f59 | ||
|
|
98f40abfc3 | ||
|
|
55cd13ed4e | ||
|
|
7cb113fcbc | ||
|
|
ddbb999194 | ||
|
|
1db96829ed | ||
|
|
3498dea18c | ||
|
|
97a8c87490 | ||
|
|
fa72e09840 | ||
|
|
d8cad710a5 | ||
|
|
f91dcc33d3 | ||
|
|
e5f524ec56 | ||
|
|
f9b13cbc7f | ||
|
|
c7e0ba68f6 | ||
|
|
220262d192 | ||
|
|
1f860ecba6 | ||
|
|
69a13b71ea | ||
|
|
0a27afe6ee | ||
|
|
a3de021cd2 | ||
|
|
4dcbe78e83 | ||
|
|
84a8e8cbf6 | ||
|
|
e4b2b3d06b | ||
|
|
000a2ea00c | ||
|
|
89de91301e | ||
|
|
6be84bfa73 | ||
|
|
6cd79e7237 | ||
|
|
11d73173b8 | ||
|
|
9b3d62b765 | ||
|
|
2bdff53e68 | ||
|
|
8af5f9c28e | ||
|
|
9f71abf978 | ||
|
|
3fc2d3c1d9 | ||
|
|
c0c8db6dcc | ||
|
|
27249c0bb4 | ||
|
|
62e9684542 | ||
|
|
74b006cb0b | ||
|
|
77b33f11ab | ||
|
|
0aba638e6e | ||
|
|
e82522349f | ||
|
|
e520700260 | ||
|
|
3ec335f3a6 | ||
|
|
0f212e4dd1 | ||
|
|
c263654c88 | ||
|
|
874e3f24a6 | ||
|
|
b14343f3cf | ||
|
|
732b50dea7 | ||
|
|
be3bbe9b61 | ||
|
|
51211bbba0 | ||
|
|
5d1ff1c912 | ||
|
|
ea1be4fc50 | ||
|
|
7cc05f0d92 | ||
|
|
54606f3c21 | ||
|
|
a5d1705930 | ||
|
|
bfc0fb6c88 | ||
|
|
d1d4452217 | ||
|
|
2166a434a3 | ||
|
|
1053fa18e1 | ||
|
|
fa147d467e | ||
|
|
3f61598d6c | ||
|
|
9bf3724ce6 | ||
|
|
c2f27a4deb | ||
|
|
b6b99dde02 | ||
|
|
1fd0f4a8fe | ||
|
|
67d39737a4 | ||
|
|
aef4fe29a3 | ||
|
|
34c0677c45 | ||
|
|
3cb4a64b47 | ||
|
|
0db0375a5e | ||
|
|
47e6bdc644 | ||
|
|
66c2e27711 | ||
|
|
72387873b4 | ||
|
|
59017bc0bd | ||
|
|
2dde114d3d | ||
|
|
9e89904f03 | ||
|
|
56d0aca254 | ||
|
|
239710cebf | ||
|
|
1eaad617cb | ||
|
|
3c21994adc | ||
|
|
d15d14e197 | ||
|
|
e32694dc79 | ||
|
|
6ec8124287 | ||
|
|
2222adfc10 | ||
|
|
fcc2377657 | ||
|
|
1704ac4ae1 | ||
|
|
666e4348e0 | ||
|
|
9b22f279bc | ||
|
|
0835de983b | ||
|
|
1a892d58fc | ||
|
|
73a3cce4a4 | ||
|
|
d5ef4774fa | ||
|
|
c5bd4239a4 | ||
|
|
634e811888 | ||
|
|
3204998fcb | ||
|
|
12e428733a | ||
|
|
9cc6ad09a9 | ||
|
|
84abb5962e | ||
|
|
4bba55d2dc | ||
|
|
b4cabad44e | ||
|
|
28cc0f9e3b | ||
|
|
95a2cc18fe | ||
|
|
8e315a9859 | ||
|
|
89adcf4f4e | ||
|
|
3a6c5e41c9 | ||
|
|
86ed24b94c | ||
|
|
c792c471c3 | ||
|
|
e371cc2c3c | ||
|
|
ccf218c8f0 | ||
|
|
0d7fc0a573 | ||
|
|
74cacd5209 | ||
|
|
bb79cf2a87 | ||
|
|
28451df1a8 | ||
|
|
38e288f62a | ||
|
|
905f908450 | ||
|
|
672171fd0c | ||
|
|
520660ecbc | ||
|
|
06acb5faf2 | ||
|
|
d43fa03f05 | ||
|
|
baed1aaf92 | ||
|
|
cc229e803c | ||
|
|
d6b1728c23 | ||
|
|
bc5dcc359c | ||
|
|
ced07e1d6b | ||
|
|
cffffd9591 | ||
|
|
b8766e263f | ||
|
|
fc426d5764 | ||
|
|
056d2381cd | ||
|
|
2932a0ec5f | ||
|
|
d3d85a3fcf | ||
|
|
7a1feaa897 | ||
|
|
5f3cf72cc6 | ||
|
|
cae980788e | ||
|
|
df763b8765 | ||
|
|
365085809e | ||
|
|
3ee7998261 | ||
|
|
6fb9ed8312 | ||
|
|
89f79c35f5 | ||
|
|
6bdd4b2670 | ||
|
|
acd79d19e2 | ||
|
|
8f17873da2 | ||
|
|
b0cac776ee | ||
|
|
625dba4d85 |
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: monthly
|
||||
time: "15:00"
|
||||
open-pull-requests-limit: 10
|
||||
50
.github/pull_request_template.md
vendored
Normal file
50
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
||||
## High Level Overview of Change
|
||||
|
||||
<!--
|
||||
Please include a summary/list of the changes.
|
||||
If too broad, please consider splitting into multiple PRs.
|
||||
If a relevant Asana task, please link it here.
|
||||
-->
|
||||
|
||||
### Context of Change
|
||||
|
||||
<!--
|
||||
Please include the context of a change.
|
||||
If a bug fix, when was the bug introduced? What was the behavior?
|
||||
If a new feature, why was this architecture chosen? What were the alternatives?
|
||||
If a refactor, how is this better than the previous implementation?
|
||||
|
||||
If there is a design document for this feature, please link it here.
|
||||
-->
|
||||
|
||||
### Type of Change
|
||||
|
||||
<!--
|
||||
Please check relevant options, delete irrelevant ones.
|
||||
-->
|
||||
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
|
||||
- [ ] Refactor (non-breaking change that only restructures code)
|
||||
- [ ] Tests (You added tests for code that already exists, or your new feature included in this PR)
|
||||
- [ ] Documentation Updates
|
||||
- [ ] Release
|
||||
|
||||
## Before / After
|
||||
|
||||
<!--
|
||||
If just refactoring / back-end changes, this can be just an in-English description of the change at a technical level.
|
||||
If a UI change, screenshots should be included.
|
||||
-->
|
||||
|
||||
## Test Plan
|
||||
|
||||
<!--
|
||||
Please describe the tests that you ran to verify your changes and provide instructions so that others can reproduce.
|
||||
-->
|
||||
|
||||
<!--
|
||||
## Future Tasks
|
||||
For future tasks related to PR.
|
||||
-->
|
||||
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
67
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
# For most projects, this workflow file will not need changing; you simply need
|
||||
# to commit it to your repository.
|
||||
#
|
||||
# You may wish to alter this file to override the set of languages analyzed,
|
||||
# or to provide custom queries or build logic.
|
||||
#
|
||||
# ******** NOTE ********
|
||||
# We have attempted to detect the languages in your repository. Please check
|
||||
# the `language` matrix defined below to confirm you have the correct set of
|
||||
# supported CodeQL languages.
|
||||
#
|
||||
name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ develop ]
|
||||
schedule:
|
||||
- cron: '44 5 * * 6'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'javascript' ]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||
# Learn more:
|
||||
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v1
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 https://git.io/JvXDl
|
||||
|
||||
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||
# and modify them (or add more) to build your code if your project
|
||||
# uses a compiled language
|
||||
|
||||
#- run: |
|
||||
# make bootstrap
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v1
|
||||
103
.github/workflows/nodejs.yml
vendored
Normal file
103
.github/workflows/nodejs.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||
|
||||
name: Node.js CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ develop, master, 2.0 ]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build-and-lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install -g npm@7
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run lint
|
||||
|
||||
unit:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install -g npm@7
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
|
||||
integration:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [12.x, 14.x, 16.x]
|
||||
|
||||
services:
|
||||
rippled:
|
||||
image: natenichols/rippled-standalone:latest
|
||||
ports:
|
||||
- 6006:6006
|
||||
options:
|
||||
--health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install -g npm@7
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run test:integration
|
||||
env:
|
||||
HOST: localhost
|
||||
PORT: ${{ job.services.rippled.ports['6006'] }}
|
||||
|
||||
browser:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x] # This just needs to be compatible w/ puppeteer
|
||||
|
||||
services:
|
||||
rippled:
|
||||
image: natenichols/rippled-standalone:latest
|
||||
ports:
|
||||
- 6006:6006
|
||||
options:
|
||||
--health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm install -g npm@7
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm run test:browser
|
||||
26
.gitignore
vendored
26
.gitignore
vendored
@@ -1,5 +1,9 @@
|
||||
# .gitignore
|
||||
|
||||
# Ignore package locks other than npm.
|
||||
yarn.lock
|
||||
npm-shrinkwrap.json
|
||||
|
||||
# Ignore vim swap files.
|
||||
*.swp
|
||||
|
||||
@@ -17,7 +21,7 @@
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build/*.js
|
||||
build/
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
@@ -25,6 +29,7 @@ Release/*.*
|
||||
|
||||
# Ignore locally installed node_modules
|
||||
node_modules
|
||||
!test/node_modules
|
||||
|
||||
# Ignore tmp directory.
|
||||
tmp
|
||||
@@ -50,5 +55,22 @@ test/config.js
|
||||
# Ignore npm-debug
|
||||
npm-debug.log
|
||||
|
||||
# Ignore dist folder, build for bower
|
||||
# Ignore dist folder, built from tsc
|
||||
dist/
|
||||
|
||||
# TypeScript incremental compilation cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Ignore perf test cache
|
||||
scripts/cache
|
||||
|
||||
.eslintrc
|
||||
|
||||
# nyc (istanbul)
|
||||
.nyc_output
|
||||
|
||||
# browser tests
|
||||
testCompiledForWeb
|
||||
|
||||
# lerna debug
|
||||
lerna-debug.log
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
deploy
|
||||
lib-cov
|
||||
coverage.html
|
||||
13
.travis.yml
13
.travis.yml
@@ -1,13 +0,0 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
script: npm test --coverage
|
||||
after_success:
|
||||
- npm run coveralls
|
||||
notifications:
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/d1ec4245f90231619d30
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
6
.vscode/extensions.json
vendored
Normal file
6
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode"
|
||||
]
|
||||
}
|
||||
38
.vscode/settings.json
vendored
Normal file
38
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"cSpell.words": [
|
||||
"secp256k1"
|
||||
],
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"eslint.alwaysShowStatus": true,
|
||||
"eslint.lintTask.enable": true,
|
||||
"eslint.codeAction.showDocumentation": {
|
||||
"enable": true
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
},
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.watcherExclude": {
|
||||
"**/.git/objects/**": true,
|
||||
"**/.git/subtree-cache/**": true,
|
||||
"**/.hg/store/**": true
|
||||
},
|
||||
}
|
||||
163
APPLICATIONS.md
Normal file
163
APPLICATIONS.md
Normal file
@@ -0,0 +1,163 @@
|
||||
# Applications using xrpl.js
|
||||
|
||||
A curated list of some of the projects and apps that leverage `xrpl.js` in some way.
|
||||
|
||||
**Have one to add?** Please edit this file and open a PR!
|
||||
|
||||
## Notice (disclaimer)
|
||||
|
||||
These sites are independent of Ripple and have not been authorized, endorsed, sponsored or otherwise approved by Ripple or its affiliates.
|
||||
|
||||
Warning: Use at your own risk.
|
||||
|
||||
## Exchanges
|
||||
|
||||
- **[The World Exchange](https://www.theworldexchange.net/)**
|
||||
|
||||
Trade, issue, and send directly on the XRP Ledger. A user interface for the XRPL's decentralized exchange.
|
||||
|
||||
- **[Bitso](https://bitso.com/)**
|
||||
|
||||
Exchange allowing clients to buy and sell XRP, based in Mexico.
|
||||
|
||||
## Explorers
|
||||
|
||||
- **[xrpintel - XRP Intelligence](https://xrpintel.com/)**
|
||||
|
||||
Monitor the XRP Network in real time and explore historical statistics.
|
||||
|
||||
- **[XRP Charts](https://xrpcharts.ripple.com/)** (xrpcharts.ripple.com)
|
||||
|
||||
XRP Charts provides information based on public data, including trade volume, top markets, metrics, transactions, and more.
|
||||
|
||||
- **[Ripple Live](https://gatehub.net/live)** (gatehub.net/live)
|
||||
|
||||
Visualize XRP network transactions.
|
||||
|
||||
- **[XRPL Dev. Dashboard](https://xrp.fans/)** (xrp.fans)
|
||||
|
||||
Debugging dashboard for `rippled-ws-client-pool`, transaction and query explorer, and transaction signing and submission tool.
|
||||
|
||||
- **[XRP Value](http://xrpvalue.com/)**
|
||||
|
||||
Real-time XRP price, trades, and orderbook data from the XRP Ledger.
|
||||
|
||||
- **[Bithomp - XRP Explorer](https://bithomp.com/explorer/)**
|
||||
|
||||
Look up information by entering an address, transaction hash, username, or PayID.
|
||||
|
||||
- **[Bithomp - XRPL validators](https://bithomp.com/validators)**
|
||||
|
||||
List of XRPL validators, nodes, and testnet validators.
|
||||
|
||||
- **[XRP Scan - XRP Ledger explorer](https://xrpscan.com)**
|
||||
|
||||
XRP Ledger explorer, metrics and analytics.
|
||||
|
||||
- **[xrplorer](https://xrplorer.com)**
|
||||
|
||||
XRP Ledger explorer, API, metrics, and analytics using a graph database that is synchronized live with the XRPL.
|
||||
|
||||
## Data monitoring
|
||||
|
||||
- **[zerptracker](https://zerptracker.com)**
|
||||
|
||||
Monitor the XRPL using powerful JSONPath expressions, and receive notifications via email, SMS, webhooks, and more.
|
||||
|
||||
- **[Utility-Scan](https://utility-scan.com)**
|
||||
|
||||
Attempts to detect RippleNet on-demand liquidity (ODL) transactions through known fiat corridors and report these transactions in real time.
|
||||
|
||||
- **[XRPL Rosetta](https://xrpl-rosetta-oepox.ondigitalocean.app)**
|
||||
|
||||
3D Globe written in three.js connected to a Node.js websocket server that is listening to exchanges and the XRPL. The visualization aims to show trading, ODL, and liquidity at exchanges, intra-exchange volume, and flows.
|
||||
|
||||
## Wallets and wallet tools
|
||||
|
||||
- **[XUMM](https://xumm.app/)**
|
||||
|
||||
Users can use the xumm application to track their accounts, balances and transactions. The true power of xumm is the platform available for developers.
|
||||
|
||||
- **[Xpring Wallet](https://xpring.io)** (uses `ripple-keypairs`)
|
||||
|
||||
Non-custodial XRP wallet.
|
||||
|
||||
- **[XRP Toolkit](https://www.xrptoolkit.com)**
|
||||
|
||||
A web interface to the XRP Ledger, supporting both hardware and software wallets.
|
||||
|
||||
- **[Toast Wallet](https://toastwallet.com/)**
|
||||
|
||||
A free, open source XRP Wallet for iOS, Android, Windows, Mac and Linux.
|
||||
|
||||
- **[Toastify Ledger](https://github.com/WietseWind/toastify-ledger)** (uses `ripple-keypairs`)
|
||||
|
||||
Add a Regular Key to a mnemonic XRP Wallet (e.g. Ledger Nano S) to use the account with a Family Seed (secret).
|
||||
|
||||
- **[Bithomp-submit](https://github.com/Bithomp/bithomp-submit)** (GitHub)
|
||||
|
||||
A tool to submit an offline-signed XRPL transaction.
|
||||
|
||||
- **[Kyte](https://kyteapp.co/)** (kyteapp.co) ([Source](https://github.com/WietseWind/Zerp-Wallet)) (Deprecated)
|
||||
|
||||
Web-based XRP wallet.
|
||||
|
||||
- **[XRP Vanity Address Generator](https://github.com/WietseWind/xrp-vanity-generator)** (Node.js)
|
||||
|
||||
A vanity address is a wallet address containing a few characters you like at the beginning or the end of the wallet address.
|
||||
|
||||
- **[XRP Account Mnemonic Recovery](https://github.com/WietseWind/xrp-mnemonic-recovery)** (uses `ripple-keypairs`)
|
||||
|
||||
Recover a 24 word mnemonic if one word is wrong or one word is missing.
|
||||
|
||||
## Send and request payments
|
||||
|
||||
- **[XRP Tip Bot](https://www.xrptipbot.com/)**
|
||||
|
||||
A bot that enables users on reddit, Twitter and Discord to send XRP to each other through reddit comments and Twitter tweets.
|
||||
|
||||
- **[XRP Text](https://xrptext.com/)**
|
||||
|
||||
Send XRP using SMS text messages.
|
||||
|
||||
- **[XRParrot](https://xrparrot.com/)** (uses `ripple-address-codec`)
|
||||
|
||||
Easy EUR (SEPA) to XRP transfer (currency conversion).
|
||||
|
||||
- **[XRP Payment](https://xrpayments.co/)** (xrpayments.co)
|
||||
|
||||
Tool for generating a XRP payment request URI in a QR code, with currency converter.
|
||||
|
||||
## Development tools
|
||||
|
||||
- **[XRP Faucets for Testnet and Devnet](https://xrpl.org/xrp-testnet-faucet.html)**
|
||||
|
||||
Get some test funds for development on the test network. The faucet uses `xrpl.js`.
|
||||
|
||||
## Code samples and libraries
|
||||
|
||||
- **[ilp-plugin-xrp-paychan](https://github.com/interledgerjs/ilp-plugin-xrp-paychan)**
|
||||
|
||||
Send ILP payments using XRP and payment channels (PayChan).
|
||||
|
||||
- **[RunKit: WietseWind](https://runkit.com/wietsewind/)**
|
||||
|
||||
XRP Ledger code samples for Node.js.
|
||||
|
||||
- **[GitHub Gist: WietseWind](https://gist.github.com/WietseWind)**
|
||||
|
||||
XRP Ledger code samples for Node.js and the web (mostly).
|
||||
|
||||
- **[rippled-ws-client-sign](https://github.com/WietseWind/rippled-ws-client-sign)**
|
||||
|
||||
Sign transactions, with support for MultiSign.
|
||||
|
||||
- **[ILP-enabled power switch](https://xrpcommunity.blog/raspberry-pi-interledger-xp-powerswitch-howto/)** ([video](https://www.youtube.com/watch?v=c-eS0HQUuJg)) (uses [`moneyd-uplink-xrp`](https://github.com/interledgerjs/moneyd-uplink-xrp))
|
||||
|
||||
For about $30 in parts (Raspberry Pi, 3.3V Relay board and a few wires) you can build your own power switch that will switch on if a streaming ILP payment comes in. When the payment stream stops, the power turns off.
|
||||
|
||||
## Related apps that do not appear to use xrpl.js
|
||||
|
||||
- **[XRP Stats](https://ledger.exposed/)** (ledger.exposed)
|
||||
|
||||
Rich list, live ledger stats and XRP distribution. Visualize escrows and flow of funds.
|
||||
149
CONTRIBUTING.md
Normal file
149
CONTRIBUTING.md
Normal file
@@ -0,0 +1,149 @@
|
||||
# Contributing
|
||||
|
||||
## Set up your dev environment
|
||||
|
||||
### Requirements
|
||||
|
||||
We use Node v14 for development - that is the version that our linters require.
|
||||
You must also use `npm` v7. You can check your `npm` version with:
|
||||
|
||||
```bash
|
||||
npm -v
|
||||
```
|
||||
|
||||
If your `npm` version is too old, use this command to update it:
|
||||
|
||||
```bash
|
||||
npm -g i npm@7
|
||||
```
|
||||
|
||||
### Set up
|
||||
|
||||
1. Clone the repository
|
||||
2. `cd` into the repository
|
||||
3. Install dependencies with `npm install`
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Run the linter
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npm run lint
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
For integration and browser tests, we use a `rippled` node in standalone mode to test xrpl.js code against. To set this up, you can either run `rippled` locally, or set up the Docker container `natenichols/rippled-standalone:latest` for this purpose. The latter will require you to [install Docker](https://docs.docker.com/get-docker/).
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build
|
||||
npm test
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```bash
|
||||
npm install
|
||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
||||
docker run -p 6006:6006 -it natenichols/rippled-standalone:latest
|
||||
npm run build
|
||||
npm run test:integration
|
||||
```
|
||||
|
||||
### Browser Tests
|
||||
|
||||
There are two ways to run browser tests.
|
||||
|
||||
One is in the browser - run `npm run build:browserTests` and open `test/localIntegrationRunner.html` in your browser.
|
||||
|
||||
The other is in the command line (this is what we use for CI) -
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
||||
docker run -p 6006:6006 -it natenichols/rippled-standalone:latest
|
||||
npm run test:browser
|
||||
```
|
||||
|
||||
## Generate reference docs
|
||||
|
||||
You can see the complete reference documentation at [`xrpl.js` docs](js.xrpl.org). You can also generate them locally using `typedoc`:
|
||||
|
||||
```bash
|
||||
npm run docgen
|
||||
```
|
||||
|
||||
## Adding and removing packages
|
||||
|
||||
`xrpl.js` uses `lerna` and `npm`'s workspaces features to manage a monorepo.
|
||||
Adding and removing packages requires a slightly different process than normal
|
||||
as a result.
|
||||
|
||||
### Adding or removing development dependencies
|
||||
|
||||
`xrpl.js` strives to use the same development dependencies in all packages.
|
||||
You may add and remove dev dependencies like normal:
|
||||
|
||||
```bash
|
||||
### adding a new dependency
|
||||
npm install --save-dev abbrev
|
||||
### removing a dependency
|
||||
npm uninstall --save-dev abbrev
|
||||
```
|
||||
|
||||
### Adding or removing runtime dependencies
|
||||
|
||||
You need to specify which package is changing using the `-w` flag:
|
||||
|
||||
```bash
|
||||
### adding a new dependency to `xrpl`
|
||||
npm install abbrev -w xrpl
|
||||
### adding a new dependency to `ripple-keypairs`
|
||||
npm install abbrev -w ripple-keypairs
|
||||
### removing a dependency
|
||||
npm uninstall abbrev -w xrpl
|
||||
```
|
||||
|
||||
## Release process
|
||||
|
||||
### Editing the Code
|
||||
|
||||
* Your changes should have unit and/or integration tests.
|
||||
* Your changes should pass the linter.
|
||||
* Your code should pass all the tests on Github (which check the linter, unit and integration tests on Node 12/14/16, and browser tests).
|
||||
* Open a PR against `develop` and ensure that all CI passes.
|
||||
* Get a full code review from one of the maintainers.
|
||||
* Merge your changes.
|
||||
|
||||
### Release
|
||||
|
||||
1. Ensure that all tests passed on the last CI that ran on `develop`.
|
||||
2. Open a PR to update the docs if docs were modified.
|
||||
3. Create a branch off `develop` that ensures that `HISTORY.md` is updated appropriately for each package.
|
||||
* Use `shasum -a 256 build/*` to get the SHA-256 checksums. Add these to `HISTORY.md` as well.
|
||||
4. Merge this branch into `develop`.
|
||||
5. If this is not a beta release: Merge `develop` into `master` (`--ff-only`) and push to github. This is important because we have docs telling developers to use master to get the latest release.
|
||||
6. Create a new Github release/tag off of this branch.
|
||||
7. Run `npm publish --dry-run` and make sure everything looks good.
|
||||
8. Publish the release to `npm`.
|
||||
* If this is a beta release, run `npm publish --tag beta`. This allows someone else to install this version of the package with `npm install xrpl@beta`.
|
||||
* If this is a stable release, run `npm publish`.
|
||||
* This will require entering `npm` login info.
|
||||
9. Send an email to [xrpl-announce](https://groups.google.com/g/xrpl-announce).
|
||||
|
||||
## Mailing Lists
|
||||
We have a low-traffic mailing list for announcements of new `xrpl.js` releases. (About 1 email every couple of weeks)
|
||||
|
||||
+ [Subscribe to xrpl-announce](https://groups.google.com/g/xrpl-announce)
|
||||
|
||||
If you're using the XRP Ledger in production, you should run a [rippled server](https://github.com/ripple/rippled) and subscribe to the ripple-server mailing list as well.
|
||||
|
||||
+ [Subscribe to ripple-server](https://groups.google.com/g/ripple-server)
|
||||
163
Gulpfile.js
163
Gulpfile.js
@@ -1,163 +0,0 @@
|
||||
var gulp = require('gulp');
|
||||
var concat = require('gulp-concat');
|
||||
var uglify = require('gulp-uglify');
|
||||
var rename = require('gulp-rename');
|
||||
var webpack = require('webpack');
|
||||
var jshint = require('gulp-jshint');
|
||||
var map = require('map-stream');
|
||||
var bump = require('gulp-bump');
|
||||
var argv = require('yargs').argv;
|
||||
//var header = require('gulp-header');
|
||||
|
||||
var pkg = require('./package.json');
|
||||
|
||||
var banner = '/*! <%= pkg.name %> - v<%= pkg.version %> - '
|
||||
+ '<%= new Date().toISOString() %>\n'
|
||||
+ '<%= pkg.homepage ? "* " + pkg.homepage + "\n" : "" %>'
|
||||
+ '* Copyright (c) <%= new Date().getFullYear() %> <%= pkg.author.name %>;'
|
||||
+ ' Licensed <%= pkg.license %> */'
|
||||
|
||||
var sjclSrc = [
|
||||
'src/js/sjcl/core/sjcl.js',
|
||||
'src/js/sjcl/core/aes.js',
|
||||
'src/js/sjcl/core/bitArray.js',
|
||||
'src/js/sjcl/core/codecString.js',
|
||||
'src/js/sjcl/core/codecHex.js',
|
||||
'src/js/sjcl/core/codecBase64.js',
|
||||
'src/js/sjcl/core/codecBytes.js',
|
||||
'src/js/sjcl/core/sha256.js',
|
||||
'src/js/sjcl/core/sha512.js',
|
||||
'src/js/sjcl/core/sha1.js',
|
||||
'src/js/sjcl/core/ccm.js',
|
||||
// 'src/js/sjcl/core/cbc.js',
|
||||
// 'src/js/sjcl/core/ocb2.js',
|
||||
'src/js/sjcl/core/hmac.js',
|
||||
'src/js/sjcl/core/pbkdf2.js',
|
||||
'src/js/sjcl/core/random.js',
|
||||
'src/js/sjcl/core/convenience.js',
|
||||
'src/js/sjcl/core/bn.js',
|
||||
'src/js/sjcl/core/ecc.js',
|
||||
'src/js/sjcl/core/srp.js',
|
||||
'src/js/sjcl-custom/sjcl-ecc-pointextras.js',
|
||||
'src/js/sjcl-custom/sjcl-secp256k1.js',
|
||||
'src/js/sjcl-custom/sjcl-ripemd160.js',
|
||||
'src/js/sjcl-custom/sjcl-extramath.js',
|
||||
'src/js/sjcl-custom/sjcl-montgomery.js',
|
||||
'src/js/sjcl-custom/sjcl-validecc.js',
|
||||
'src/js/sjcl-custom/sjcl-ecdsa-canonical.js',
|
||||
'src/js/sjcl-custom/sjcl-ecdsa-der.js',
|
||||
'src/js/sjcl-custom/sjcl-ecdsa-recoverablepublickey.js',
|
||||
'src/js/sjcl-custom/sjcl-jacobi.js'
|
||||
];
|
||||
|
||||
gulp.task('concat-sjcl', function() {
|
||||
return gulp.src(sjclSrc)
|
||||
.pipe(concat('sjcl.js'))
|
||||
.pipe(gulp.dest('./build/'));
|
||||
});
|
||||
|
||||
gulp.task('build', [ 'concat-sjcl' ], function(callback) {
|
||||
webpack({
|
||||
cache: true,
|
||||
entry: './src/js/ripple/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: './build/',
|
||||
filename: [ 'ripple-', '.js' ].join(pkg.version)
|
||||
},
|
||||
}, callback);
|
||||
});
|
||||
|
||||
gulp.task('bower-build', [ 'build' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
|
||||
.pipe(rename('ripple.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-min', [ 'build-min' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '-min.js' ].join(pkg.version))
|
||||
.pipe(rename('ripple-min.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-debug', [ 'build-debug' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '-debug.js' ].join(pkg.version))
|
||||
.pipe(rename('ripple-debug.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('bower-version', function() {
|
||||
gulp.src('./dist/bower.json')
|
||||
.pipe(bump({version: pkg.version}))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
});
|
||||
|
||||
gulp.task('version-bump', function() {
|
||||
if (!argv.type) {
|
||||
throw new Error("No type found, pass it in using the --type argument");
|
||||
}
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({type:argv.type}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('version-beta', function() {
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({version: pkg.version+'-beta'}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('build-min', [ 'build' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
|
||||
.pipe(uglify())
|
||||
.pipe(rename([ 'ripple-', '-min.js' ].join(pkg.version)))
|
||||
.pipe(gulp.dest('./build/'));
|
||||
});
|
||||
|
||||
gulp.task('build-debug', [ 'concat-sjcl' ], function(callback) {
|
||||
webpack({
|
||||
cache: true,
|
||||
entry: './src/js/ripple/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: './build/',
|
||||
filename: [ 'ripple-', '-debug.js' ].join(pkg.version)
|
||||
},
|
||||
debug: true,
|
||||
devtool: 'eval'
|
||||
}, callback);
|
||||
});
|
||||
|
||||
gulp.task('lint', function() {
|
||||
gulp.src('src/js/ripple/*.js')
|
||||
.pipe(jshint())
|
||||
.pipe(map(function(file, callback) {
|
||||
if (!file.jshint.success) {
|
||||
console.log('\nIn', file.path);
|
||||
|
||||
file.jshint.results.forEach(function(err) {
|
||||
if (err && err.error) {
|
||||
var col1 = err.error.line + ':' + err.error.character;
|
||||
var col2 = '[' + err.error.reason + ']';
|
||||
var col3 = '(' + err.error.code + ')';
|
||||
|
||||
while (col1.length < 8) {
|
||||
col1 += ' ';
|
||||
}
|
||||
|
||||
console.log(' ' + [ col1, col2, col3 ].join(' '));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
callback(null, file);
|
||||
}));
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/js/ripple/*', [ 'build-debug' ]);
|
||||
});
|
||||
|
||||
gulp.task('default', [ 'concat-sjcl', 'build', 'build-debug', 'build-min' ]);
|
||||
|
||||
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug', 'bower-version']);
|
||||
211
HISTORY.md
211
HISTORY.md
@@ -1,210 +1,3 @@
|
||||
##0.9.2
|
||||
|
||||
+ [**Breaking change**: Change accountRequest method signature](https://github.com/ripple/ripple-lib/commit/6f5d1104aa3eb440c518ec4f39e264fdce15fa15)
|
||||
|
||||
+ [Add paging behavior for account requests, `account_lines` and `account_offers`](https://github.com/ripple/ripple-lib/commit/722f4e175dbbf378e51b49142d0285f87acb22d7)
|
||||
|
||||
+ [Add max_fee setter to transactions to set max fee the submitter is willing to pay] (https://github.com/ripple/ripple-lib/commit/24587fab9c8ad3840d7aa345a7037b48839e09d7)
|
||||
|
||||
+ [Fix: cap IOU Amounts to their max and min value] (https://github.com/ripple/ripple-lib/commit/f05941fbc46fdb7c6fe7ad72927af02d527ffeed)
|
||||
|
||||
Example on how to use paging with `account_offers`:
|
||||
```
|
||||
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
|
||||
// Results can change between ledger closes, so the provided ledger will be used as base.
|
||||
var options = {
|
||||
account: < rippleAccount >,
|
||||
limit: < Number between 10 and 400 >,
|
||||
ledger: < valid ledger_index or ledger_hash >
|
||||
}
|
||||
|
||||
// The `marker` comes back in an account request if there are more results than are returned
|
||||
// in the current response. The amount of results per response are determined by the `limit`.
|
||||
if (marker) {
|
||||
options.marker = < marker >;
|
||||
}
|
||||
|
||||
var request = remote.requestAccountOffers(options);
|
||||
```
|
||||
|
||||
[Full working example](https://github.com/geertweening/ripple-lib-scripts/blob/master/account_offers_paging.js)
|
||||
|
||||
|
||||
##0.9.1
|
||||
|
||||
+ Switch account requests to use ledgerSelect rather than ledgerChoose ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
|
||||
|
||||
+ **Deprecated** setting `ident` and `account_index` on account requests ([278df90](https://github.com/ripple/ripple-lib/commit/278df9025a20228de22379a53c76ca12d40fa591))
|
||||
|
||||
+ Change initial account transaction sequence to 1 ([a3c1d06](https://github.com/ripple/ripple-lib/commit/a3c1d06eba883dc84fe2bfe700e4309795c84cac))
|
||||
|
||||
+ Fix: instance transaction withoute remote ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca))
|
||||
|
||||
+ Fix: account root request ledger argument ([bc1f9f8](https://github.com/ripple/ripple-lib/commit/bc1f9f8a286b187d36ebaf552694e31e73742293))
|
||||
|
||||
+ Fix: rsign.js local signing and example ([d3b6b81](https://github.com/ripple/ripple-lib/commit/d3b6b8127c7b01e416b400c25abf1719bdd008ca) and [f1004c6](https://github.com/ripple/ripple-lib/commit/f1004c6db2a0ce59bbabbb8f2b355a9fd9995fd8))
|
||||
|
||||
|
||||
##0.9.0
|
||||
|
||||
+ Add routes to the vault client for KYC attestations ([ed2da574](https://github.com/ripple/ripple-lib/commit/ed2da57475acf5e9d2cf3373858f4274832bd83f))
|
||||
|
||||
+ Currency: add `show_interest` flag to show or hide interest in `Currency.to_human()` and `Currency.to_json()` [Example use in tests](https://github.com/ripple/ripple-lib/blob/947ec3edc2e7c8f1ef097e496bf552c74366e749/test/currency-test.js#L123)
|
||||
|
||||
+ Configurable maxAttempts for transaction submission ([d107092](https://github.com/ripple/ripple-lib/commit/d10709254061e9e4416d2cb78b5cac1ec0d7ffa5))
|
||||
|
||||
+ Binformat: added missing TransactionResult options ([6abed8d](https://github.com/ripple/ripple-lib/commit/6abed8dd5311765b2eb70505dadbdf5121439ca8))
|
||||
|
||||
+ **Breaking change:** make maxLoops in seed.get_key optional. [Example use in tests](https://github.com/ripple/ripple-lib/blob/23e473b6886c457781949c825b3ff48b3984e51f/test/seed-test.js) ([23e473b](https://github.com/ripple/ripple-lib/commit/23e473b6886c457781949c825b3ff48b3984e51f))
|
||||
|
||||
+ Shrinkwrap packages for dependency locking ([2dcd5f9](2dcd5f94fbc71200eb08a5044c76ef94f7971913))
|
||||
|
||||
+ Fix: Amount.to_human() precision bugs ([4be209e](https://github.com/ripple/ripple-lib/commit/4be209e286b5b209bec7bcd1212098985e15ff2f) and [7708c64](https://github.com/ripple/ripple-lib/commit/7708c64576e70ce3ac190442daceb30e4446aab7))
|
||||
|
||||
+ Fix: change handling of requestLedger options ([57b7030](https://github.com/ripple/ripple-lib/commit/57b70300f5f0c7534ede118ddbb5d8762668a4f8))
|
||||
|
||||
|
||||
##0.8.2
|
||||
|
||||
+ Currency: Allow mixed letters and numbers in currencies
|
||||
|
||||
+ Deprecate account_tx map/reduce/filterg
|
||||
|
||||
+ Fix: correct requestLedger arguments
|
||||
|
||||
+ Fix: missing subscription on error events for some server methods
|
||||
|
||||
+ Fix: orderbook reset on reconnect
|
||||
|
||||
+ Fix: ripple-lib crashing. Add potential missing error handlers
|
||||
|
||||
|
||||
##0.8.1
|
||||
|
||||
+ Wallet: Add Wallet class that generates wallets
|
||||
|
||||
+ Make npm test runnable in Windows.
|
||||
|
||||
+ Fix several stability issues, see merged PR's for details
|
||||
|
||||
+ Fix bug in Amount.to_human_full()
|
||||
|
||||
+ Fix undefined fee states when connecting to a rippled that is syncing
|
||||
|
||||
|
||||
##0.8.0
|
||||
|
||||
+ Orderbook: Added tracking of offer funds for determining when offers are not funded
|
||||
|
||||
+ Orderbook: Added tests
|
||||
|
||||
+ Orderbook: Update owner funds
|
||||
|
||||
+ Transactions: If transaction errs with `tefALREADY`, wait until all possible submissions err with the same before emitting `error`. Fixes a client "Transaction malformed" bug.
|
||||
|
||||
+ Transactions: Track submissions, don't bother submitting to unconnected servers
|
||||
|
||||
+ Request: `request.request()` now accepts an array of servers as first argument. Servers can be represented with URL, or the server object itself.
|
||||
|
||||
+ Request: `request.broadcast()` now returns the number of servers request was sent to
|
||||
|
||||
+ Server: Acquire host information from server without additional request
|
||||
|
||||
+ Amount: Add a constant for the maximum canonical value that can be expressed as a Ripple value
|
||||
|
||||
+ Amount: Make Constants static fields on the class, instead of a seperate export
|
||||
|
||||
|
||||
##0.7.39
|
||||
|
||||
+ Improvements to multi-server support. Fixed an issue where a server's score was not reset and connections would keep dropping after being connected for a significant amount of time.
|
||||
|
||||
+ Improvements in order book support. Added support for currency pairs with interest bearing currencies. You can request an order book with hex, ISO code or full name for the currency.
|
||||
|
||||
+ Fix value parsing for amount/currency order pairs, e.g. `Amount.from_human("XAU 12345.6789")`
|
||||
|
||||
+ Improved Amount parsing from human readable string given a hex currency, e.g. `Amount.from_human("10 015841551A748AD2C1F76FF6ECB0CCCD00000000")`
|
||||
|
||||
+ Improvements to username normalization in the vault client
|
||||
|
||||
+ Add 2-factor authentication support for vault client
|
||||
|
||||
+ Removed vestiges of Grunt, switched to Gulp
|
||||
|
||||
|
||||
##0.7.37
|
||||
|
||||
+ **Deprecations**
|
||||
|
||||
1. Removed humanistic amount detection in `transaction.payment`. Passing `1XRP` as the payment amount no longer works.
|
||||
2. `remote.setServer` uses full server URL rather than hostname. Example: `remote.setServer('wss://s`.ripple.com:443')`
|
||||
3. Removed constructors for deprecated transaction types from `transaction.js`.
|
||||
4. Removed `invoiceID` option from `transaction.payment`. Instead, use the `transaction.invoiceID` method.
|
||||
5. Removed `transaction.transactionManager` getter.
|
||||
|
||||
+ Improved multi-server support. Servers are now ranked dynamically, and transactions are broadcasted to all connected servers.
|
||||
|
||||
+ Automatically ping connected servers. Client configuration now should contain `ping: <seconds>` to specify the ping interval.
|
||||
|
||||
+ Added `transaction.lastLedger` to specify `LastLedgerSequence`. Setting it this way also ensures that the sequence is not bumped on subsequent requests.
|
||||
|
||||
+ Added optional `remote.accountTx` binary parsing.
|
||||
```js
|
||||
{
|
||||
binary: true,
|
||||
parseBinary: false
|
||||
}
|
||||
```
|
||||
+ Added full currency name support, e.g. `Currency.from_json('XRP').to_human({full_name:'Ripples'})` will return `XRP - Ripples`
|
||||
|
||||
+ Improved interest bearing currency support, e.g. `Currency.from_human('USD - US Dollar (2.5%pa)')`
|
||||
|
||||
+ Improve test coverage
|
||||
|
||||
+ Added blob vault client. The vault client facilitates interaction with ripple's namespace and blob vault or 3rd party blob vaults using ripple's blob vault software (https://github.com/ripple/ripple-blobvault). A list of the available functions can be found at [docs/VAULTCLIENT.md](docs/VAULTCLIENT.md)
|
||||
|
||||
|
||||
##0.7.35
|
||||
|
||||
+ `LastLedgerSequence` is set by default on outgoing transactions. This refers to the last valid ledger index (AKA sequence) for a transaction. By default, this index is set to the current index (at submission time) plus 8. In theory, this allows ripple-lib to deterministically fail a transaction whose submission request timed out, but whose associated server continues to emit ledger_closed events.
|
||||
|
||||
+ Transactions that err with `telINSUF_FEE_P` will be automatically resubmitted. This error indicates that the `Fee` supplied in the transaction submission request was inadquate. Ideally, the `Fee` is tracked by ripple-lib in real-time, and the resubmitted transaction will most likely succeed.
|
||||
|
||||
+ Added Transaction.iff(function(callback) { }). Callback expects first argument to be an Error or null, second argument is a boolean which indicates whether or not to proceed with the transaction submission. If an `iff` function is specified, it will be executed prior to every submission of the transaction (including resubmissions).
|
||||
|
||||
+ Transactions will now emit `presubmit` and `postsubmit` events. They will be emitted before and after a transaction is submitted, respectively.
|
||||
|
||||
+ Added Transaction.summary(). Returns a summary of a transaction in semi-human-readable form. JSON-stringifiable.
|
||||
|
||||
+ Remote.requestAccountTx() with `binary: true` will automatically parse transactions.
|
||||
|
||||
+ Added Remote.requestAccountTx filter, map, and reduce.
|
||||
|
||||
```js
|
||||
remote.requestAccountTx({
|
||||
account: 'retc',
|
||||
ledger_index_min: -1,
|
||||
ledger_index_max: -1,
|
||||
limit: 100,
|
||||
binary: true,
|
||||
|
||||
filter: function(transaction) {
|
||||
return transaction.tx.TransactionType === 'Payment';
|
||||
},
|
||||
|
||||
map: function(transaction) {
|
||||
return Number(transaction.tx.Amount);
|
||||
},
|
||||
|
||||
reduce: function(a, b) {
|
||||
return a + b;
|
||||
},
|
||||
|
||||
pluck: 'transactions'
|
||||
}, console.log)
|
||||
```
|
||||
|
||||
+ Added persistence hooks.
|
||||
|
||||
+ General performance improvements, especially for long-running processes.
|
||||
# xrpl.js (ripple-lib) Release History
|
||||
|
||||
Please see the individual HISTORY.md documents in each package for changes.
|
||||
|
||||
52
LICENSE
52
LICENSE
@@ -1,52 +0,0 @@
|
||||
Copyright (c) 2012,2013,2014 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
--------------------------------------
|
||||
|
||||
Some code from Tom Wu:
|
||||
This software is covered under the following copyright:
|
||||
|
||||
Copyright (c) 2003-2005 Tom Wu
|
||||
All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
|
||||
WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
IN NO EVENT SHALL TOM WU BE LIABLE FOR ANY SPECIAL, INCIDENTAL,
|
||||
INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND, OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER OR NOT ADVISED OF
|
||||
THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF LIABILITY, ARISING OUT
|
||||
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
In addition, the following condition applies:
|
||||
|
||||
All redistributions must retain an intact copy of this copyright notice
|
||||
and disclaimer.
|
||||
|
||||
Address all questions regarding this license to:
|
||||
|
||||
Tom Wu
|
||||
tjw@cs.Stanford.EDU
|
||||
176
README.md
176
README.md
@@ -1,94 +1,146 @@
|
||||
#ripple-lib
|
||||
# xrpl.js
|
||||
|
||||
A JavaScript API for interacting with Ripple in Node.js and the browser
|
||||
A JavaScript/TypeScript library for interacting with the XRP Ledger
|
||||
|
||||
[](https://travis-ci.org/ripple/ripple-lib) [](https://coveralls.io/r/ripple/ripple-lib?branch=develop)
|
||||
[](https://www.npmjs.org/package/xrpl)
|
||||

|
||||
|
||||
[](https://www.npmjs.org/package/ripple-lib)
|
||||
This is the recommended library for integrating a JavaScript/TypeScript app with the XRP Ledger, especially if you intend to use advanced functionality such as IOUs, payment paths, the decentralized exchange, account settings, payment channels, escrows, multi-signing, and more.
|
||||
|
||||
###Features
|
||||
## [➡️ Reference Documentation](http://js.xrpl.org)
|
||||
|
||||
+ Connect to a rippled server in JavaScript (Node.js or browser)
|
||||
+ Issue [rippled API](https://ripple.com/wiki/JSON_Messages) requests
|
||||
+ Listen to events on the Ripple network (transaction, ledger, etc.)
|
||||
+ Sign and submit transactions to the Ripple network
|
||||
See the full reference documentation for all classes, methods, and utilities.
|
||||
|
||||
###In this file
|
||||
## [➡️ Applications and Projects](https://github.com/XRPLF/xrpl.js/blob/master/APPLICATIONS.md)
|
||||
|
||||
1. [Installation](README.md#installation)
|
||||
2. [Quickstart](README.md#quickstart)
|
||||
3. [Running tests](https://github.com/ripple/ripple-lib#running-tests)
|
||||
What is `xrpl.js` used for? The applications on the list linked above use `xrpl.js`. Open a PR to add your app or project to the list!
|
||||
|
||||
###Additional documentation
|
||||
### Features
|
||||
|
||||
1. [Guides](docs/GUIDES.md)
|
||||
2. [API Reference](docs/REFERENCE.md)
|
||||
3. [Wiki](https://ripple.com/wiki/Ripple_JavaScript_library)
|
||||
+ Works in Node.js and in web browsers
|
||||
+ Helpers for creating requests and parsing responses for the [XRP Ledger APIs](https://xrpl.org/rippled-api.html)
|
||||
+ Listen to events on the XRP Ledger (transactions, ledger, validations, etc.)
|
||||
+ Sign and submit transactions to the XRP Ledger
|
||||
+ Type definitions for TypeScript
|
||||
|
||||
###Also see
|
||||
### Requirements
|
||||
|
||||
+ [The Ripple wiki](https://ripple.com/wiki)
|
||||
+ [ripple.com](https://ripple.com)
|
||||
+ **[Node.js v14](https://nodejs.org/)** is recommended. We also support v12 and v16. Other versions may work but are not frequently tested.
|
||||
|
||||
##Installation
|
||||
## Getting Started
|
||||
|
||||
**Via npm for Node.js**
|
||||
In an existing project (with `package.json`), install `xrpl.js`:
|
||||
|
||||
```
|
||||
$ npm install ripple-lib
|
||||
$ npm install --save xrpl
|
||||
```
|
||||
|
||||
**Via bower (for browser use)**
|
||||
Or with `yarn`:
|
||||
|
||||
```
|
||||
$ bower install ripple
|
||||
$ yarn add xrpl
|
||||
```
|
||||
|
||||
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions
|
||||
|
||||
|
||||
**Building ripple-lib from github**
|
||||
|
||||
```
|
||||
$ git clone https://github.com/ripple/ripple-lib
|
||||
$ npm install
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
Then use the minified `build/ripple-*-min.js`
|
||||
|
||||
##Quickstart
|
||||
|
||||
`Remote.js` ([remote.js](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/remote.js)) is the point of entry for interacting with rippled
|
||||
Example usage:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
const xrpl = require("xrpl")
|
||||
async function main() {
|
||||
const client = new xrpl.Client("wss://s.altnet.rippletest.net:51233")
|
||||
await client.connect()
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
const response = await client.request({
|
||||
"command": "account_info",
|
||||
"account": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
|
||||
"ledger_index": "validated"
|
||||
})
|
||||
console.log(response)
|
||||
|
||||
var remote = new Remote({
|
||||
// see the API Reference for available options
|
||||
servers: [ 'wss://s1.ripple.com:443' ]
|
||||
});
|
||||
|
||||
remote.connect(function() {
|
||||
/* remote connected */
|
||||
remote.requestServerInfo(function(err, info) {
|
||||
// process err and info
|
||||
});
|
||||
});
|
||||
client.disconnect()
|
||||
}
|
||||
main()
|
||||
```
|
||||
|
||||
##Running tests
|
||||
For more examples, see the [documentation](#documentation).
|
||||
|
||||
1. Clone the repository
|
||||
### Using xrpl.js with React Native
|
||||
|
||||
2. `cd` into the repository and install dependencies with `npm install`
|
||||
If you want to use `xrpl.js` with React Native you will need to install shims for core NodeJS modules. To help with this you can use a module like [rn-nodeify](https://github.com/tradle/rn-nodeify).
|
||||
|
||||
3. `npm test`
|
||||
1. Install dependencies (you can use `yarn` as well):
|
||||
|
||||
**Generating code coverage**
|
||||
```shell
|
||||
npm install react-native-crypto
|
||||
npm install xrpl
|
||||
# install peer deps
|
||||
npm install react-native-randombytes
|
||||
# install latest rn-nodeify
|
||||
npm install rn-nodeify@latest --dev
|
||||
```
|
||||
|
||||
ripple-lib uses `istanbul` to generate code coverage. To create a code coverage report, run `npm test --coverage`. The report will be created in `coverage/lcov-report/`.
|
||||
2. After that, run the following command:
|
||||
|
||||
```shell
|
||||
# install node core shims and recursively hack package.json files
|
||||
# in ./node_modules to add/update the "browser"/"react-native" field with relevant mappings
|
||||
./node_modules/.bin/rn-nodeify --hack --install
|
||||
```
|
||||
|
||||
3. Enable `crypto`:
|
||||
|
||||
`rn-nodeify` will create a `shim.js` file in the project root directory.
|
||||
Open it and uncomment the line that requires the crypto module:
|
||||
|
||||
```javascript
|
||||
// If using the crypto shim, uncomment the following line to ensure
|
||||
// crypto is loaded first, so it can populate global.crypto
|
||||
require('crypto')
|
||||
```
|
||||
|
||||
4. Import `shim` in your project (it must be the first line):
|
||||
|
||||
```javascript
|
||||
import './shim'
|
||||
...
|
||||
```
|
||||
|
||||
### Using xrpl.js with Deno
|
||||
|
||||
Until official support for [Deno](https://deno.land) is added, you can use the following work-around to use `xrpl.js` with Deno:
|
||||
|
||||
```javascript
|
||||
import xrpl from 'https://dev.jspm.io/npm:xrpl';
|
||||
|
||||
(async () => {
|
||||
const api = new (xrpl as any).Client('wss://s.altnet.rippletest.net:51233');
|
||||
const address = 'rH8NxV12EuV...khfJ5uw9kT';
|
||||
|
||||
api.connect().then(() => {
|
||||
api.getBalances(address).then((balances: any) => {
|
||||
console.log(JSON.stringify(balances, null, 2));
|
||||
});
|
||||
});
|
||||
})();
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
+ [Get Started in Node.js](https://xrpl.org/get-started-using-node-js.html)
|
||||
+ [Full Reference Documentation](https://js.xrpl.org)
|
||||
+ [Code Samples](https://github.com/XRPLF/xrpl.js/tree/develop/packages/xrpl/snippets/src)
|
||||
|
||||
### Mailing Lists
|
||||
|
||||
We have a low-traffic mailing list for announcements of new `xrpl.js` releases. (About 1 email per week)
|
||||
|
||||
+ [Subscribe to xrpl-announce](https://groups.google.com/g/xrpl-announce)
|
||||
|
||||
If you're using the XRP Ledger in production, you should run a [rippled server](https://github.com/ripple/rippled) and subscribe to the ripple-server mailing list as well.
|
||||
|
||||
+ [Subscribe to ripple-server](https://groups.google.com/g/ripple-server)
|
||||
|
||||
## More Information
|
||||
|
||||
+ [xrpl-announce mailing list](https://groups.google.com/g/xrpl-announce) - subscribe for release announcements
|
||||
+ [xrpl.js API Reference](https://js.xrpl.org)
|
||||
+ [XRP Ledger Dev Portal](https://xrpl.org)
|
||||
|
||||
30
SECURITY.md
Normal file
30
SECURITY.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
This table shows which versions of xrpl.js are currently supported with security updates:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ---------------------- |
|
||||
| 2.x | :white_check_mark: Yes |
|
||||
| 1.x | :white_check_mark: Yes |
|
||||
| 0.x | :x: No |
|
||||
|
||||
## Responsible disclosure security policy
|
||||
|
||||
The responsible disclosure of vulnerabilities helps to protect users of the project. Vulnerabilities are first triaged in a private manner, and only publicly disclosed after a reasonable time period that allows patching the vulnerability and provides an upgrade path for users.
|
||||
|
||||
When contacting us directly via email, we will do our best to respond in a reasonable time to resolve the issue. Do not disclose the vulnerability until it has been patched and users have been given time to upgrade.
|
||||
|
||||
We kindly ask you to refrain from malicious acts that put our users, the project, or any of the project’s team members at risk.
|
||||
|
||||
## Reporting a security issue
|
||||
|
||||
Security is a top priority. But no matter how much effort we put into security, there can still be vulnerabilities present.
|
||||
|
||||
If you discover a security vulnerability, please use the following means of communications to report it to us:
|
||||
|
||||
- Report the security issue to bugs@ripple.com
|
||||
- [Ripple Bug Bounty](https://ripple.com/bug-bounty/)
|
||||
|
||||
Your efforts to responsibly disclose your findings are sincerely appreciated and will be taken into account to acknowledge your contributions.
|
||||
@@ -1,44 +0,0 @@
|
||||
var Benchmark;
|
||||
try {
|
||||
Benchmark = require('benchmark');
|
||||
} catch (e) {
|
||||
console.error("Please install Benchmark.js: npm install benchmark");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var sjcl = require('../build/sjcl');
|
||||
var jsbn = require('../src/js/jsbn/jsbn');
|
||||
|
||||
var base = "3f70f29d3f3ae354a6d2536ceafba83cfc787cd91e7acd2b6bde05e62beb8295ae18e3f786726f8d034bbc15bf8331df959f59d431736d5f306aaba63dacec279484e39d76db9b527738072af15730e8b9956a64e8e4dbe868f77d1414a8a8b8bf65380a1f008d39c5fabe1a9f8343929342ab7b4f635bdc52532d764701ff3d8072c475c012ff0c59373e8bc423928d99f58c3a6d9f6ab21ee20bc8e8818fc147db09f60c81906f2c6f73dc69725f075853a89f0cd02a30a8dd86b660ccdeffc292f398efb54088c822774445a6afde471f7dd327ef9996296898a5747726ccaeeceeb2e459df98b4128cb5ab8c7cd20c563f960a1aa770f3c81f13f967b6cc";
|
||||
var exponent = "322e393f76a1c22b147e7d193c00c023afb7c1500b006ff1bc1cc8d391fc38bd";
|
||||
var modulus = "c7f1bc1dfb1be82d244aef01228c1409c198894eca9e21430f1669b4aa3864c9f37f3d51b2b4ba1ab9e80f59d267fda1521e88b05117993175e004543c6e3611242f24432ce8efa3b81f0ff660b4f91c5d52f2511a6f38181a7bf9abeef72db056508bbb4eeb5f65f161dd2d5b439655d2ae7081fcc62fdcb281520911d96700c85cdaf12e7d1f15b55ade867240722425198d4ce39019550c4c8a921fc231d3e94297688c2d77cd68ee8fdeda38b7f9a274701fef23b4eaa6c1a9c15b2d77f37634930386fc20ec291be95aed9956801e1c76601b09c413ad915ff03bfdc0b6b233686ae59e8caf11750b509ab4e57ee09202239baee3d6e392d1640185e1cd";
|
||||
var expected = "5b3823974b3eda87286d3f38499de290bd575d8b02f06720acacf3d50950f9ca0ff6b749f3be03913ddca0b291e0b263bdab6c9cb97e4ab47ee9c235ff20931a8ca358726fab93614e2c549594f5c50b1c979b34f840b6d4fc51d6feb2dd072995421d17862cb405e040fc1ed662a3245a1f97bbafa6d1f7f76c7db6a802e3037acdf01ab5053f5da518d6753477193b9c25e1720519dcb9e2f6e70d5786656d356151845a49861dfc40187eff0e85cd18b1f3f3b97c476472edfa090b868b2388edfffecc521c20df8cebb8aacfb3669b020330dd6ea64b2a3067a972b8f249bccc19347eff43893e916f0949bd5789a5cce0f8b7cd87cece909d679345c0d4";
|
||||
|
||||
var BigInteger = jsbn.BigInteger;
|
||||
var jsbnBase = new BigInteger(base, 16);
|
||||
var jsbnExponent = new BigInteger(exponent, 16);
|
||||
var jsbnModulus = new BigInteger(modulus, 16);
|
||||
|
||||
var bn = sjcl.bn;
|
||||
var sjclBase = new bn(base);
|
||||
var sjclExponent = new bn(exponent);
|
||||
var sjclModulus = new bn(modulus);
|
||||
|
||||
var suite = new Benchmark.Suite;
|
||||
|
||||
// add tests
|
||||
suite.add('jsbn#modPow', function() {
|
||||
jsbnBase.modPow(jsbnExponent, jsbnModulus);
|
||||
});
|
||||
suite.add('sjcl#powermodMontgomery', function() {
|
||||
sjclBase.powermodMontgomery(sjclExponent, sjclModulus);
|
||||
});
|
||||
suite.on('cycle', function(event) {
|
||||
console.log(String(event.target));
|
||||
});
|
||||
suite.on('complete', function() {
|
||||
console.log('Fastest is ' + this.filter('fastest').pluck('name'));
|
||||
});
|
||||
// run async
|
||||
console.log("Running benchmark...");
|
||||
suite.run({ 'async': false });
|
||||
@@ -1,47 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var SerializedObject = require('../src/js/ripple/serializedobject').SerializedObject;
|
||||
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var blob;
|
||||
|
||||
blob = argv.shift();
|
||||
|
||||
if (blob === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var valid_arguments = blob;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
decode();
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
'Usage: decode_binary.js <hex_blob>\n\n',
|
||||
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
|
||||
);
|
||||
};
|
||||
|
||||
function decode() {
|
||||
buffer = new SerializedObject(blob);
|
||||
console.log(buffer.to_json());
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
96
bin/rsign.js
96
bin/rsign.js
@@ -1,96 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var Transaction = require('../src/js/ripple/transaction').Transaction;
|
||||
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var verbose;
|
||||
var secret;
|
||||
var tx_json;
|
||||
|
||||
if (~argv.indexOf('-v')){
|
||||
argv.splice(argv.indexOf('-v'), 1);
|
||||
verbose = true;
|
||||
}
|
||||
|
||||
secret = argv.shift();
|
||||
tx_json = argv.shift();
|
||||
|
||||
if (tx_json === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
var valid_arguments = secret && tx_json;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
var valid_json = true;
|
||||
|
||||
try {
|
||||
tx_json = JSON.parse(tx_json);
|
||||
} catch(exception) {
|
||||
valid_json = false;
|
||||
}
|
||||
|
||||
if (!valid_json) {
|
||||
console.error('Invalid JSON\n');
|
||||
print_usage();
|
||||
} else {
|
||||
sign_transaction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
console.log(
|
||||
'Usage: rsign.js <secret> <json>\n\n',
|
||||
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC','\''+
|
||||
JSON.stringify({
|
||||
TransactionType: 'Payment',
|
||||
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
|
||||
Destination: 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV',
|
||||
Amount: '200000000',
|
||||
Fee: '10',
|
||||
Sequence: 1
|
||||
})+'\''
|
||||
);
|
||||
};
|
||||
|
||||
function sign_transaction() {
|
||||
var tx = new Transaction();
|
||||
|
||||
tx.tx_json = tx_json;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
|
||||
var unsigned_blob = tx.serialize().to_hex();
|
||||
var unsigned_hash = tx.signingHash();
|
||||
tx.sign();
|
||||
|
||||
if (verbose) {
|
||||
var sim = { };
|
||||
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = unsigned_hash;
|
||||
sim.tx_unsigned = unsigned_blob;
|
||||
|
||||
console.log(JSON.stringify(sim, null, 2));
|
||||
} else {
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
};
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var UInt160 = require('../').UInt160;
|
||||
var address = process.argv[2];
|
||||
|
||||
if (address === '-') {
|
||||
readInput(validateAddress);
|
||||
} else {
|
||||
validateAddress(address);
|
||||
}
|
||||
|
||||
function readInput(callback) {
|
||||
var result = '';
|
||||
process.stdin.resume();
|
||||
process.stdin.setEncoding('utf8');
|
||||
process.stdin.on('data', function(data) {
|
||||
result += data;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(result);
|
||||
});
|
||||
};
|
||||
|
||||
function validateAddress(address) {
|
||||
process.stdout.write((UInt160.is_valid(address.trim()) ? '0' : '1') + '\r\n');
|
||||
};
|
||||
@@ -1,3 +0,0 @@
|
||||
start newcoin
|
||||
sleep 4
|
||||
start index.html
|
||||
236
docs/GUIDES.md
236
docs/GUIDES.md
@@ -1,236 +0,0 @@
|
||||
#Guides
|
||||
|
||||
This file provides step-by-step walkthroughs for some of the most common usages of `ripple-lib`.
|
||||
|
||||
###In this document
|
||||
|
||||
1. [Connecting to the Ripple network with `Remote`](GUIDES.md#connecting-to-the-ripple-network)
|
||||
2. [Using `Remote` functions and `Request` objects](GUIDES.md#sending-rippled-API-requests)
|
||||
3. [Listening to the network](GUIDES.md#listening-to-the-network)
|
||||
4. [Submitting a payment to the network](GUIDES.md#submitting-a-payment-to-the-network)
|
||||
* [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
5. [Submitting a trade offer to the network](GUIDES.md#submitting-a-trade-offer-to-the-network)
|
||||
|
||||
###Also see
|
||||
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib API Reference](REFERENCE.md)
|
||||
|
||||
##Generating a new Ripple Wallet
|
||||
|
||||
```js
|
||||
var Wallet = require('ripple-lib').Wallet;
|
||||
|
||||
var wallet = Wallet.generate();
|
||||
console.log(wallet);
|
||||
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
|
||||
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
|
||||
```
|
||||
|
||||
##Connecting to the Ripple network
|
||||
|
||||
1. [Get ripple-lib](README.md#getting-ripple-lib)
|
||||
2. Load the ripple-lib module into a Node.js file or webpage:
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
```
|
||||
3. Create a new `Remote` and connect to the network:
|
||||
```js
|
||||
|
||||
var options = {
|
||||
trace : false,
|
||||
trusted: true,
|
||||
local_signing: true,
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true }
|
||||
]
|
||||
}
|
||||
|
||||
var remote = new Remote({options});
|
||||
|
||||
remote.connect(function(err, res) {
|
||||
/* remote connected, use some remote functions here */
|
||||
});
|
||||
```
|
||||
__NOTE:__ See the API Reference for available [`Remote` options](REFERENCE.md#1-remote-options)
|
||||
|
||||
4. You're connected! Read on to see what to do now.
|
||||
|
||||
|
||||
##Sending rippled API requests
|
||||
|
||||
`Remote` contains functions for constructing a `Request` object.
|
||||
|
||||
A `Request` is an `EventEmitter` so you can listen for success or failure events -- or, instead, you can provide a callback.
|
||||
|
||||
Here is an example, using [requestServerInfo](https://ripple.com/wiki/JSON_Messages#server_info).
|
||||
|
||||
+ Constructing a `Request` with event listeners
|
||||
```js
|
||||
var request = remote.requestServerInfo();
|
||||
|
||||
request.on('success', function onSuccess(res) {
|
||||
//handle success
|
||||
});
|
||||
|
||||
request.on('error', function onError(err) {
|
||||
//handle error
|
||||
});
|
||||
|
||||
request.request();
|
||||
```
|
||||
|
||||
+ Using a callback:
|
||||
```js
|
||||
remote.request('server_info', function(err, res) {
|
||||
if (err) {
|
||||
//handle error
|
||||
} else {
|
||||
//handle success
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
__NOTE:__ See the API Reference for available [`Remote` functions](REFERENCE.md#2-remote-functions)
|
||||
|
||||
|
||||
##Listening to the network
|
||||
|
||||
See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on subscription requests.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var remote = new Remote({options});
|
||||
|
||||
remote.connect(function() {
|
||||
var remote = new Remote({
|
||||
// see the API Reference for available options
|
||||
servers: [ 'wss://s1.ripple.com:443' ]
|
||||
});
|
||||
|
||||
remote.connect(function() {
|
||||
console.log('Remote connected');
|
||||
|
||||
var streams = [
|
||||
'ledger',
|
||||
'transactions'
|
||||
];
|
||||
|
||||
var request = remote.requestSubscribe(streams);
|
||||
|
||||
request.on('error', function(error) {
|
||||
console.log('request error: ', error);
|
||||
});
|
||||
|
||||
|
||||
// the `ledger_closed` and `transaction` will come in on the remote
|
||||
// since the request for subscribe is finalized after the success return
|
||||
// the streaming events will still come in, but not on the initial request
|
||||
remote.on('ledger_closed', function(ledger) {
|
||||
console.log('ledger_closed: ', JSON.stringify(ledger, null, 2));
|
||||
});
|
||||
|
||||
remote.on('transaction', function(transaction) {
|
||||
console.log('transaction: ', JSON.stringify(transaction, null, 2));
|
||||
});
|
||||
|
||||
remote.on('error', function(error) {
|
||||
console.log('remote error: ', error);
|
||||
});
|
||||
|
||||
// fire the request
|
||||
request.request();
|
||||
});
|
||||
});
|
||||
```
|
||||
* https://ripple.com/wiki/RPC_API#transactions_stream_messages
|
||||
* https://ripple.com/wiki/RPC_API#ledger_stream_messages
|
||||
|
||||
##Submitting a payment to the network
|
||||
|
||||
Submitting a payment transaction to the Ripple network involves connecting to a `Remote`, creating a transaction, signing it with the user's secret, and submitting it to the `rippled` server. Note that the `Amount` module is used to convert human-readable amounts like '1XRP' or '10.50USD' to the type of Amount object used by the Ripple network.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
/* Loading ripple-lib Remote and Amount modules in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
// var Amount = ripple.Amount;
|
||||
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
var RECIPIENT = 'rrrRecipient';
|
||||
var AMOUNT = Amount.from_human('1XRP');
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: RECIPIENT,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
###A note on transaction fees
|
||||
|
||||
A full description of network transaction fees can be found on the [Ripple Wiki](https://ripple.com/wiki/Transaction_Fee).
|
||||
|
||||
In short, transaction fees are very small amounts (on the order of ~10) of [XRP drops](https://ripple.com/wiki/Ripple_credits#Notes_on_drops) spent and destroyed with every transaction. They are largely used to account for network load and prevent spam. With `ripple-lib`, transaction fees are calculated locally by default and the fee you are willing to pay is submitted along with your transaction.
|
||||
|
||||
Since the fee required for a transaction may change between the time when the original fee was calculated and the time when the transaction is submitted, it is wise to use the [`fee_cushion`](REFERENCE.md#1-remote-options) to ensure that the transaction will go through. For example, suppose the original fee calculated for a transaction was 10 XRP drops but at the instant the transaction is submitted the server is experiencing a higher load and it has raised its minimum fee to 12 XRP drops. Without a `fee_cusion`, this transaction would not be processed by the server, but with a `fee_cusion` of, say, 1.5 it would be processed and you would just pay the 2 extra XRP drops.
|
||||
|
||||
The [`max_fee`](REFERENCE.md#1-remote-options) option can be used to avoid submitting a transaction to a server that is charging unreasonably high fees.
|
||||
|
||||
|
||||
##4. Submitting a trade offer to the network
|
||||
|
||||
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example for a trade that expires in 24 hours where you are offering to sell 1 USD in exchange for 100 XRP:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
/* Loading ripple-lib Remote and Amount modules in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
// var Amount = ripple.Amount;
|
||||
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
var GATEWAY = 'rrrGateWay';
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_gets: '1/USD/' + GATEWAY
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
/* handle submission errors / success */
|
||||
});
|
||||
});
|
||||
```
|
||||
@@ -1,354 +0,0 @@
|
||||
#API Reference
|
||||
|
||||
__(More examples coming soon!)__
|
||||
|
||||
###In this document:
|
||||
|
||||
1. [`Remote` options](REFERENCE.md#remote-options)
|
||||
2. [`Request` constructors](REFERENCE.md#request-constructor-functions)
|
||||
+ [Server requests](REFERENCE.md#server-requests)
|
||||
+ [Ledger requests](REFERENCE.md#ledger-requests)
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
+ [Account requests](REFERENCE.md#account-requests)
|
||||
+ [Orderbook requests](REFERENCE.md#orderbook-requests)
|
||||
+ [Transaction requests](REFERENCE.md#transaction-requests)
|
||||
3. [`Transaction` constructors](REFERENCE.md#transaction-constructors)
|
||||
+ [Transaction events](REFERENCE.md#transaction-events)
|
||||
|
||||
###Also see:
|
||||
|
||||
1. [The ripple-lib README](../README.md)
|
||||
2. [The ripple-lib GUIDES](GUIDES.md)a
|
||||
|
||||
#Remote options
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
|
||||
/* Loading ripple-lib in a webpage */
|
||||
// var Remote = ripple.Remote;
|
||||
|
||||
var options = { };
|
||||
|
||||
var remote = new Remote(options);
|
||||
```
|
||||
|
||||
A new `Remote` can be created with the following options:
|
||||
|
||||
+ `trace` *boolean default: false* Log all of the events emitted
|
||||
+ `max_listeners` *number default: 0* Set maxListeners for servers
|
||||
+ `trusted` *boolean default: false*, if remote is trusted (boolean)
|
||||
+ `local_signing` *boolean default: true*
|
||||
+ `local_fee` *boolean default: true* Set whether the transaction fee range will be set locally, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `fee_cushion` *number default: 1.2* Extra fee multiplier to account for async fee changes, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees))
|
||||
+ `max_fee` *number default: Infinity* Maximum acceptable transaction fee, see [A note on transaction fees](GUIDES.md#a-note-on-transaction-fees)
|
||||
+ `servers` *array* Array of server objects of the following form:
|
||||
|
||||
```js
|
||||
{
|
||||
host: <string>,
|
||||
port: <number>,
|
||||
secure: <boolean>
|
||||
}
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```js
|
||||
'wss://host:port'
|
||||
```
|
||||
|
||||
#Request constructor functions
|
||||
|
||||
Some requests have helper methods to construct the requests object and set properties on the message object. These will often be the more used requests and the helper methods is the preferred way of constructing these requests.
|
||||
Other request can still be made, but the type will have to be passed in directly to request constructor. See examples below.
|
||||
|
||||
If the method is camelCased and starts with `request`, it's a helper method that wraps the request constructor.
|
||||
|
||||
##Server requests
|
||||
|
||||
**[requestServerInfo([callback])](https://ripple.com/wiki/JSON_Messages#server_info)**
|
||||
|
||||
Returns information about the state of the server. If you are connected to multiple servers and want to select by a particular host, use `request.setServer`. Example:
|
||||
|
||||
```js
|
||||
var request = remote.requestServerInfo();
|
||||
|
||||
request.setServer('wss://s1.ripple.com');
|
||||
|
||||
request.request(function(err, res) {
|
||||
|
||||
});
|
||||
```
|
||||
**[requestPeers([callback])](https://ripple.com/wiki/JSON_Messages#peers)**
|
||||
|
||||
**[requestConnect(ip, port, [callback])](https://ripple.com/wiki/JSON_Messages#connect)**
|
||||
|
||||
**[unl_list([callback])](https://ripple.com/wiki/JSON_Messages#unl_list)**
|
||||
|
||||
```js
|
||||
var request = remote.request('un_list');
|
||||
|
||||
request.setServer('wss://s1.ripple.com');
|
||||
|
||||
request.request(function(err, res) {
|
||||
|
||||
});
|
||||
```
|
||||
|
||||
**[unl_add(addr, comment, [callback])](https://ripple.com/wiki/JSON_Messages#unl_add)**
|
||||
|
||||
**[unl_delete(node, [callback])](https://ripple.com/wiki/JSON_Messages#unl_delete)**
|
||||
|
||||
|
||||
|
||||
##Ledger requests
|
||||
|
||||
**[requestLedger([opts], [callback])](https://ripple.com/wiki/JSON_Messages#ledger)**
|
||||
|
||||
**[requestLedgerHeader([callback])](https://wiki.ripple.com/JSON_Messages#ledger_data)**
|
||||
|
||||
**[requestLedgerCurrent([callback])](https://ripple.com/wiki/JSON_Messages#ledger_current)**
|
||||
|
||||
**[requestLedgerEntry(type, [callback])](https://ripple.com/wiki/JSON_Messages#ledger_entry)**
|
||||
|
||||
**[requestSubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#subscribe)**
|
||||
|
||||
Start receiving selected streams from the server.
|
||||
|
||||
**[requestUnsubscribe([streams], [callback])](https://ripple.com/wiki/JSON_Messages#unsubscribe)**
|
||||
|
||||
Stop receiving selected streams from the server.
|
||||
|
||||
##Account requests
|
||||
|
||||
**[requestAccountInfo(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_info)**
|
||||
|
||||
Return information about the specified account.
|
||||
|
||||
```
|
||||
var options = {
|
||||
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
ledger: 'validated'
|
||||
};
|
||||
|
||||
var request = remote.requestAccountInfo(options, function(err, info) {
|
||||
/* process info */
|
||||
});
|
||||
|
||||
|
||||
// response
|
||||
{
|
||||
ledger_current_index: <number>,
|
||||
account_data: {
|
||||
Account: <string>,
|
||||
Balance: <number>,
|
||||
Flags: <number>,
|
||||
LedgerEntryType: <string>,
|
||||
OwnerCount: <number>,
|
||||
PreviousTxnID: <string>,
|
||||
PreviousTxnLgrSeq: <number>,
|
||||
Sequence: <number> ,
|
||||
index: <string>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**[requestAccountLines(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_lines)**
|
||||
|
||||
**[requestAccountOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_offers)**
|
||||
|
||||
Return the specified account's outstanding offers.
|
||||
|
||||
Requests for both `account_lines` and `account_offers` support paging. The amount of results per response can be configured with the `limit`.
|
||||
The responses can be paged through by using the `marker`.
|
||||
|
||||
```
|
||||
// A valid `ledger_index` or `ledger_hash` is required to provide a reliable result.
|
||||
// Results can change between ledger closes, so the provided ledger will be used as base.
|
||||
var options = {
|
||||
account: < rippleAccount >,
|
||||
limit: < Number between 10 and 400 >,
|
||||
ledger: < valid ledger_index or ledger_hash >
|
||||
}
|
||||
|
||||
// The `marker` comes back in an account request if there are more results than are returned
|
||||
// in the current response. The amount of results per response are determined by the `limit`.
|
||||
if (marker) {
|
||||
options.marker = < marker >;
|
||||
}
|
||||
|
||||
var request = remote.requestAccountOffers(options);
|
||||
```
|
||||
|
||||
|
||||
**[requestAccountTransactions(options, [callback])](https://ripple.com/wiki/JSON_Messages#account_tx)**
|
||||
|
||||
Fetch a list of transactions that applied to this account.
|
||||
|
||||
Options:
|
||||
|
||||
+ `account`
|
||||
+ `ledger_index_min`
|
||||
+ `ledger_index_max`
|
||||
+ `binary` *false*
|
||||
+ `count` *false*
|
||||
+ `descending` *false*
|
||||
+ `offset` *0*
|
||||
+ `limit`
|
||||
+ `forward` *false*
|
||||
+ `fwd_marker`
|
||||
+ `rev_marker`
|
||||
|
||||
**[requestWalletAccounts(seed, [callback])](https://ripple.com/wiki/JSON_Messages#wallet_accounts)**
|
||||
|
||||
Return a list of accounts for a wallet. *Requires trusted remote*
|
||||
|
||||
**requestAccountBalance(account, [ledger], [callback])**
|
||||
|
||||
Get the balance for an account. Returns an [Amount](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/amount.js) object.
|
||||
|
||||
**requestAccountFlags(account, [ledger], [callback])**
|
||||
|
||||
Return the flags for an account.
|
||||
|
||||
**requestOwnerCount(account, [ledger], [callback])**
|
||||
|
||||
Return the owner count for an account.
|
||||
|
||||
**requestRippleBalance(account, issuer, currency, [ledger], [callback])**
|
||||
|
||||
Return a request to get a ripple balance
|
||||
|
||||
##Orderbook requests
|
||||
|
||||
**[requestBookOffers(options, [callback])](https://ripple.com/wiki/JSON_Messages#book_offers)**
|
||||
|
||||
Return the offers for an order book, also called a *snapshot*
|
||||
|
||||
```js
|
||||
var options = {
|
||||
gets: {
|
||||
issuer: < issuer >,
|
||||
currency: < currency >
|
||||
},
|
||||
pays: {
|
||||
issuer: < issuer >,
|
||||
currency: < currency >
|
||||
},
|
||||
limit: < limit >
|
||||
};
|
||||
|
||||
var request = remote.requestBookOffers(options);
|
||||
|
||||
request.request(function(err, offers) {
|
||||
//handle offers
|
||||
});
|
||||
```
|
||||
|
||||
##Transaction requests
|
||||
|
||||
**[requestTransactionEntry(hash, [ledger_hash], [callback])](https://ripple.com/wiki/JSON_Messages#transaction_entry)**
|
||||
|
||||
Searches a particular ledger for a transaction hash. Default ledger is the open ledger.
|
||||
|
||||
**[requestTransaction(hash, [callback])](https://ripple.com/wiki/JSON_Messages#tx)**
|
||||
|
||||
Searches ledger history for validated transaction hashes.
|
||||
|
||||
**[requestSign(secret, tx_json, [callback])](https://ripple.com/wiki/JSON_Messages#sign)**
|
||||
|
||||
Sign a transaction. *Requires trusted remote*
|
||||
|
||||
**[requestSubmit([callback])](https://ripple.com/wiki/JSON_Messages#submit)**
|
||||
|
||||
Submit a transaction to the network. This command is used internally to submit transactions with a greater degree of reliability. See [Submitting a payment to the network](GUIDES.md#3-submitting-a-payment-to-the-network) for details.
|
||||
|
||||
**[pathFind(src_account, dst_account, dst_amount, src_currencies)](https://ripple.com/wiki/JSON_Messages#path_find)**
|
||||
|
||||
#Transaction constructors
|
||||
|
||||
Use `remote.createTransaction('TransactionType', [options])` to construct a transaction. To submit, use `transaction.submit([callback])`.
|
||||
|
||||
**Payment**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: DEST_ADDRESS,
|
||||
amount: AMOUNT
|
||||
});
|
||||
```
|
||||
|
||||
**AccountSet**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('AccountSet', {
|
||||
account: MY_ADDRESS,
|
||||
set: 'RequireDest',
|
||||
clear: 'RequireAuth'
|
||||
});
|
||||
```
|
||||
|
||||
**TrustSet**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('TrustSet', {
|
||||
account: MY_ADDRESS,
|
||||
limit: '1/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
```
|
||||
|
||||
**OfferCreate**
|
||||
|
||||
```js
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_gets: '1/USD/rrrrrrrrrrrrrrrrrrrrBZbvji'
|
||||
});
|
||||
```
|
||||
|
||||
##Transaction events
|
||||
|
||||
[Transaction](https://github.com/ripple/ripple-lib/blob/develop/src/js/ripple/transaction.js) objects are EventEmitters. They may emit the following events.
|
||||
|
||||
+ `final` Transaction has erred or succeeded. This event indicates that the transaction has finished processing.
|
||||
+ `error` Transaction has erred. This event is a final state.
|
||||
+ `success` Transaction succeeded. This event is a final state.
|
||||
+ `presubmit` Immediately before transaction is submitted
|
||||
+ `postsubmit` Immediately after transaction is submitted
|
||||
+ `submitted` Transaction has been submitted to the network. The submission may result in a remote error or success.
|
||||
+ `resubmitted` Transaction is beginning resubmission.
|
||||
+ `proposed` Transaction has been submitted *successfully* to the network. The transaction at this point is awaiting validation in a ledger.
|
||||
+ `timeout` Transaction submission timed out. The transaction will be resubmitted.
|
||||
+ `fee_adjusted` Transaction fee has been adjusted during its pending state. The transaction fee will only be adjusted if the remote is configured for local fees, which it is by default.
|
||||
+ `abort` Transaction has been aborted. Transactions are only aborted by manual calls to `#abort`.
|
||||
+ `missing` Four ledgers have closed without detecting validated transaction
|
||||
+ `lost` Eight ledgers have closed without detecting validated transaction. Consider the transaction lost and err/finalize.
|
||||
|
||||
##Complete payment example
|
||||
|
||||
```js
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: DEST_ADDRESS,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
transaction.on('resubmitted', function() {
|
||||
// initial submission failed, resubmitting
|
||||
});
|
||||
|
||||
transaction.submit(function(err, res) {
|
||||
// submission has finalized with either an error or success.
|
||||
// the transaction will not be retried after this point
|
||||
});
|
||||
```
|
||||
|
||||
#Amount objects
|
||||
|
||||
Coming Soon
|
||||
@@ -1,168 +0,0 @@
|
||||
ripple-vault-client
|
||||
===================
|
||||
|
||||
A javascript / http client to interact with Ripple Vault servers.
|
||||
|
||||
The purpose of this tool is to enable applications in any javascript
|
||||
environment to login with the ripple vault and access the decrypted
|
||||
data stored using credentials originally obtained at ripple.com
|
||||
|
||||
|
||||
## Vault Client Usage
|
||||
|
||||
vaultClient = new ripple.VaultClient(domain);
|
||||
|
||||
vaultClient.getAuthInfo(username, callback);
|
||||
|
||||
vaultClient.getRippleName(address, url, callback);
|
||||
|
||||
vaultClient.exists(username, callback);
|
||||
|
||||
|
||||
|
||||
vaultClient.login(username, password, callback);
|
||||
|
||||
vaultClient.relogin(id, cryptKey, callback);
|
||||
|
||||
vaultClient.unlock(username, password, encryptSecret, callback);
|
||||
|
||||
vaultClient.loginAndUnlock(username, password, callback);
|
||||
|
||||
|
||||
|
||||
vaultClient.register(options, callback);
|
||||
|
||||
vaultClient.deleteBlob(options, callback);
|
||||
|
||||
vaultClient.recoverBlob(options, callback);
|
||||
|
||||
vaultClient.rename(options, callback);
|
||||
|
||||
vaultClient.changePassword(options, callback);
|
||||
|
||||
vaultClient.verify(username, token, callback);
|
||||
|
||||
vaultClient.resendEmail(options, callback);
|
||||
|
||||
vaultClient.updateProfile(options, fn);
|
||||
|
||||
|
||||
# Blob Methods
|
||||
|
||||
blob.encrypt();
|
||||
|
||||
blob.decrypt(encryptedBlob);
|
||||
|
||||
blob.encryptSecret(encryptionKey);
|
||||
|
||||
blob.decryptSecret(encryptionKey, secret);
|
||||
|
||||
blob.set(pointer, value, callback);
|
||||
|
||||
blob.unset(pointer, callback);
|
||||
|
||||
blob.extend(pointer, value, callback);
|
||||
|
||||
blob.unshift(pointer, value, callback);
|
||||
|
||||
blob.filter(pointer, field, value, subcommands, callback);
|
||||
|
||||
|
||||
## Identity Vault
|
||||
|
||||
The identity vault stores identity information inside the encrypted
|
||||
blob vault. The identity fields can be additionally encrypted with the
|
||||
unlock key, that encrypts the secret, for added security. Methods are
|
||||
accessed from the 'identity' property of the blob object.
|
||||
|
||||
|
||||
# Identity fields
|
||||
+ name
|
||||
+ entityType (individual, corporation, organization)
|
||||
+ email
|
||||
+ phone
|
||||
+ address
|
||||
+ contact
|
||||
+ line1
|
||||
+ line2
|
||||
+ city
|
||||
+ postalCode
|
||||
+ region - state/province/region
|
||||
+ country
|
||||
+ nationalID
|
||||
+ number
|
||||
+ type (ssn, taxID, passport, driversLicense, other)
|
||||
+ country - issuing country
|
||||
+ birthday
|
||||
+ birthplace
|
||||
|
||||
|
||||
# Identity Methods
|
||||
|
||||
blob.identity.set(pointer, key, value, callback);
|
||||
|
||||
blob.identity.unset(pointer, key, callback);
|
||||
|
||||
blob.identity.get(pointer, key);
|
||||
|
||||
blob.identity.getAll(key);
|
||||
|
||||
blob.identity.getFullAddress(key); //get text string of full address
|
||||
|
||||
|
||||
## Spec Tests
|
||||
|
||||
Run `npm test` to test the high-level behavior specs
|
||||
|
||||
Ripple Txt
|
||||
✓ should get the content of a ripple.txt file from a given domain
|
||||
✓ should get currencies from a ripple.txt file for a given domain
|
||||
✓ should get the domain from a given url
|
||||
|
||||
AuthInfo
|
||||
✓ should get auth info
|
||||
|
||||
VaultClient
|
||||
#initialization
|
||||
✓ should be initialized with a domain
|
||||
✓ should default to ripple.com without a domain
|
||||
#exists
|
||||
✓ should determine if a username exists on the domain
|
||||
#login
|
||||
✓ with username and password should retrive the blob, crypt key, and id
|
||||
#relogin
|
||||
✓ should retrieve the decrypted blob with blob vault url, id, and crypt key
|
||||
#unlock
|
||||
✓ should access the wallet secret using encryption secret, username and password
|
||||
#loginAndUnlock
|
||||
✓ should get the decrypted blob and decrypted secret given name and password
|
||||
#register
|
||||
✓ should create a new blob
|
||||
#deleteBlob
|
||||
✓ should remove an existing blob
|
||||
#updateProfile
|
||||
✓ should update profile parameters associated with a blob
|
||||
|
||||
Blob
|
||||
✓ #set
|
||||
✓ #extend
|
||||
✓ #unset
|
||||
✓ #unshift
|
||||
✓ #filter
|
||||
✓ #consolidate
|
||||
#rename
|
||||
✓ should change the username of a blob
|
||||
#changePassword
|
||||
✓ should change the password and keys of a blob
|
||||
#recoverBlob
|
||||
✓ should recover the blob given a username and secret
|
||||
#verifyEmail
|
||||
✓ should verify an email given a username and token
|
||||
#resendVerifcationEmail
|
||||
✓ should resend a verification given options
|
||||
identity
|
||||
✓ #identity_set
|
||||
✓ #identity_get
|
||||
✓ #identity_getAll
|
||||
✓ #identity_getFullAddress
|
||||
✓ #identity_unset
|
||||
5
lerna.json
Normal file
5
lerna.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"version": "independent",
|
||||
"useWorkspaces": true,
|
||||
"npmClient": "npm"
|
||||
}
|
||||
142
npm-shrinkwrap.json
generated
142
npm-shrinkwrap.json
generated
@@ -1,142 +0,0 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.0-rc5",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.8.0",
|
||||
"from": "async@>=0.8.0 <0.9.0"
|
||||
},
|
||||
"extend": {
|
||||
"version": "1.2.1",
|
||||
"from": "extend@>=1.2.1 <1.3.0"
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.5.0",
|
||||
"from": "lru-cache@>=2.5.0 <2.6.0"
|
||||
},
|
||||
"ripple-wallet-generator": {
|
||||
"version": "1.0.1",
|
||||
"from": "ripple-wallet-generator@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.1.tgz"
|
||||
},
|
||||
"superagent": {
|
||||
"version": "0.18.2",
|
||||
"from": "superagent@>=0.18.0 <0.19.0",
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "0.6.6",
|
||||
"from": "qs@0.6.6",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz"
|
||||
},
|
||||
"formidable": {
|
||||
"version": "1.0.14",
|
||||
"from": "formidable@1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.11",
|
||||
"from": "mime@1.2.11",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz"
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.1.2",
|
||||
"from": "component-emitter@1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.1.2.tgz"
|
||||
},
|
||||
"methods": {
|
||||
"version": "1.0.1",
|
||||
"from": "methods@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.0.1.tgz"
|
||||
},
|
||||
"cookiejar": {
|
||||
"version": "2.0.1",
|
||||
"from": "cookiejar@2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.0.1.tgz"
|
||||
},
|
||||
"debug": {
|
||||
"version": "1.0.4",
|
||||
"from": "debug@>=1.0.1 <1.1.0",
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "0.6.2",
|
||||
"from": "ms@0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"reduce-component": {
|
||||
"version": "1.0.1",
|
||||
"from": "reduce-component@1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/reduce-component/-/reduce-component-1.0.1.tgz"
|
||||
},
|
||||
"form-data": {
|
||||
"version": "0.1.3",
|
||||
"from": "form-data@0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.3.tgz",
|
||||
"dependencies": {
|
||||
"combined-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "combined-stream@>=0.0.4 <0.1.0",
|
||||
"dependencies": {
|
||||
"delayed-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "delayed-stream@0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"async": {
|
||||
"version": "0.9.0",
|
||||
"from": "async@>=0.9.0 <0.10.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "1.0.27-1",
|
||||
"from": "readable-stream@1.0.27-1",
|
||||
"dependencies": {
|
||||
"core-util-is": {
|
||||
"version": "1.0.1",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "0.0.1",
|
||||
"from": "isarray@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.1",
|
||||
"from": "inherits@>=2.0.1 <2.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "0.4.32",
|
||||
"from": "ws@>=0.4.31 <0.5.0",
|
||||
"dependencies": {
|
||||
"commander": {
|
||||
"version": "2.1.0",
|
||||
"from": "commander@>=2.1.0 <2.2.0"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.0.0",
|
||||
"from": "nan@>=1.0.0 <1.1.0"
|
||||
},
|
||||
"tinycolor": {
|
||||
"version": "0.0.1",
|
||||
"from": "tinycolor@>=0.0.0 <1.0.0"
|
||||
},
|
||||
"options": {
|
||||
"version": "0.0.6",
|
||||
"from": "options@>=0.0.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32277
package-lock.json
generated
Normal file
32277
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
112
package.json
112
package.json
@@ -1,54 +1,76 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.2-rc6",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
"src/js/*",
|
||||
"bin/*",
|
||||
"build/*",
|
||||
"test/*",
|
||||
"Makefile",
|
||||
"Gulpfile.js"
|
||||
],
|
||||
"main": "src/js/ripple",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
"name": "xrpl.js",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "lerna run test --stream",
|
||||
"test:browser": "lerna run test:browser --stream",
|
||||
"test:integration": "lerna run test:integration --stream",
|
||||
"lint": "lerna run lint --stream",
|
||||
"clean": "lerna run clean --stream",
|
||||
"build": "lerna run build --stream",
|
||||
"docgen": "lerna run docgen --stream"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.8.0",
|
||||
"ws": "~0.4.31",
|
||||
"extend": "~1.2.1",
|
||||
"lru-cache": "~2.5.0",
|
||||
"superagent": "^0.18.0",
|
||||
"ripple-wallet-generator": "1.0.1"
|
||||
"ripple-address-codec": "file:packages/ripple-address-codec",
|
||||
"ripple-binary-codec": "file:packages/ripple-binary-codec",
|
||||
"ripple-keypairs": "file:packages/ripple-keypairs",
|
||||
"xrpl": "file:packages/xrpl"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "~1.14.0",
|
||||
"gulp": "~3.6.2",
|
||||
"gulp-concat": "~2.2.0",
|
||||
"gulp-jshint": "~1.5.5",
|
||||
"gulp-uglify": "~0.3.0",
|
||||
"gulp-rename": "~1.2.0",
|
||||
"gulp-bump": "~0.1.10",
|
||||
"webpack": "~1.1.11",
|
||||
"map-stream": "~0.1.0",
|
||||
"istanbul": "~0.2.10",
|
||||
"coveralls": "~2.10.0",
|
||||
"nock": "^0.34.1",
|
||||
"yargs": "~1.3.1"
|
||||
"@types/chai": "^4.2.21",
|
||||
"@types/lodash": "^4.14.136",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/node": "^16.4.3",
|
||||
"@types/puppeteer": "5.4.4",
|
||||
"@types/ws": "^8.2.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.30.0",
|
||||
"@typescript-eslint/parser": "^4.0.0",
|
||||
"@xrplf/eslint-config": "^1.6.0",
|
||||
"@xrplf/prettier-config": "^1.5.0",
|
||||
"assert": "^2.0.0",
|
||||
"buffer": "^6.0.2",
|
||||
"chai": "^4.3.4",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"ejs": "^3.0.1",
|
||||
"eslint": "^7.5.0",
|
||||
"eslint-plugin-array-func": "^3.1.7",
|
||||
"eslint-plugin-consistent-default-export-name": "^0.0.13",
|
||||
"eslint-plugin-eslint-comments": "^3.2.0",
|
||||
"eslint-plugin-import": "^2.24.1",
|
||||
"eslint-plugin-jsdoc": "^36.0.0",
|
||||
"eslint-plugin-mocha": "^9.0.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.14",
|
||||
"eventemitter2": "^6.0.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"jest": "^26.0.1",
|
||||
"lerna": "^4.0.0",
|
||||
"mocha": "^9",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"nyc": "^15",
|
||||
"path-browserify": "1.0.1",
|
||||
"prettier": "^2.3.2",
|
||||
"process": "^0.11.10",
|
||||
"puppeteer": "10.4.0",
|
||||
"source-map-support": "^0.5.16",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-http": "3.2.0",
|
||||
"ts-jest": "^26.4.4",
|
||||
"ts-loader": "^9.2.5",
|
||||
"ts-node": "^10.2.1",
|
||||
"typedoc": "^0.22.5",
|
||||
"typescript": "^4.4.2",
|
||||
"url": "^0.11.0",
|
||||
"webpack": "^5.6.0",
|
||||
"webpack-bundle-analyzer": "^4.1.0",
|
||||
"webpack-cli": "^4.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node_modules/.bin/gulp",
|
||||
"pretest": "node_modules/.bin/gulp concat-sjcl",
|
||||
"test": "./node_modules/.bin/istanbul test -x build/sjcl.js -x src/js/jsbn/* ./node_modules/mocha/bin/_mocha -- --reporter spec test/*-test.js",
|
||||
"coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/ripple/ripple-lib.git"
|
||||
},
|
||||
"readmeFilename": "README.md",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=7.0.0 < 8.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
12
packages/ripple-address-codec/.eslintignore
Normal file
12
packages/ripple-address-codec/.eslintignore
Normal file
@@ -0,0 +1,12 @@
|
||||
# Don't ever lint node_modules
|
||||
node_modules
|
||||
|
||||
# Don't lint build output
|
||||
dist
|
||||
|
||||
# don't lint nyc coverage output
|
||||
coverage
|
||||
.nyc_output
|
||||
|
||||
# Don't lint NYC configuration
|
||||
nyc.config.js
|
||||
85
packages/ripple-address-codec/.eslintrc.js
Normal file
85
packages/ripple-address-codec/.eslintrc.js
Normal file
@@ -0,0 +1,85 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
parser: '@typescript-eslint/parser', // Make ESLint compatible with TypeScript
|
||||
parserOptions: {
|
||||
// Enable linting rules with type information from our tsconfig
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.json'],
|
||||
|
||||
sourceType: 'module', // Allow the use of imports / ES modules
|
||||
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true, // Enable global strict mode
|
||||
},
|
||||
},
|
||||
|
||||
// Specify global variables that are predefined
|
||||
env: {
|
||||
browser: true, // Enable browser global variables
|
||||
node: true, // Enable node global variables & Node.js scoping
|
||||
es2020: true, // Add all ECMAScript 2020 globals and automatically set the ecmaVersion parser option to ES2020
|
||||
},
|
||||
|
||||
plugins: [],
|
||||
extends: ['@xrplf/eslint-config/base'],
|
||||
|
||||
rules: {
|
||||
// ** TODO **
|
||||
// all of the below are turned off for now during the migration to a
|
||||
// monorepo. They need to actually be addressed!
|
||||
// **
|
||||
'@typescript-eslint/no-for-in-array': 'off',
|
||||
'@typescript-eslint/consistent-type-assertions': 'off',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
'@typescript-eslint/prefer-for-of': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-magic-numbers': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/explicit-member-accessibility': 'off',
|
||||
'@typescript-eslint/promise-function-async': 'off',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/array-type': 'off',
|
||||
'@typescript-eslint/restrict-plus-operands': 'off',
|
||||
'@typescript-eslint/no-unnecessary-type-assertion': 'off',
|
||||
'jsdoc/require-returns': 'off',
|
||||
'jsdoc/check-param-names': 'off',
|
||||
'jsdoc/require-throws': 'off',
|
||||
'jsdoc/require-hyphen-before-param-description': 'off',
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
'jsdoc/require-description-complete-sentence': 'off',
|
||||
'jsdoc/require-param': 'off',
|
||||
'jsdoc/no-types': 'off',
|
||||
'tsdoc/syntax': 'off',
|
||||
'import/no-commonjs': 'off',
|
||||
'import/order': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
'guard-for-in': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'no-negated-condition': 'off',
|
||||
'no-loop-func': 'off',
|
||||
'id-length': 'off',
|
||||
'no-inline-comments': 'off',
|
||||
'max-lines-per-function': 'off',
|
||||
'max-len': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'no-bitwise': 'off',
|
||||
'multiline-comment-style': 'off',
|
||||
'id-blacklist': 'off',
|
||||
'func-names': 'off',
|
||||
'max-params': 'off',
|
||||
'prefer-template': 'off',
|
||||
'no-else-return': 'off',
|
||||
},
|
||||
}
|
||||
61
packages/ripple-address-codec/.gitignore
vendored
Normal file
61
packages/ripple-address-codec/.gitignore
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# .gitignore
|
||||
|
||||
# Ignore vim swap files.
|
||||
*.swp
|
||||
|
||||
# Ignore SCons support files.
|
||||
.sconsign.dblite
|
||||
|
||||
# Ignore python compiled files.
|
||||
*.pyc
|
||||
|
||||
# Ignore Macintosh Desktop Services Store files.
|
||||
.DS_Store
|
||||
|
||||
# Ignore backup/temps
|
||||
*~
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build/
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
Release/*.*
|
||||
|
||||
# Ignore locally installed node_modules
|
||||
node_modules
|
||||
!test/node_modules
|
||||
|
||||
# Ignore tmp directory.
|
||||
tmp
|
||||
|
||||
# Ignore database directory.
|
||||
db/*.db
|
||||
db/*.db-*
|
||||
|
||||
# Ignore customized configs
|
||||
rippled.cfg
|
||||
validators.txt
|
||||
test/config.js
|
||||
|
||||
# Ignore coverage files
|
||||
/lib-cov
|
||||
/src-cov
|
||||
/coverage.html
|
||||
/coverage
|
||||
|
||||
# Ignore IntelliJ files
|
||||
.idea
|
||||
|
||||
# Ignore npm-debug
|
||||
npm-debug.log
|
||||
|
||||
# Ignore dist folder
|
||||
dist/
|
||||
|
||||
# Ignore flow output directory
|
||||
out/
|
||||
|
||||
# Ignore perf test cache
|
||||
scripts/cache
|
||||
64
packages/ripple-address-codec/HISTORY.md
Normal file
64
packages/ripple-address-codec/HISTORY.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# ripple-address-cod
|
||||
|
||||
## 4.1.3 (2021-05-10)
|
||||
|
||||
* Update dependencies
|
||||
* Add `build` script as an alias for `compile`
|
||||
* Update README
|
||||
|
||||
## 4.1.2 (2021-01-11)
|
||||
|
||||
* Internal dependencies
|
||||
* Update jest, ts-jest, typescript, lodash
|
||||
* Fix potential moderate severity vulnerabilities
|
||||
* Update @types/node, @types/jest, base-x
|
||||
* Docs
|
||||
* Update example for encoding test address
|
||||
* Document functions (#73)
|
||||
* xAddressToClassicAddress when there is no tag (#114)
|
||||
* Add README badges (#120)
|
||||
* Add LICENSE (#138)
|
||||
* Cleanup and polish
|
||||
* Add GitHub CI (#115)
|
||||
* Fix linting
|
||||
|
||||
## 4.1.1 (2020-04-03)
|
||||
|
||||
* Require node v10+
|
||||
* CI: Drop node 6 & 8 and add node 13
|
||||
* Update dependencies
|
||||
* Bump @types/node to 13.7.7 (#60)
|
||||
* Bump jest and ts-jest (#40)
|
||||
* Bump @types/jest to 25.1.2 (#51)
|
||||
* Bump ts-jest from 25.0.0 to 25.2.0 (#50)
|
||||
* Bump typescript from 3.7.5 to 3.8.3 (#61)
|
||||
* Update all dependencies in yarn.lock
|
||||
|
||||
## 4.1.0 (2020-01-22)
|
||||
|
||||
* Throwable 'unexpected_payload_length' error: The message has been expanded with ' Ensure that the bytes are a Buffer.'
|
||||
* Docs (readme): Correct X-address to classic address example (#15) (thanks @RareData)
|
||||
|
||||
### New Features
|
||||
|
||||
* `encodeAccountPublic` - Encode a public key, as for payment channels
|
||||
* `decodeAccountPublic` - Decode a public key, as for payment channels
|
||||
|
||||
* Internal
|
||||
* Update dependencies: ts-jest, @types/jest, @types/node, typescript, tslint,
|
||||
base-x
|
||||
|
||||
## 4.0.0 (2019-10-08)
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
* `decodeAddress` has been renamed to `decodeAccountID`
|
||||
* `isValidAddress` has been renamed to `isValidClassicAddress`
|
||||
|
||||
### New Features
|
||||
|
||||
* `classicAddressToXAddress` - Derive X-address from classic address, tag, and network ID
|
||||
* `encodeXAddress` - Encode account ID, tag, and network ID as an X-address
|
||||
* `xAddressToClassicAddress` - Decode an X-address to account ID, tag, and network ID
|
||||
* `decodeXAddress` - Convert X-address to classic address, tag, and network ID
|
||||
* `isValidXAddress` - Check whether an X-address (X...) is valid
|
||||
201
packages/ripple-address-codec/LICENSE
Normal file
201
packages/ripple-address-codec/LICENSE
Normal file
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2020 Ripple Labs Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
196
packages/ripple-address-codec/README.md
Normal file
196
packages/ripple-address-codec/README.md
Normal file
@@ -0,0 +1,196 @@
|
||||
# ripple-address-codec
|
||||
|
||||
[![NPM Version][npm-version-image]][npm-url]
|
||||
[![NPM Downloads][npm-downloads-image]][npm-url]
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
|
||||
Functions for encoding and decoding XRP Ledger addresses and seeds.
|
||||
|
||||
Also includes support for encoding/decoding [rippled validator (node) public keys](https://xrpl.org/run-rippled-as-a-validator.html).
|
||||
|
||||
[](https://www.npmjs.org/package/ripple-address-codec)
|
||||
|
||||
## X-address Conversion
|
||||
|
||||
All tools and apps in the XRP Ledger ecosystem are encouraged to adopt support for the X-address format. The X-address format is a single Base58 string that encodes an 'Account ID', a (destination) tag, and whether the address is intended for a test network. This prevents users from unintentionally omitting the destination tag when sending and receiving payments and other transactions.
|
||||
|
||||
## API
|
||||
|
||||
### classicAddressToXAddress(classicAddress: string, tag: number | false, test: boolean): string
|
||||
|
||||
Convert a classic address and (optional) tag to an X-address. If `tag` is `false`, the returned X-address explicitly indicates that the recipient does not want a tag to be used. If `test` is `true`, consumers of the address will know that the address is intended for use on test network(s) and the address will start with `T`.
|
||||
|
||||
```js
|
||||
> const api = require('ripple-address-codec')
|
||||
> api.classicAddressToXAddress('rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf', 4294967295)
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi'
|
||||
```
|
||||
|
||||
Encode a test address e.g. for use with [Testnet or Devnet](https://xrpl.org/xrp-testnet-faucet.html):
|
||||
|
||||
```js
|
||||
> const api = require('ripple-address-codec')
|
||||
> api.classicAddressToXAddress('r3SVzk8ApofDJuVBPKdmbbLjWGCCXpBQ2g', 123, true)
|
||||
'T7oKJ3q7s94kDH6tpkBowhetT1JKfcfdSCmAXbS75iATyLD'
|
||||
```
|
||||
|
||||
### xAddressToClassicAddress(xAddress: string): {classicAddress: string, tag: number | false, test: boolean}
|
||||
|
||||
Convert an X-address to a classic address and tag. If the X-address did not have a tag, the returned object's `tag` will be `false`. (Since `0` is a valid tag, instead of `if (tag)`, use `if (tag !== false)` if you want to check for a tag.) If the X-address is intended for use on test network(s), `test` will be `true`; if it is intended for use on the main network (mainnet), `test` will be `false`.
|
||||
|
||||
```js
|
||||
> const api = require('ripple-address-codec')
|
||||
> api.xAddressToClassicAddress('XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi')
|
||||
{
|
||||
classicAddress: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
tag: 4294967295,
|
||||
test: false
|
||||
}
|
||||
```
|
||||
|
||||
### isValidXAddress(xAddress: string): boolean
|
||||
|
||||
Returns `true` if the provided X-address is valid, or `false` otherwise.
|
||||
|
||||
```js
|
||||
> const api = require('ripple-address-codec')
|
||||
> api.isValidXAddress('XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi')
|
||||
true
|
||||
```
|
||||
|
||||
Returns `false` for classic addresses (starting with `r`). To validate a classic address, use `isValidClassicAddress`.
|
||||
|
||||
### isValidClassicAddress(address: string): boolean
|
||||
|
||||
Check whether a classic address (starting with `r`...) is valid.
|
||||
|
||||
Returns `false` for X-addresses (extended addresses). To validate an X-address, use `isValidXAddress`.
|
||||
|
||||
### encodeSeed(entropy: Buffer, type: 'ed25519' | 'secp256k1'): string
|
||||
|
||||
Encode the given entropy as an XRP Ledger seed (secret). The entropy must be exactly 16 bytes (128 bits). The encoding includes which elliptic curve digital signature algorithm (ECDSA) the seed is intended to be used with. The seed is used to produce the private key.
|
||||
|
||||
### decodeSeed(seed: string): object
|
||||
|
||||
Decode a seed into an object with its version, type, and bytes.
|
||||
|
||||
Return object type:
|
||||
```
|
||||
{
|
||||
version: number[],
|
||||
bytes: Buffer,
|
||||
type: string | null
|
||||
}
|
||||
```
|
||||
|
||||
### encodeAccountID(bytes: Buffer): string
|
||||
|
||||
Encode bytes as a classic address (starting with `r`...).
|
||||
|
||||
### decodeAccountID(accountId: string): Buffer
|
||||
|
||||
Decode a classic address (starting with `r`...) to its raw bytes.
|
||||
|
||||
### encodeNodePublic(bytes: Buffer): string
|
||||
|
||||
Encode bytes to the XRP Ledger "node public key" format (base58).
|
||||
|
||||
This is useful for rippled validators.
|
||||
|
||||
### decodeNodePublic(base58string: string): Buffer
|
||||
|
||||
Decode an XRP Ledger "node public key" (in base58 format) into its raw bytes.
|
||||
|
||||
### encodeAccountPublic(bytes: Buffer): string
|
||||
|
||||
Encode a public key, as for payment channels.
|
||||
|
||||
### decodeAccountPublic(base58string: string): Buffer
|
||||
|
||||
Decode a public key, as for payment channels.
|
||||
|
||||
### encodeXAddress(accountId: Buffer, tag: number | false, test: boolean): string
|
||||
|
||||
Encode account ID, tag, and network ID to X-address.
|
||||
|
||||
`accountId` must be 20 bytes because it is a RIPEMD160 hash, which is 160 bits (160 bits = 20 bytes).
|
||||
|
||||
At this time, `tag` must be <= MAX_32_BIT_UNSIGNED_INT (4294967295) as the XRP Ledger only supports 32-bit tags.
|
||||
|
||||
If `test` is `true`, this address is intended for use with a test network such as Testnet or Devnet.
|
||||
|
||||
### decodeXAddress(xAddress: string): {accountId: Buffer, tag: number | false, test: boolean}
|
||||
|
||||
Convert an X-address to its classic address, tag, and network ID.
|
||||
|
||||
### Other functions
|
||||
|
||||
```js
|
||||
> var api = require('ripple-address-codec');
|
||||
> api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
|
||||
{ version: [ 1, 225, 75 ],
|
||||
bytes: [ 76, 58, 29, 33, 63, 189, 251, 20, 199, 194, 141, 96, 148, 105, 179, 65 ],
|
||||
type: 'ed25519' }
|
||||
> api.decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
|
||||
{ version: 33,
|
||||
bytes: [ 207, 45, 227, 120, 251, 221, 126, 46, 232, 125, 72, 109, 251, 90, 123, 255 ],
|
||||
type: 'secp256k1' }
|
||||
> api.decodeAccountID('rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
|
||||
[ 186,
|
||||
142,
|
||||
120,
|
||||
98,
|
||||
110,
|
||||
228,
|
||||
44,
|
||||
65,
|
||||
180,
|
||||
109,
|
||||
70,
|
||||
195,
|
||||
4,
|
||||
141,
|
||||
243,
|
||||
161,
|
||||
195,
|
||||
200,
|
||||
112,
|
||||
114 ]
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Run unit tests with:
|
||||
|
||||
npm test
|
||||
|
||||
Use `--watch` to run in watch mode, so that when you modify the tests, they are automatically re-run:
|
||||
|
||||
npm test -- --watch
|
||||
|
||||
Use `--coverage` to generate and display code coverage information:
|
||||
|
||||
npm test -- --coverage
|
||||
|
||||
This tells jest to output code coverage info in the `./coverage` directory, in addition to showing it on the command line.
|
||||
|
||||
## Releases
|
||||
|
||||
Use the [ripple-lib release checklist](https://github.com/ripple/ripple-lib/wiki/Release-Checklist), but just use `master` instead of `develop` as this project does not use a develop branch.
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
This library references and adopts code and standards from the following sources:
|
||||
|
||||
- [XLS-5d Standard for Tagged Addresses](https://github.com/xrp-community/standards-drafts/issues/6) by @nbougalis
|
||||
- [XRPL Tagged Address Codec](https://github.com/xrp-community/xrpl-tagged-address-codec) by @WietseWind
|
||||
- [X-Address transaction functions](https://github.com/codetsunami/xrpl-tools/tree/master/xaddress-functions) by @codetsunami
|
||||
|
||||
[coveralls-image]: https://badgen.net/coveralls/c/github/ripple/ripple-address-codec/master
|
||||
[coveralls-url]: https://coveralls.io/r/ripple/ripple-address/codec?branch=master
|
||||
[npm-downloads-image]: https://badgen.net/npm/dm/ripple-address-codec
|
||||
[npm-url]: https://npmjs.org/package/ripple-address-codec
|
||||
[npm-version-image]: https://badgen.net/npm/v/ripple-address-codec
|
||||
[travis-image]: https://badgen.net/travis/ripple/ripple-address-codec/master
|
||||
[travis-url]: https://travis-ci.org/github/ripple/ripple-address-codec
|
||||
10
packages/ripple-address-codec/examples/bitcoin.js
Normal file
10
packages/ripple-address-codec/examples/bitcoin.js
Normal file
@@ -0,0 +1,10 @@
|
||||
var api = require('../');
|
||||
var pubVersion = [0x04, 0x88, 0xB2, 0x1E];
|
||||
var options = {version: pubVersion, alphabet: 'bitcoin'};
|
||||
var key = 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5e4cp9LB';
|
||||
var decoded = api.decode(key, options);
|
||||
var reencoded = api.encode(decoded, options);
|
||||
console.log(key);
|
||||
// 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5e4cp9LB'
|
||||
console.log(reencoded);
|
||||
// 'xpub661MyMwAqRbcEYS8w7XLSVeEsBXy79zSzH1J8vCdxAZningWLdN3zgtU6LBpB85b3D2yc8sfvZU521AAwdZafEz7mnzBBsz4wKY5e4cp9LB'
|
||||
8
packages/ripple-address-codec/jest.config.js
Normal file
8
packages/ripple-address-codec/jest.config.js
Normal file
@@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
"roots": [
|
||||
"<rootDir>/src"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.tsx?$": "ts-jest"
|
||||
},
|
||||
}
|
||||
33
packages/ripple-address-codec/package.json
Normal file
33
packages/ripple-address-codec/package.json
Normal file
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "ripple-address-codec",
|
||||
"version": "4.2.0",
|
||||
"description": "encodes/decodes base58 encoded XRP Ledger identifiers",
|
||||
"files": [
|
||||
"dist/*",
|
||||
"build/*"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"base-x": "3.0.9",
|
||||
"create-hash": "^1.1.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:XRPLF/xrpl.js.git"
|
||||
},
|
||||
"prepublish": "tsc -b",
|
||||
"prepublishOnly": "tslint -b ./ && jest",
|
||||
"scripts": {
|
||||
"build": "tsc -b",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo"
|
||||
},
|
||||
"prettier": "@xrplf/prettier-config",
|
||||
"engines": {
|
||||
"node": ">= 10",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
}
|
||||
259
packages/ripple-address-codec/src/index.test.js
Normal file
259
packages/ripple-address-codec/src/index.test.js
Normal file
@@ -0,0 +1,259 @@
|
||||
const {
|
||||
classicAddressToXAddress,
|
||||
xAddressToClassicAddress,
|
||||
isValidXAddress,
|
||||
encodeXAddress
|
||||
} = require('./index')
|
||||
|
||||
const testCases = [
|
||||
[
|
||||
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
false,
|
||||
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ',
|
||||
'T719a5UwUCnEs54UsxG9CJYYDhwmFCqkr7wxCcNcfZ6p5GZ'
|
||||
],
|
||||
[
|
||||
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
1,
|
||||
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaGZMhc9YTE92ehJ2Fu',
|
||||
'T719a5UwUCnEs54UsxG9CJYYDhwmFCvbJNZbi37gBGkRkbE'
|
||||
],
|
||||
[
|
||||
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
14,
|
||||
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaGo2K5VpXpmCqbV2gS',
|
||||
'T719a5UwUCnEs54UsxG9CJYYDhwmFCvqXVCALUGJGSbNV3x'
|
||||
],
|
||||
[
|
||||
'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59',
|
||||
11747,
|
||||
'X7AcgcsBL6XDcUb289X4mJ8djcdyKaLFuhLRuNXPrDeJd9A',
|
||||
'T719a5UwUCnEs54UsxG9CJYYDhwmFCziiNHtUukubF2Mg6t'
|
||||
],
|
||||
[
|
||||
'rLczgQHxPhWtjkaQqn3Q6UM8AbRbbRvs5K',
|
||||
false,
|
||||
'XVZVpQj8YSVpNyiwXYSqvQoQqgBttTxAZwMcuJd4xteQHyt',
|
||||
'TVVrSWtmQQssgVcmoMBcFQZKKf56QscyWLKnUyiuZW8ALU4'
|
||||
],
|
||||
[
|
||||
'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||
false,
|
||||
'X7YenJqxv3L66CwhBSfd3N8RzGXxYqPopMGMsCcpho79rex',
|
||||
'T77wVQzA8ntj9wvCTNiQpNYLT5hmhRsFyXDoMLqYC4BzQtV'
|
||||
],
|
||||
[
|
||||
'rpZc4mVfWUif9CRoHRKKcmhu1nx2xktxBo',
|
||||
58,
|
||||
'X7YenJqxv3L66CwhBSfd3N8RzGXxYqV56ZkTCa9UCzgaao1',
|
||||
'T77wVQzA8ntj9wvCTNiQpNYLT5hmhR9kej6uxm4jGcQD7rZ'
|
||||
],
|
||||
[
|
||||
'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
|
||||
23480,
|
||||
'X7d3eHCXzwBeWrZec1yT24iZerQjYL8m8zCJ16ACxu1BrBY',
|
||||
'T7YChPFWifjCAXLEtg5N74c7fSAYsvSokwcmBPBUZWhxH5P'
|
||||
],
|
||||
[
|
||||
'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
|
||||
11747,
|
||||
'X7d3eHCXzwBeWrZec1yT24iZerQjYLo2CJf8oVC5CMWey5m',
|
||||
'T7YChPFWifjCAXLEtg5N74c7fSAYsvTcc7nEfwuEEvn5Q4w'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
false,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV5fdx1mHp98tDMoQXb',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQn49b3qD26PK7FcGSKE'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
0,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV8AqEL4xcZj5whKbmc',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnSy8RHqGHoGJ59spi2'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
1,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV8xvjGQTYPiAx6gwDC',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnSz1uDimDdPYXzSpyw'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
2,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV8zpDURx7DzBCkrQE7',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnTryP9tG9TW8GeMBmd'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
32,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtVoYiC9UvKfjKar4LJe',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnT2oqaCDzMEuCDAj1j'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
276,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtVoKj3MnFGMXEFMnvJV',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnTMgJJYfAbsiPsc6Zg'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
65591,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtVozpjdhPQVdt3ghaWw',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQn7ryu2W6njw7mT1jmS'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
16781933,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtVqrDUk2vDpkTjPsY73',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnVsw45sDtGHhLi27Qa'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
4294967294,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV1kAsixQTdMjbWi39u',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnX8tDFQ53itLNqs6vU'
|
||||
],
|
||||
[
|
||||
'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
4294967295,
|
||||
'XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8yuPT7y4xaEHi',
|
||||
'TVE26TYGhfLC7tQDno7G8dGtxSkYQnXoy6kSDh6rZzApc69'
|
||||
],
|
||||
[
|
||||
'rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY',
|
||||
false,
|
||||
'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2gYsjNFQLKYW33DzBm',
|
||||
'TVd2rqMkYL2AyS97NdELcpeiprNBjwLZzuUG5rZnaewsahi'
|
||||
],
|
||||
[
|
||||
'rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY',
|
||||
0,
|
||||
'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2m4Er6SnvjVLpMWPjR',
|
||||
'TVd2rqMkYL2AyS97NdELcpeiprNBjwRQUBetPbyrvXSTuxU'
|
||||
],
|
||||
[
|
||||
'rPEPPER7kfTD9w2To4CQk6UCfuHM9c6GDY',
|
||||
13371337,
|
||||
'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2qwGkhgc48zzcx6Gkr',
|
||||
'TVd2rqMkYL2AyS97NdELcpeiprNBjwVUDvp3vhpXbNhLwJi'
|
||||
]
|
||||
]
|
||||
|
||||
;[false, true].forEach(isTestAddress => {
|
||||
const MAX_32_BIT_UNSIGNED_INT = 4294967295
|
||||
const network = isTestAddress ? ' (test)' : ' (main)'
|
||||
|
||||
for (const i in testCases) {
|
||||
const testCase = testCases[i]
|
||||
const classicAddress = testCase[0]
|
||||
const tag = testCase[1] !== false ? testCase[1] : false
|
||||
const xAddress = isTestAddress ? testCase[3] : testCase[2]
|
||||
test(`Converts ${classicAddress}${tag ? ':' + tag : ''} to ${xAddress}${network}`, () => {
|
||||
expect(classicAddressToXAddress(classicAddress, tag, isTestAddress)).toBe(xAddress)
|
||||
const myClassicAddress = xAddressToClassicAddress(xAddress)
|
||||
expect(myClassicAddress).toEqual({
|
||||
classicAddress,
|
||||
tag,
|
||||
test: isTestAddress
|
||||
})
|
||||
expect(isValidXAddress(xAddress)).toBe(true)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const classicAddress = 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf'
|
||||
const tag = MAX_32_BIT_UNSIGNED_INT + 1
|
||||
|
||||
test(`Converting ${classicAddress}:${tag}${network} throws`, () => {
|
||||
expect(() => {
|
||||
classicAddressToXAddress(classicAddress, tag, isTestAddress)
|
||||
}).toThrowError(new Error('Invalid tag'))
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const classicAddress = 'r'
|
||||
test(`Invalid classic address: Converting ${classicAddress}${network} throws`, () => {
|
||||
expect(() => {
|
||||
classicAddressToXAddress(classicAddress, false, isTestAddress)
|
||||
}).toThrowError(new Error('invalid_input_size: decoded data must have length >= 5'))
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const highAndLowAccounts = [
|
||||
Buffer.from('00'.repeat(20), 'hex'),
|
||||
Buffer.from('00'.repeat(19) + '01', 'hex'),
|
||||
Buffer.from('01'.repeat(20), 'hex'),
|
||||
Buffer.from('FF'.repeat(20), 'hex')
|
||||
]
|
||||
|
||||
highAndLowAccounts.forEach(accountId => {
|
||||
[false, 0, 1, MAX_32_BIT_UNSIGNED_INT].forEach(t => {
|
||||
const tag = (t | false)
|
||||
const xAddress = encodeXAddress(accountId, tag, isTestAddress)
|
||||
test(`Encoding ${accountId.toString('hex')}${tag ? ':' + tag : ''} to ${xAddress} has expected length`, () => {
|
||||
expect(xAddress.length).toBe(47)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
{
|
||||
const xAddress = 'XVLhHMPHU98es4dbozjVtdWzVrDjtV5fdx1mHp98tDMoQXa'
|
||||
test(`Invalid X-address (bad checksum): Converting ${xAddress} throws`, () => {
|
||||
expect(() => {
|
||||
xAddressToClassicAddress(xAddress)
|
||||
}).toThrowError(new Error('checksum_invalid'))
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const xAddress = 'dGzKGt8CVpWoa8aWL1k18tAdy9Won3PxynvbbpkAqp3V47g'
|
||||
test(`Invalid X-address (bad prefix): Converting ${xAddress} throws`, () => {
|
||||
expect(() => {
|
||||
xAddressToClassicAddress(xAddress)
|
||||
}).toThrowError(new Error('Invalid X-address: bad prefix'))
|
||||
})
|
||||
}
|
||||
|
||||
test(`Invalid X-address (64-bit tag) throws`, () => {
|
||||
expect(() => {
|
||||
// Encoded from:
|
||||
// {
|
||||
// classicAddress: 'rGWrZyQqhTp9Xu7G5Pkayo7bXjH4k4QYpf',
|
||||
// tag: MAX_32_BIT_UNSIGNED_INT + 1
|
||||
// }
|
||||
xAddressToClassicAddress('XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8zeUygYrCgrPh')
|
||||
}).toThrowError('Unsupported X-address')
|
||||
})
|
||||
|
||||
test(`Invalid Account ID throws`, () => {
|
||||
expect(() => {
|
||||
encodeXAddress(Buffer.from('00'.repeat(19), 'hex'), false, false)
|
||||
}).toThrowError('Account ID must be 20 bytes')
|
||||
})
|
||||
|
||||
test(`isValidXAddress returns false for invalid X-address`, () => {
|
||||
expect(isValidXAddress('XVLhHMPHU98es4dbozjVtdWzVrDjtV18pX8zeUygYrCgrPh')).toBe(false)
|
||||
})
|
||||
|
||||
test(`Converts X7AcgcsBL6XDcUb... to r9cZA1mLK5R5A... and tag: false`, () => {
|
||||
const classicAddress = 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59'
|
||||
const tag = false
|
||||
const xAddress = 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ'
|
||||
const isTestAddress = false
|
||||
expect(classicAddressToXAddress(classicAddress, tag, isTestAddress)).toBe(xAddress)
|
||||
const myClassicAddress = xAddressToClassicAddress(xAddress)
|
||||
expect(myClassicAddress).toEqual({
|
||||
classicAddress,
|
||||
tag,
|
||||
test: isTestAddress
|
||||
})
|
||||
expect(isValidXAddress(xAddress)).toBe(true)
|
||||
|
||||
// Notice that converting an X-address to a classic address has `result.tag === false` (not undefined)
|
||||
expect(myClassicAddress.tag).toEqual(false)
|
||||
})
|
||||
149
packages/ripple-address-codec/src/index.ts
Normal file
149
packages/ripple-address-codec/src/index.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
import {
|
||||
codec,
|
||||
encodeSeed,
|
||||
decodeSeed,
|
||||
encodeAccountID,
|
||||
decodeAccountID,
|
||||
encodeNodePublic,
|
||||
decodeNodePublic,
|
||||
encodeAccountPublic,
|
||||
decodeAccountPublic,
|
||||
isValidClassicAddress,
|
||||
} from './xrp-codec'
|
||||
import * as assert from 'assert'
|
||||
|
||||
const PREFIX_BYTES = {
|
||||
MAIN: Buffer.from([0x05, 0x44]), // 5, 68
|
||||
TEST: Buffer.from([0x04, 0x93]), // 4, 147
|
||||
}
|
||||
|
||||
function classicAddressToXAddress(
|
||||
classicAddress: string,
|
||||
tag: number | false,
|
||||
test: boolean,
|
||||
): string {
|
||||
const accountId = decodeAccountID(classicAddress)
|
||||
return encodeXAddress(accountId, tag, test)
|
||||
}
|
||||
|
||||
function encodeXAddress(
|
||||
accountId: Buffer,
|
||||
tag: number | false,
|
||||
test: boolean,
|
||||
): string {
|
||||
if (accountId.length !== 20) {
|
||||
// RIPEMD160 is 160 bits = 20 bytes
|
||||
throw new Error('Account ID must be 20 bytes')
|
||||
}
|
||||
const MAX_32_BIT_UNSIGNED_INT = 4294967295
|
||||
const flag = tag === false ? 0 : tag <= MAX_32_BIT_UNSIGNED_INT ? 1 : 2
|
||||
if (flag === 2) {
|
||||
throw new Error('Invalid tag')
|
||||
}
|
||||
if (tag === false) {
|
||||
tag = 0
|
||||
}
|
||||
const bytes = Buffer.concat([
|
||||
test ? PREFIX_BYTES.TEST : PREFIX_BYTES.MAIN,
|
||||
accountId,
|
||||
Buffer.from([
|
||||
flag, // 0x00 if no tag, 0x01 if 32-bit tag
|
||||
tag & 0xff, // first byte
|
||||
(tag >> 8) & 0xff, // second byte
|
||||
(tag >> 16) & 0xff, // third byte
|
||||
(tag >> 24) & 0xff, // fourth byte
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0, // four zero bytes (reserved for 64-bit tags)
|
||||
]),
|
||||
])
|
||||
const xAddress = codec.encodeChecked(bytes)
|
||||
return xAddress
|
||||
}
|
||||
|
||||
function xAddressToClassicAddress(xAddress: string): {
|
||||
classicAddress: string
|
||||
tag: number | false
|
||||
test: boolean
|
||||
} {
|
||||
const { accountId, tag, test } = decodeXAddress(xAddress)
|
||||
const classicAddress = encodeAccountID(accountId)
|
||||
return {
|
||||
classicAddress,
|
||||
tag,
|
||||
test,
|
||||
}
|
||||
}
|
||||
|
||||
function decodeXAddress(xAddress: string): {
|
||||
accountId: Buffer
|
||||
tag: number | false
|
||||
test: boolean
|
||||
} {
|
||||
const decoded = codec.decodeChecked(xAddress)
|
||||
const test = isBufferForTestAddress(decoded)
|
||||
const accountId = decoded.slice(2, 22)
|
||||
const tag = tagFromBuffer(decoded)
|
||||
return {
|
||||
accountId,
|
||||
tag,
|
||||
test,
|
||||
}
|
||||
}
|
||||
|
||||
function isBufferForTestAddress(buf: Buffer): boolean {
|
||||
const decodedPrefix = buf.slice(0, 2)
|
||||
if (PREFIX_BYTES.MAIN.equals(decodedPrefix)) {
|
||||
return false
|
||||
} else if (PREFIX_BYTES.TEST.equals(decodedPrefix)) {
|
||||
return true
|
||||
} else {
|
||||
throw new Error('Invalid X-address: bad prefix')
|
||||
}
|
||||
}
|
||||
|
||||
function tagFromBuffer(buf: Buffer): number | false {
|
||||
const flag = buf[22]
|
||||
if (flag >= 2) {
|
||||
// No support for 64-bit tags at this time
|
||||
throw new Error('Unsupported X-address')
|
||||
}
|
||||
if (flag === 1) {
|
||||
// Little-endian to big-endian
|
||||
return buf[23] + buf[24] * 0x100 + buf[25] * 0x10000 + buf[26] * 0x1000000
|
||||
}
|
||||
assert.strictEqual(flag, 0, 'flag must be zero to indicate no tag')
|
||||
assert.ok(
|
||||
Buffer.from('0000000000000000', 'hex').equals(buf.slice(23, 23 + 8)),
|
||||
'remaining bytes must be zero',
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
function isValidXAddress(xAddress: string): boolean {
|
||||
try {
|
||||
decodeXAddress(xAddress)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export {
|
||||
codec, // Codec with XRP alphabet
|
||||
encodeSeed, // Encode entropy as a "seed"
|
||||
decodeSeed, // Decode a seed into an object with its version, type, and bytes
|
||||
encodeAccountID, // Encode bytes as a classic address (r...)
|
||||
decodeAccountID, // Decode a classic address to its raw bytes
|
||||
encodeNodePublic, // Encode bytes to XRP Ledger node public key format
|
||||
decodeNodePublic, // Decode an XRP Ledger node public key into its raw bytes
|
||||
encodeAccountPublic, // Encode a public key, as for payment channels
|
||||
decodeAccountPublic, // Decode a public key, as for payment channels
|
||||
isValidClassicAddress, // Check whether a classic address (r...) is valid
|
||||
classicAddressToXAddress, // Derive X-address from classic address, tag, and network ID
|
||||
encodeXAddress, // Encode account ID, tag, and network ID to X-address
|
||||
xAddressToClassicAddress, // Decode X-address to account ID, tag, and network ID
|
||||
decodeXAddress, // Convert X-address to classic address, tag, and network ID
|
||||
isValidXAddress, // Check whether an X-address (X...) is valid
|
||||
}
|
||||
40
packages/ripple-address-codec/src/utils.test.js
Normal file
40
packages/ripple-address-codec/src/utils.test.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const {seqEqual, concatArgs} = require('./utils')
|
||||
|
||||
test('two sequences are equal', () => {
|
||||
expect(seqEqual([1, 2, 3], [1, 2, 3])).toBe(true)
|
||||
})
|
||||
|
||||
test('elements must be in the same order', () => {
|
||||
expect(seqEqual([3, 2, 1], [1, 2, 3])).toBe(false)
|
||||
})
|
||||
|
||||
test('sequences do not need to be the same type', () => {
|
||||
expect(seqEqual(Buffer.from([1, 2, 3]), [1, 2, 3])).toBe(true)
|
||||
expect(seqEqual(Buffer.from([1, 2, 3]), new Uint8Array([1, 2, 3]))).toBe(true)
|
||||
})
|
||||
|
||||
test('sequences with a single element', () => {
|
||||
expect(seqEqual(Buffer.from([1]), [1])).toBe(true)
|
||||
expect(seqEqual(Buffer.from([1]), new Uint8Array([1]))).toBe(true)
|
||||
})
|
||||
|
||||
test('empty sequences', () => {
|
||||
expect(seqEqual(Buffer.from([]), [])).toBe(true)
|
||||
expect(seqEqual(Buffer.from([]), new Uint8Array([]))).toBe(true)
|
||||
})
|
||||
|
||||
test('plain numbers are concatenated', () => {
|
||||
expect(concatArgs(10, 20, 30, 40)).toStrictEqual([10, 20, 30, 40])
|
||||
})
|
||||
|
||||
test('a variety of values are concatenated', () => {
|
||||
expect(concatArgs(1, [2, 3], Buffer.from([4,5]), new Uint8Array([6, 7]))).toStrictEqual([1,2,3,4,5,6,7])
|
||||
})
|
||||
|
||||
test('a single value is returned as an array', () => {
|
||||
expect(concatArgs(Buffer.from([7]))).toStrictEqual([7])
|
||||
})
|
||||
|
||||
test('no arguments returns an empty array', () => {
|
||||
expect(concatArgs()).toStrictEqual([])
|
||||
})
|
||||
54
packages/ripple-address-codec/src/utils.ts
Normal file
54
packages/ripple-address-codec/src/utils.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
type Sequence = number[] | Buffer | Uint8Array
|
||||
|
||||
/**
|
||||
* Check whether two sequences (e.g. arrays of numbers) are equal.
|
||||
*
|
||||
* @param arr1 One of the arrays to compare.
|
||||
* @param arr2 The other array to compare.
|
||||
*/
|
||||
export function seqEqual(arr1: Sequence, arr2: Sequence): boolean {
|
||||
if (arr1.length !== arr2.length) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (let i = 0; i < arr1.length; i++) {
|
||||
if (arr1[i] !== arr2[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a value is a sequence (e.g. array of numbers).
|
||||
*
|
||||
* @param val The value to check.
|
||||
*/
|
||||
function isSequence(val: Sequence | number): val is Sequence {
|
||||
return (val as Sequence).length !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenate all `arguments` into a single array. Each argument can be either
|
||||
* a single element or a sequence, which has a `length` property and supports
|
||||
* element retrieval via sequence[ix].
|
||||
*
|
||||
* > concatArgs(1, [2, 3], Buffer.from([4,5]), new Uint8Array([6, 7]));
|
||||
* [1,2,3,4,5,6,7]
|
||||
*
|
||||
* @returns {number[]} Array of concatenated arguments
|
||||
*/
|
||||
export function concatArgs(...args: (number | Sequence)[]): number[] {
|
||||
const ret: number[] = []
|
||||
|
||||
args.forEach(function (arg) {
|
||||
if (isSequence(arg)) {
|
||||
for (let j = 0; j < arg.length; j++) {
|
||||
ret.push(arg[j])
|
||||
}
|
||||
} else {
|
||||
ret.push(arg)
|
||||
}
|
||||
})
|
||||
return ret
|
||||
}
|
||||
246
packages/ripple-address-codec/src/xrp-codec.test.js
Normal file
246
packages/ripple-address-codec/src/xrp-codec.test.js
Normal file
@@ -0,0 +1,246 @@
|
||||
const api = require('./xrp-codec')
|
||||
|
||||
function toHex(bytes) {
|
||||
return Buffer.from(bytes).toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
function toBytes(hex) {
|
||||
return Buffer.from(hex, 'hex')
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a test case for encoding data and a test case for decoding data.
|
||||
*
|
||||
* @param encoder Encoder function to test
|
||||
* @param decoder Decoder function to test
|
||||
* @param base58 Base58-encoded string to decode
|
||||
* @param hex Hexadecimal representation of expected decoded data
|
||||
*/
|
||||
function makeEncodeDecodeTest(encoder, decoder, base58, hex) {
|
||||
test(`can translate between ${hex} and ${base58}`, function() {
|
||||
const actual = encoder(toBytes(hex))
|
||||
expect(actual).toBe(base58)
|
||||
})
|
||||
test(`can translate between ${base58} and ${hex})`, function() {
|
||||
const buf = decoder(base58)
|
||||
expect(toHex(buf)).toBe(hex)
|
||||
})
|
||||
}
|
||||
|
||||
makeEncodeDecodeTest(api.encodeAccountID, api.decodeAccountID, 'rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN',
|
||||
'BA8E78626EE42C41B46D46C3048DF3A1C3C87072')
|
||||
|
||||
makeEncodeDecodeTest(api.encodeNodePublic, api.decodeNodePublic,
|
||||
'n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH',
|
||||
'0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828')
|
||||
|
||||
makeEncodeDecodeTest(api.encodeAccountPublic, api.decodeAccountPublic,
|
||||
'aB44YfzW24VDEJQ2UuLPV2PvqcPCSoLnL7y5M1EzhdW4LnK5xMS3',
|
||||
'023693F15967AE357D0327974AD46FE3C127113B1110D6044FD41E723689F81CC6')
|
||||
|
||||
test('can decode arbitrary seeds', function() {
|
||||
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
|
||||
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
|
||||
expect(decoded.type).toBe('ed25519')
|
||||
|
||||
const decoded2 = api.decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
|
||||
expect(toHex(decoded2.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
|
||||
expect(decoded2.type).toBe('secp256k1')
|
||||
})
|
||||
|
||||
test('can pass a type as second arg to encodeSeed', function() {
|
||||
const edSeed = 'sEdTM1uX8pu2do5XvTnutH6HsouMaM2'
|
||||
const decoded = api.decodeSeed(edSeed)
|
||||
const type = 'ed25519'
|
||||
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
|
||||
expect(decoded.type).toBe(type)
|
||||
expect(api.encodeSeed(decoded.bytes, type)).toBe(edSeed)
|
||||
})
|
||||
|
||||
test('isValidClassicAddress - secp256k1 address valid', function() {
|
||||
expect(api.isValidClassicAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw1')).toBe(true)
|
||||
})
|
||||
|
||||
test('isValidClassicAddress - ed25519 address valid', function() {
|
||||
expect(api.isValidClassicAddress('rLUEXYuLiQptky37CqLcm9USQpPiz5rkpD')).toBe(true)
|
||||
})
|
||||
|
||||
test('isValidClassicAddress - invalid', function() {
|
||||
expect(api.isValidClassicAddress('rU6K7V3Po4snVhBBaU29sesqs2qTQJWDw2')).toBe(false)
|
||||
})
|
||||
|
||||
test('isValidClassicAddress - empty', function() {
|
||||
expect(api.isValidClassicAddress('')).toBe(false)
|
||||
})
|
||||
|
||||
describe('encodeSeed', function() {
|
||||
|
||||
it('encodes a secp256k1 seed', function() {
|
||||
const result = api.encodeSeed(Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFF', 'hex'), 'secp256k1')
|
||||
expect(result).toBe('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
|
||||
})
|
||||
|
||||
it('encodes low secp256k1 seed', function() {
|
||||
const result = api.encodeSeed(Buffer.from('00000000000000000000000000000000', 'hex'), 'secp256k1')
|
||||
expect(result).toBe('sp6JS7f14BuwFY8Mw6bTtLKWauoUs')
|
||||
})
|
||||
|
||||
it('encodes high secp256k1 seed', function() {
|
||||
const result = api.encodeSeed(Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'), 'secp256k1')
|
||||
expect(result).toBe('saGwBRReqUNKuWNLpUAq8i8NkXEPN')
|
||||
})
|
||||
|
||||
it('encodes an ed25519 seed', function() {
|
||||
const result = api.encodeSeed(Buffer.from('4C3A1D213FBDFB14C7C28D609469B341', 'hex'), 'ed25519')
|
||||
expect(result).toBe('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
|
||||
})
|
||||
|
||||
it('encodes low ed25519 seed', function() {
|
||||
const result = api.encodeSeed(Buffer.from('00000000000000000000000000000000', 'hex'), 'ed25519')
|
||||
expect(result).toBe('sEdSJHS4oiAdz7w2X2ni1gFiqtbJHqE')
|
||||
})
|
||||
|
||||
it('encodes high ed25519 seed', function() {
|
||||
const result = api.encodeSeed(Buffer.from('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', 'hex'), 'ed25519')
|
||||
expect(result).toBe('sEdV19BLfeQeKdEXyYA4NhjPJe6XBfG')
|
||||
})
|
||||
|
||||
test('attempting to encode a seed with less than 16 bytes of entropy throws', function() {
|
||||
expect(() => {
|
||||
api.encodeSeed(Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7B', 'hex'), 'secp256k1')
|
||||
}).toThrow('entropy must have length 16')
|
||||
})
|
||||
|
||||
test('attempting to encode a seed with more than 16 bytes of entropy throws', function() {
|
||||
expect(() => {
|
||||
api.encodeSeed(Buffer.from('CF2DE378FBDD7E2EE87D486DFB5A7BFFFF', 'hex'), 'secp256k1')
|
||||
}).toThrow('entropy must have length 16')
|
||||
})
|
||||
})
|
||||
|
||||
describe('decodeSeed', function() {
|
||||
|
||||
it('can decode an Ed25519 seed', function() {
|
||||
const decoded = api.decodeSeed('sEdTM1uX8pu2do5XvTnutH6HsouMaM2')
|
||||
expect(toHex(decoded.bytes)).toBe('4C3A1D213FBDFB14C7C28D609469B341')
|
||||
expect(decoded.type).toBe('ed25519')
|
||||
})
|
||||
|
||||
it('can decode a secp256k1 seed', function() {
|
||||
const decoded = api.decodeSeed('sn259rEFXrQrWyx3Q7XneWcwV6dfL')
|
||||
expect(toHex(decoded.bytes)).toBe('CF2DE378FBDD7E2EE87D486DFB5A7BFF')
|
||||
expect(decoded.type).toBe('secp256k1')
|
||||
})
|
||||
})
|
||||
|
||||
describe('encodeAccountID', function() {
|
||||
|
||||
it('can encode an AccountID', function() {
|
||||
const encoded = api.encodeAccountID(Buffer.from('BA8E78626EE42C41B46D46C3048DF3A1C3C87072', 'hex'))
|
||||
expect(encoded).toBe('rJrRMgiRgrU6hDF4pgu5DXQdWyPbY35ErN')
|
||||
})
|
||||
|
||||
test('unexpected length should throw', function() {
|
||||
expect(() => {
|
||||
api.encodeAccountID(Buffer.from('ABCDEF', 'hex'))
|
||||
}).toThrow(
|
||||
'unexpected_payload_length: bytes.length does not match expectedLength'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('decodeNodePublic', function() {
|
||||
|
||||
it('can decode a NodePublic', function() {
|
||||
const decoded = api.decodeNodePublic('n9MXXueo837zYH36DvMc13BwHcqtfAWNJY5czWVbp7uYTj7x17TH')
|
||||
expect(toHex(decoded)).toBe('0388E5BA87A000CB807240DF8C848EB0B5FFA5C8E5A521BC8E105C0F0A44217828')
|
||||
})
|
||||
})
|
||||
|
||||
test('encodes 123456789 with version byte of 0', () => {
|
||||
expect(api.codec.encode(Buffer.from('123456789'), {
|
||||
versions: [0],
|
||||
expectedLength: 9
|
||||
})).toBe('rnaC7gW34M77Kneb78s')
|
||||
})
|
||||
|
||||
test('multiple versions with no expected length should throw', () => {
|
||||
expect(() => {
|
||||
api.codec.decode('rnaC7gW34M77Kneb78s', {
|
||||
versions: [0, 1]
|
||||
})
|
||||
}).toThrow('expectedLength is required because there are >= 2 possible versions')
|
||||
})
|
||||
|
||||
test('attempting to decode data with length < 5 should throw', () => {
|
||||
expect(() => {
|
||||
api.codec.decode('1234', {
|
||||
versions: [0]
|
||||
})
|
||||
}).toThrow('invalid_input_size: decoded data must have length >= 5')
|
||||
})
|
||||
|
||||
test('attempting to decode data with unexpected version should throw', () => {
|
||||
expect(() => {
|
||||
api.codec.decode('rnaC7gW34M77Kneb78s', {
|
||||
versions: [2]
|
||||
})
|
||||
}).toThrow('version_invalid: version bytes do not match any of the provided version(s)')
|
||||
})
|
||||
|
||||
test('invalid checksum should throw', () => {
|
||||
expect(() => {
|
||||
api.codec.decode('123456789', {
|
||||
versions: [0, 1]
|
||||
})
|
||||
}).toThrow('checksum_invalid')
|
||||
})
|
||||
|
||||
test('empty payload should throw', () => {
|
||||
expect(() => {
|
||||
api.codec.decode('', {
|
||||
versions: [0, 1]
|
||||
})
|
||||
}).toThrow('invalid_input_size: decoded data must have length >= 5')
|
||||
})
|
||||
|
||||
test('decode data', () => {
|
||||
expect(api.codec.decode('rnaC7gW34M77Kneb78s', {
|
||||
versions: [0]
|
||||
})).toStrictEqual({
|
||||
version: [0],
|
||||
bytes: Buffer.from('123456789'),
|
||||
type: null
|
||||
})
|
||||
})
|
||||
|
||||
test('decode data with expected length', function() {
|
||||
expect(api.codec.decode('rnaC7gW34M77Kneb78s', {
|
||||
versions: [0],
|
||||
expectedLength: 9
|
||||
})
|
||||
).toStrictEqual({
|
||||
version: [0],
|
||||
bytes: Buffer.from('123456789'),
|
||||
type: null
|
||||
})
|
||||
})
|
||||
|
||||
test('decode data with wrong expected length should throw', function() {
|
||||
expect(() => {
|
||||
api.codec.decode('rnaC7gW34M77Kneb78s', {
|
||||
versions: [0],
|
||||
expectedLength: 8
|
||||
})
|
||||
}).toThrow(
|
||||
'version_invalid: version bytes do not match any of the provided version(s)'
|
||||
)
|
||||
expect(() => {
|
||||
api.codec.decode('rnaC7gW34M77Kneb78s', {
|
||||
versions: [0],
|
||||
expectedLength: 10
|
||||
})
|
||||
}).toThrow(
|
||||
'version_invalid: version bytes do not match any of the provided version(s)'
|
||||
)
|
||||
})
|
||||
241
packages/ripple-address-codec/src/xrp-codec.ts
Normal file
241
packages/ripple-address-codec/src/xrp-codec.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* Codec class
|
||||
*/
|
||||
|
||||
import * as baseCodec from 'base-x'
|
||||
import { seqEqual, concatArgs } from './utils'
|
||||
|
||||
class Codec {
|
||||
sha256: (bytes: Uint8Array) => Buffer
|
||||
alphabet: string
|
||||
codec: any
|
||||
base: number
|
||||
|
||||
constructor(options: {
|
||||
sha256: (bytes: Uint8Array) => Buffer
|
||||
alphabet: string
|
||||
}) {
|
||||
this.sha256 = options.sha256
|
||||
this.alphabet = options.alphabet
|
||||
this.codec = baseCodec(this.alphabet)
|
||||
this.base = this.alphabet.length
|
||||
}
|
||||
|
||||
/**
|
||||
* Encoder.
|
||||
*
|
||||
* @param bytes Buffer of data to encode.
|
||||
* @param opts Options object including the version bytes and the expected length of the data to encode.
|
||||
*/
|
||||
encode(
|
||||
bytes: Buffer,
|
||||
opts: {
|
||||
versions: number[]
|
||||
expectedLength: number
|
||||
},
|
||||
): string {
|
||||
const versions = opts.versions
|
||||
return this.encodeVersioned(bytes, versions, opts.expectedLength)
|
||||
}
|
||||
|
||||
encodeVersioned(
|
||||
bytes: Buffer,
|
||||
versions: number[],
|
||||
expectedLength: number,
|
||||
): string {
|
||||
if (expectedLength && bytes.length !== expectedLength) {
|
||||
throw new Error(
|
||||
'unexpected_payload_length: bytes.length does not match expectedLength.' +
|
||||
' Ensure that the bytes are a Buffer.',
|
||||
)
|
||||
}
|
||||
return this.encodeChecked(Buffer.from(concatArgs(versions, bytes)))
|
||||
}
|
||||
|
||||
encodeChecked(buffer: Buffer): string {
|
||||
const check = this.sha256(this.sha256(buffer)).slice(0, 4)
|
||||
return this.encodeRaw(Buffer.from(concatArgs(buffer, check)))
|
||||
}
|
||||
|
||||
encodeRaw(bytes: Buffer): string {
|
||||
return this.codec.encode(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decoder.
|
||||
*
|
||||
* @param base58string Base58Check-encoded string to decode.
|
||||
* @param opts Options object including the version byte(s) and the expected length of the data after decoding.
|
||||
*/
|
||||
decode(
|
||||
base58string: string,
|
||||
opts: {
|
||||
versions: (number | number[])[]
|
||||
expectedLength?: number
|
||||
versionTypes?: ['ed25519', 'secp256k1']
|
||||
},
|
||||
): {
|
||||
version: number[]
|
||||
bytes: Buffer
|
||||
type: string | null
|
||||
} {
|
||||
const versions = opts.versions
|
||||
const types = opts.versionTypes
|
||||
|
||||
const withoutSum = this.decodeChecked(base58string)
|
||||
|
||||
if (versions.length > 1 && !opts.expectedLength) {
|
||||
throw new Error(
|
||||
'expectedLength is required because there are >= 2 possible versions',
|
||||
)
|
||||
}
|
||||
const versionLengthGuess =
|
||||
typeof versions[0] === 'number' ? 1 : (versions[0] as number[]).length
|
||||
const payloadLength =
|
||||
opts.expectedLength || withoutSum.length - versionLengthGuess
|
||||
const versionBytes = withoutSum.slice(0, -payloadLength)
|
||||
const payload = withoutSum.slice(-payloadLength)
|
||||
|
||||
for (let i = 0; i < versions.length; i++) {
|
||||
const version: number[] = Array.isArray(versions[i])
|
||||
? (versions[i] as number[])
|
||||
: [versions[i] as number]
|
||||
if (seqEqual(versionBytes, version)) {
|
||||
return {
|
||||
version,
|
||||
bytes: payload,
|
||||
type: types ? types[i] : null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'version_invalid: version bytes do not match any of the provided version(s)',
|
||||
)
|
||||
}
|
||||
|
||||
decodeChecked(base58string: string): Buffer {
|
||||
const buffer = this.decodeRaw(base58string)
|
||||
if (buffer.length < 5) {
|
||||
throw new Error('invalid_input_size: decoded data must have length >= 5')
|
||||
}
|
||||
if (!this.verifyCheckSum(buffer)) {
|
||||
throw new Error('checksum_invalid')
|
||||
}
|
||||
return buffer.slice(0, -4)
|
||||
}
|
||||
|
||||
decodeRaw(base58string: string): Buffer {
|
||||
return this.codec.decode(base58string)
|
||||
}
|
||||
|
||||
verifyCheckSum(bytes: Buffer): boolean {
|
||||
const computed = this.sha256(this.sha256(bytes.slice(0, -4))).slice(0, 4)
|
||||
const checksum = bytes.slice(-4)
|
||||
return seqEqual(computed, checksum)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* XRP codec
|
||||
*/
|
||||
|
||||
// Pure JavaScript hash functions in the browser, native hash functions in Node.js
|
||||
const createHash = require('create-hash')
|
||||
|
||||
// base58 encodings: https://xrpl.org/base58-encodings.html
|
||||
const ACCOUNT_ID = 0 // Account address (20 bytes)
|
||||
const ACCOUNT_PUBLIC_KEY = 0x23 // Account public key (33 bytes)
|
||||
const FAMILY_SEED = 0x21 // 33; Seed value (for secret keys) (16 bytes)
|
||||
const NODE_PUBLIC = 0x1c // 28; Validation public key (33 bytes)
|
||||
|
||||
const ED25519_SEED = [0x01, 0xe1, 0x4b] // [1, 225, 75]
|
||||
|
||||
const codecOptions = {
|
||||
sha256: function (bytes: Uint8Array) {
|
||||
return createHash('sha256').update(Buffer.from(bytes)).digest()
|
||||
},
|
||||
alphabet: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
|
||||
}
|
||||
|
||||
const codecWithXrpAlphabet = new Codec(codecOptions)
|
||||
|
||||
export const codec = codecWithXrpAlphabet
|
||||
|
||||
// entropy is a Buffer of size 16
|
||||
// type is 'ed25519' or 'secp256k1'
|
||||
export function encodeSeed(
|
||||
entropy: Buffer,
|
||||
type: 'ed25519' | 'secp256k1',
|
||||
): string {
|
||||
if (entropy.length !== 16) {
|
||||
throw new Error('entropy must have length 16')
|
||||
}
|
||||
const opts = {
|
||||
expectedLength: 16,
|
||||
|
||||
// for secp256k1, use `FAMILY_SEED`
|
||||
versions: type === 'ed25519' ? ED25519_SEED : [FAMILY_SEED],
|
||||
}
|
||||
|
||||
// prefixes entropy with version bytes
|
||||
return codecWithXrpAlphabet.encode(entropy, opts)
|
||||
}
|
||||
|
||||
export function decodeSeed(
|
||||
seed: string,
|
||||
opts: {
|
||||
versionTypes: ['ed25519', 'secp256k1']
|
||||
versions: (number | number[])[]
|
||||
expectedLength: number
|
||||
} = {
|
||||
versionTypes: ['ed25519', 'secp256k1'],
|
||||
versions: [ED25519_SEED, FAMILY_SEED],
|
||||
expectedLength: 16,
|
||||
},
|
||||
) {
|
||||
return codecWithXrpAlphabet.decode(seed, opts)
|
||||
}
|
||||
|
||||
export function encodeAccountID(bytes: Buffer): string {
|
||||
const opts = { versions: [ACCOUNT_ID], expectedLength: 20 }
|
||||
return codecWithXrpAlphabet.encode(bytes, opts)
|
||||
}
|
||||
|
||||
export const encodeAddress = encodeAccountID
|
||||
|
||||
export function decodeAccountID(accountId: string): Buffer {
|
||||
const opts = { versions: [ACCOUNT_ID], expectedLength: 20 }
|
||||
return codecWithXrpAlphabet.decode(accountId, opts).bytes
|
||||
}
|
||||
|
||||
export const decodeAddress = decodeAccountID
|
||||
|
||||
export function decodeNodePublic(base58string: string): Buffer {
|
||||
const opts = { versions: [NODE_PUBLIC], expectedLength: 33 }
|
||||
return codecWithXrpAlphabet.decode(base58string, opts).bytes
|
||||
}
|
||||
|
||||
export function encodeNodePublic(bytes: Buffer): string {
|
||||
const opts = { versions: [NODE_PUBLIC], expectedLength: 33 }
|
||||
return codecWithXrpAlphabet.encode(bytes, opts)
|
||||
}
|
||||
|
||||
export function encodeAccountPublic(bytes: Buffer): string {
|
||||
const opts = { versions: [ACCOUNT_PUBLIC_KEY], expectedLength: 33 }
|
||||
return codecWithXrpAlphabet.encode(bytes, opts)
|
||||
}
|
||||
|
||||
export function decodeAccountPublic(base58string: string): Buffer {
|
||||
const opts = { versions: [ACCOUNT_PUBLIC_KEY], expectedLength: 33 }
|
||||
return codecWithXrpAlphabet.decode(base58string, opts).bytes
|
||||
}
|
||||
|
||||
export function isValidClassicAddress(address: string): boolean {
|
||||
try {
|
||||
decodeAccountID(address)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
19
packages/ripple-address-codec/tsconfig.json
Normal file
19
packages/ripple-address-codec/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"es2017"
|
||||
],
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"removeComments": false,
|
||||
"preserveConstEnums": false,
|
||||
"suppressImplicitAnyIndexErrors": false,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
4
packages/ripple-binary-codec/.eslintignore
Normal file
4
packages/ripple-binary-codec/.eslintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
.github
|
||||
coverage
|
||||
124
packages/ripple-binary-codec/.eslintrc.js
Normal file
124
packages/ripple-binary-codec/.eslintrc.js
Normal file
@@ -0,0 +1,124 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
|
||||
parser: '@typescript-eslint/parser', // Make ESLint compatible with TypeScript
|
||||
parserOptions: {
|
||||
// Enable linting rules with type information from our tsconfig
|
||||
tsconfigRootDir: __dirname,
|
||||
project: ['./tsconfig.eslint.json'],
|
||||
|
||||
sourceType: 'module', // Allow the use of imports / ES modules
|
||||
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true, // Enable global strict mode
|
||||
},
|
||||
},
|
||||
|
||||
// Specify global variables that are predefined
|
||||
env: {
|
||||
browser: true, // Enable browser global variables
|
||||
node: true, // Enable node global variables & Node.js scoping
|
||||
es2020: true, // Add all ECMAScript 2020 globals and automatically set the ecmaVersion parser option to ES2020
|
||||
jest: true, // Add Mocha testing global variables
|
||||
},
|
||||
|
||||
plugins: [],
|
||||
extends: ['@xrplf/eslint-config/base'],
|
||||
|
||||
rules: {
|
||||
// ** TODO **
|
||||
// all of the below are turned off for now during the migration to a
|
||||
// monorepo. They need to actually be addressed!
|
||||
// **
|
||||
'@typescript-eslint/naming-convention': 'off',
|
||||
'@typescript-eslint/prefer-readonly': 'off',
|
||||
'@typescript-eslint/no-parameter-properties': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-unsafe-member-access': 'off',
|
||||
'@typescript-eslint/no-unsafe-call': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-require-imports': 'off',
|
||||
'@typescript-eslint/no-unsafe-assignment': 'off',
|
||||
'@typescript-eslint/restrict-plus-operands': 'off',
|
||||
'@typescript-eslint/unbound-method': 'off',
|
||||
'@typescript-eslint/restrict-template-expressions': 'off',
|
||||
'@typescript-eslint/no-base-to-string': 'off',
|
||||
'@typescript-eslint/no-unsafe-return': 'off',
|
||||
'@typescript-eslint/promise-function-async': 'off',
|
||||
'@typescript-eslint/explicit-member-accessibility': 'off',
|
||||
'@typescript-eslint/array-type': 'off',
|
||||
'@typescript-eslint/no-magic-numbers': 'off',
|
||||
'@typescript-eslint/no-useless-constructor': 'off',
|
||||
'@typescript-eslint/no-unnecessary-condition': 'off',
|
||||
'@typescript-eslint/consistent-type-assertions': 'off',
|
||||
'@typescript-eslint/prefer-for-of': 'off',
|
||||
'@typescript-eslint/prefer-nullish-coalescing': 'off',
|
||||
'@typescript-eslint/require-array-sort-compare': 'off',
|
||||
'@typescript-eslint/prefer-includes': 'off',
|
||||
'@typescript-eslint/dot-notation': 'off',
|
||||
'@typescript-eslint/consistent-type-definitions': 'off',
|
||||
'@typescript-eslint/no-type-alias': 'off',
|
||||
'@typescript-eslint/member-ordering': 'off',
|
||||
'@typescript-eslint/prefer-string-starts-ends-with': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-extraneous-class': 'off',
|
||||
'@typescript-eslint/consistent-type-assertions': 'off',
|
||||
'import/unambiguous': 'off',
|
||||
'import/extensions': 'off',
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/no-useless-path-segments': 'off',
|
||||
'import/no-unused-modules': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
'import/order': 'off',
|
||||
'import/no-commonjs': 'off',
|
||||
'import/newline-after-import': 'off',
|
||||
'node/global-require': 'off',
|
||||
'consistent-default-export-name/default-import-match-filename': 'off',
|
||||
'jsdoc/require-throws': 'off',
|
||||
'jsdoc/require-description-complete-sentence': 'off',
|
||||
'jsdoc/require-jsdoc': 'off',
|
||||
'jsdoc/check-tag-names': 'off',
|
||||
'jsdoc/require-returns': 'off',
|
||||
'jsdoc/require-hyphen-before-param-description': 'off',
|
||||
'jsdoc/require-description': 'off',
|
||||
'jsdoc/require-param': 'off',
|
||||
'jsdoc/check-param-names': 'off',
|
||||
'jsdoc/newline-after-description': 'off',
|
||||
'jsdoc/require-returns-check': 'off',
|
||||
'tsdoc/syntax': 'off',
|
||||
'eslint-comments/require-description': 'off',
|
||||
'eslint-comments/no-unused-disable': 'off',
|
||||
'prefer-const': 'off',
|
||||
'global-require': 'off',
|
||||
'id-length': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-bitwise': 'off',
|
||||
'spaced-comment': 'off',
|
||||
'prefer-template': 'off',
|
||||
'prefer-object-spread': 'off',
|
||||
'no-inline-comments': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'new-cap': 'off',
|
||||
'id-blacklist': 'off',
|
||||
'max-lines-per-function': 'off',
|
||||
'require-unicode-regexp': 'off',
|
||||
'no-undef-init': 'off',
|
||||
'curly': 'off',
|
||||
'eqeqeq': 'off',
|
||||
'no-console': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'operator-assignment': 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'no-else-return': 'off',
|
||||
'yoda': 'off',
|
||||
'max-depth': 'off',
|
||||
'multiline-comment-style': 'off',
|
||||
'one-var': 'off',
|
||||
'no-negated-condition': 'off',
|
||||
'radix': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'no-useless-concat': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
},
|
||||
}
|
||||
64
packages/ripple-binary-codec/.gitignore
vendored
Normal file
64
packages/ripple-binary-codec/.gitignore
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
# .gitignore
|
||||
|
||||
# Ignore vim swap files.
|
||||
*.swp
|
||||
|
||||
# Ignore SCons support files.
|
||||
.sconsign.dblite
|
||||
|
||||
# Ignore python compiled files.
|
||||
*.pyc
|
||||
|
||||
# Ignore Macintosh Desktop Services Store files.
|
||||
.DS_Store
|
||||
|
||||
# Ignore backup/temps
|
||||
*~
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build/
|
||||
distrib/
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
Release/*.*
|
||||
|
||||
# Ignore locally installed node_modules
|
||||
node_modules
|
||||
!test/node_modules
|
||||
|
||||
# Ignore tmp directory.
|
||||
tmp
|
||||
|
||||
# Ignore database directory.
|
||||
db/*.db
|
||||
db/*.db-*
|
||||
|
||||
# Ignore customized configs
|
||||
rippled.cfg
|
||||
validators.txt
|
||||
test/config.js
|
||||
|
||||
# Ignore coverage files
|
||||
/lib-cov
|
||||
/src-cov
|
||||
/coverage.html
|
||||
/coverage
|
||||
|
||||
# Ignore IntelliJ files
|
||||
.idea
|
||||
|
||||
# Ignore npm-debug
|
||||
npm-debug.log
|
||||
|
||||
# Ignore dist folder, build for bower
|
||||
dist/
|
||||
|
||||
# Ignore flow output directory
|
||||
out/
|
||||
|
||||
# Ignore perf test cache
|
||||
scripts/cache
|
||||
|
||||
eslintrc
|
||||
1
packages/ripple-binary-codec/.nvmrc
Normal file
1
packages/ripple-binary-codec/.nvmrc
Normal file
@@ -0,0 +1 @@
|
||||
10.22.0
|
||||
90
packages/ripple-binary-codec/HISTORY.md
Normal file
90
packages/ripple-binary-codec/HISTORY.md
Normal file
@@ -0,0 +1,90 @@
|
||||
# ripple-binary-codec Release History
|
||||
|
||||
## 1.1.3 (2021-06-11)
|
||||
- Fix for case UInt64.from string allowing lowercase hex (#135)
|
||||
- Fix for `ValidatorToReEnable` field code (#130)
|
||||
|
||||
## 1.1.2 (2021-03-10)
|
||||
- Fix for case UInt64.from string '0' due to changes in rippled 1.7.0
|
||||
|
||||
## 1.1.1 (2021-02-12)
|
||||
- PathSet.toJSON() does not return undefined values
|
||||
- Add support for X-Addresses in Issued Currency Amounts
|
||||
- Fix STArray error message
|
||||
|
||||
## 1.1.0 (2020-12-03)
|
||||
- Add support for Tickets (TicketBatch amendment)
|
||||
- Fix web browser compatibility
|
||||
|
||||
## 1.0.2 (2020-09-11)
|
||||
- Allow currencies to be encoded from any 3 character ASCII code
|
||||
|
||||
## 1.0.1 (2020-09-08)
|
||||
- Filter out fields with undefined values
|
||||
|
||||
## 1.0.0 (2020-08-17)
|
||||
|
||||
- Migrate to TypeScript
|
||||
- Javascript classes used
|
||||
- Generics for constructing core types
|
||||
- Reduced dependencies
|
||||
- Dependent on create-hash, decimal.js, ripple-address-codec
|
||||
- Migrate testing to Jest and added tests
|
||||
- Tests for pseudo-transactions
|
||||
- Added support for NegativeUNL pseudo-transactions
|
||||
|
||||
## 0.2.6 (2019-12-31)
|
||||
|
||||
- Update dependencies
|
||||
- decimal.js, fs-extra, mocha, handlebars, bn.js, babel-eslint, ripple-address-codec
|
||||
|
||||
## 0.2.5 (2019-12-14)
|
||||
|
||||
- Add support for AccountDelete (#37)
|
||||
|
||||
## 0.2.4 (2019-09-04)
|
||||
|
||||
- Update ripple-address-codec to 3.0.4
|
||||
|
||||
## 0.2.3 (2019-08-29)
|
||||
|
||||
- Expand node version compatibility (#32, #33)
|
||||
|
||||
## 0.2.2 (2019-07-26)
|
||||
|
||||
- Input validation - Amount and Fee should not allow fractional XRP drops ([#31](https://github.com/ripple/ripple-binary-codec/issues/31))
|
||||
- Fix lint errors
|
||||
- Update dependencies (including lodash and mocha)
|
||||
- Require node 10 (.nvmrc)
|
||||
- Remove assert-diff
|
||||
- Remove codecov.io as it did not appear to work. The `package.json` script was:
|
||||
- `"codecov": "cat ./coverage/coverage.json | ./node_modules/codecov.io/bin/codecov.io.js"`
|
||||
|
||||
## 0.2.1
|
||||
|
||||
- Add tecKILLED from amendment fix1578 (PR #27 fixes #25)
|
||||
|
||||
## 0.2.0
|
||||
|
||||
- Add DepositPreauth fields
|
||||
- https://developers.ripple.com/depositauth.html
|
||||
|
||||
## 0.1.14
|
||||
|
||||
- Skip amount validation when deserializing f72c115
|
||||
|
||||
## 0.1.13
|
||||
|
||||
- Add Check, CheckCreate, CheckCash, CheckCancel
|
||||
|
||||
## 0.1.11
|
||||
|
||||
- Add ledger header decode function
|
||||
|
||||
## 0.1.8
|
||||
|
||||
## 0.1.7
|
||||
|
||||
## 0.1.6
|
||||
|
||||
## 0.1.3
|
||||
13
packages/ripple-binary-codec/LICENSE
Normal file
13
packages/ripple-binary-codec/LICENSE
Normal file
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2015 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
107
packages/ripple-binary-codec/README.md
Normal file
107
packages/ripple-binary-codec/README.md
Normal file
@@ -0,0 +1,107 @@
|
||||
# ripple-binary-codec [](https://npmjs.org/package/ripple-binary-codec)
|
||||
|
||||
Functions to encode/decode to/from the ripple [binary serialization format](https://xrpl.org/serialization.html)
|
||||
|
||||
[](https://www.npmjs.org/package/ripple-binary-codec)
|
||||
|
||||
## API
|
||||
```js
|
||||
> const api = require('ripple-binary-codec')
|
||||
```
|
||||
|
||||
|
||||
### decode(binary: string): object
|
||||
Decode a hex-string into a transaction object.
|
||||
```js
|
||||
> api.decode('1100612200000000240000000125000000072D0000000055DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF6240000002540BE4008114D0F5430B66E06498D4CEEC816C7B3337F9982337')
|
||||
{
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
Flags: 0,
|
||||
Sequence: 1,
|
||||
PreviousTxnLgrSeq: 7,
|
||||
OwnerCount: 0,
|
||||
PreviousTxnID: 'DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF',
|
||||
Balance: '10000000000',
|
||||
Account: 'rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv'
|
||||
}
|
||||
```
|
||||
|
||||
### encode(json: object): string
|
||||
Encode a transaction object into a hex-string. Note that encode filters out fields with undefined values.
|
||||
```js
|
||||
> api.encode({
|
||||
LedgerEntryType: 'AccountRoot',
|
||||
Flags: 0,
|
||||
Sequence: 1,
|
||||
PreviousTxnLgrSeq: 7,
|
||||
OwnerCount: 0,
|
||||
PreviousTxnID: 'DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF',
|
||||
Balance: '10000000000',
|
||||
Account: 'rLs1MzkFWCxTbuAHgjeTZK4fcCDDnf2KRv'
|
||||
})
|
||||
'1100612200000000240000000125000000072D0000000055DF530FB14C5304852F20080B0A8EEF3A6BDD044F41F4EBBD68B8B321145FE4FF6240000002540BE4008114D0F5430B66E06498D4CEEC816C7B3337F9982337'
|
||||
```
|
||||
|
||||
#### X-Address Compatibility
|
||||
* ripple-binary-codec handles X-addresses by looking for a few specific files (Account/SourceTag, Destination/DestinationTag).
|
||||
* If other fields (in the future) must to support X-addresses with tags, this library will need to be updated.
|
||||
* When decoding rippled binary, the output will always output classic address + tag, with no X-addresses. X-address support only applies when encoding to binary.
|
||||
|
||||
#### Encoding Currency Codes
|
||||
* The standard format for currency codes is a three-letter string such as `USD`. This is intended for use with ISO 4217 Currency Codes.
|
||||
* Currency codes must be exactly 3 ASCII characters in length and there are [a few other rules](https://xrpl.org/currency-formats.html#currency-codes).
|
||||
* ripple-binary-codec allows any 3-character ASCII string to be encoded as a currency code, although rippled may enforce tighter restrictions.
|
||||
* When _decoding_, if a currency code is three uppercase letters or numbers (`/^[A-Z0-9]{3}$/`), then it will be decoded into that string. For example,`0000000000000000000000004142430000000000` decodes as `ABC`.
|
||||
* When decoding, if a currency code is does not match the regex, then it is not considered to be an ISO 4217 or pseudo-ISO currency. ripple-binary-codec will return a 160-bit hex-string (40 hex characters). For example, `0000000000000000000000006142430000000000` (`aBC`) decodes as `0000000000000000000000006142430000000000` because it contains a lowercase letter.
|
||||
|
||||
### encodeForSigning(json: object): string
|
||||
|
||||
Encode the transaction object for signing.
|
||||
|
||||
### encodeForSigningClaim(json: object): string
|
||||
|
||||
Encode the transaction object for payment channel claim.
|
||||
|
||||
### encodeForMultisigning(json: object, signer: string): string
|
||||
|
||||
Encode the transaction object for multi-signing.
|
||||
|
||||
### encodeQuality(value: string): string
|
||||
```js
|
||||
> api.encodeQuality('195796912.5171664')
|
||||
'5D06F4C3362FE1D0'
|
||||
```
|
||||
|
||||
### decodeQuality(value: string): string
|
||||
```js
|
||||
> api.decodeQuality('5D06F4C3362FE1D0')
|
||||
'195796912.5171664'
|
||||
```
|
||||
|
||||
### decodeLedgerData(binary: string): object
|
||||
```js
|
||||
> api.decodeLedgerData("01E91435016340767BF1C4A3EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F873B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D521276CDE21276CE60A00")
|
||||
{
|
||||
ledger_index: 32052277,
|
||||
total_coins: '99994494362043555',
|
||||
parent_hash: 'EACEB081770D8ADE216C85445DD6FB002C6B5A2930F2DECE006DA18150CB18F6',
|
||||
transaction_hash: 'DD33F6F0990754C962A7CCE62F332FF9C13939B03B864117F0BDA86B6E9B4F87',
|
||||
account_hash: '3B5C3E520634D343EF5D9D9A4246643D64DAD278BA95DC0EAC6EB5350CF970D5',
|
||||
parent_close_time: 556231902,
|
||||
close_time: 556231910,
|
||||
close_time_resolution: 10,
|
||||
close_flags: 0
|
||||
}
|
||||
```
|
||||
|
||||
## Tests
|
||||
|
||||
Run unit tests with:
|
||||
|
||||
npm test
|
||||
|
||||
Use `--coverage` to generate and display code coverage information:
|
||||
|
||||
npm test --coverage
|
||||
|
||||
This tells jest to output code coverage info in the `./coverage` directory, in addition to showing it on the command line.
|
||||
44
packages/ripple-binary-codec/package.json
Normal file
44
packages/ripple-binary-codec/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "ripple-binary-codec",
|
||||
"version": "1.2.0",
|
||||
"description": "XRP Ledger binary codec",
|
||||
"files": [
|
||||
"dist/*",
|
||||
"bin/*",
|
||||
"test/*"
|
||||
],
|
||||
"main": "dist/",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"assert": "^2.0.0",
|
||||
"big-integer": "^1.6.48",
|
||||
"buffer": "5.6.0",
|
||||
"create-hash": "^1.2.0",
|
||||
"decimal.js": "^10.2.0",
|
||||
"ripple-address-codec": "^4.2.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc -b && cp ./src/enums/definitions.json ./dist/enums",
|
||||
"clean": "rm -rf ./dist && rm -rf tsconfig.tsbuildinfo",
|
||||
"prepare": "npm run build && npm test",
|
||||
"test": "jest",
|
||||
"lint": "eslint . --ext .ts --ext .test.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:XRPLF/xrpl.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/XRPLF/xrpl.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/XRPLF/xrpl.js/packages/ripple-binary-codec#readme",
|
||||
"license": "ISC",
|
||||
"readmeFilename": "README.md",
|
||||
"prettier": "@xrplf/prettier-config",
|
||||
"engines": {
|
||||
"node": ">=10.22.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
}
|
||||
3
packages/ripple-binary-codec/src/README.md
Normal file
3
packages/ripple-binary-codec/src/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# ripple-binary-codec
|
||||
|
||||
Serialize and deserialize transactions according to the XRP Ledger protocol.
|
||||
154
packages/ripple-binary-codec/src/binary.ts
Normal file
154
packages/ripple-binary-codec/src/binary.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
/* eslint-disable func-style */
|
||||
|
||||
import { coreTypes } from './types'
|
||||
import { BinaryParser } from './serdes/binary-parser'
|
||||
import { AccountID } from './types/account-id'
|
||||
import { HashPrefix } from './hash-prefixes'
|
||||
import { BinarySerializer, BytesList } from './serdes/binary-serializer'
|
||||
import { sha512Half, transactionID } from './hashes'
|
||||
import { FieldInstance } from './enums'
|
||||
import { STObject } from './types/st-object'
|
||||
import { JsonObject } from './types/serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
import * as bigInt from 'big-integer'
|
||||
|
||||
/**
|
||||
* Construct a BinaryParser
|
||||
*
|
||||
* @param bytes hex-string to construct BinaryParser from
|
||||
* @returns A BinaryParser
|
||||
*/
|
||||
const makeParser = (bytes: string): BinaryParser => new BinaryParser(bytes)
|
||||
|
||||
/**
|
||||
* Parse BinaryParser into JSON
|
||||
*
|
||||
* @param parser BinaryParser object
|
||||
* @returns JSON for the bytes in the BinaryParser
|
||||
*/
|
||||
const readJSON = (parser: BinaryParser): JsonObject =>
|
||||
(parser.readType(coreTypes.STObject) as STObject).toJSON()
|
||||
|
||||
/**
|
||||
* Parse a hex-string into its JSON interpretation
|
||||
*
|
||||
* @param bytes hex-string to parse into JSON
|
||||
* @returns JSON
|
||||
*/
|
||||
const binaryToJSON = (bytes: string): JsonObject => readJSON(makeParser(bytes))
|
||||
|
||||
/**
|
||||
* Interface for passing parameters to SerializeObject
|
||||
*
|
||||
* @field set signingFieldOnly to true if you want to serialize only signing fields
|
||||
*/
|
||||
interface OptionObject {
|
||||
prefix?: Buffer
|
||||
suffix?: Buffer
|
||||
signingFieldsOnly?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to serialize JSON object representing a transaction
|
||||
*
|
||||
* @param object JSON object to serialize
|
||||
* @param opts options for serializing, including optional prefix, suffix, and signingFieldOnly
|
||||
* @returns A Buffer containing the serialized object
|
||||
*/
|
||||
function serializeObject(object: JsonObject, opts: OptionObject = {}): Buffer {
|
||||
const { prefix, suffix, signingFieldsOnly = false } = opts
|
||||
const bytesList = new BytesList()
|
||||
|
||||
if (prefix) {
|
||||
bytesList.put(prefix)
|
||||
}
|
||||
|
||||
const filter = signingFieldsOnly
|
||||
? (f: FieldInstance): boolean => f.isSigningField
|
||||
: undefined
|
||||
coreTypes.STObject.from(object, filter).toBytesSink(bytesList)
|
||||
|
||||
if (suffix) {
|
||||
bytesList.put(suffix)
|
||||
}
|
||||
|
||||
return bytesList.toBytes()
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an object for signing
|
||||
*
|
||||
* @param transaction Transaction to serialize
|
||||
* @param prefix Prefix bytes to put before the serialized object
|
||||
* @returns A Buffer with the serialized object
|
||||
*/
|
||||
function signingData(
|
||||
transaction: JsonObject,
|
||||
prefix: Buffer = HashPrefix.transactionSig,
|
||||
): Buffer {
|
||||
return serializeObject(transaction, { prefix, signingFieldsOnly: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing fields required for a Claim
|
||||
*/
|
||||
interface ClaimObject extends JsonObject {
|
||||
channel: string
|
||||
amount: string | number
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a signingClaim
|
||||
*
|
||||
* @param claim A claim object to serialize
|
||||
* @returns the serialized object with appropriate prefix
|
||||
*/
|
||||
function signingClaimData(claim: ClaimObject): Buffer {
|
||||
const num = bigInt(String(claim.amount))
|
||||
const prefix = HashPrefix.paymentChannelClaim
|
||||
const channel = coreTypes.Hash256.from(claim.channel).toBytes()
|
||||
const amount = coreTypes.UInt64.from(num).toBytes()
|
||||
|
||||
const bytesList = new BytesList()
|
||||
|
||||
bytesList.put(prefix)
|
||||
bytesList.put(channel)
|
||||
bytesList.put(amount)
|
||||
return bytesList.toBytes()
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a transaction object for multiSigning
|
||||
*
|
||||
* @param transaction transaction to serialize
|
||||
* @param signingAccount Account to sign the transaction with
|
||||
* @returns serialized transaction with appropriate prefix and suffix
|
||||
*/
|
||||
function multiSigningData(
|
||||
transaction: JsonObject,
|
||||
signingAccount: string | AccountID,
|
||||
): Buffer {
|
||||
const prefix = HashPrefix.transactionMultiSig
|
||||
const suffix = coreTypes.AccountID.from(signingAccount).toBytes()
|
||||
return serializeObject(transaction, {
|
||||
prefix,
|
||||
suffix,
|
||||
signingFieldsOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
export {
|
||||
BinaryParser,
|
||||
BinarySerializer,
|
||||
BytesList,
|
||||
ClaimObject,
|
||||
makeParser,
|
||||
serializeObject,
|
||||
readJSON,
|
||||
multiSigningData,
|
||||
signingData,
|
||||
signingClaimData,
|
||||
binaryToJSON,
|
||||
sha512Half,
|
||||
transactionID,
|
||||
}
|
||||
29
packages/ripple-binary-codec/src/coretypes.ts
Normal file
29
packages/ripple-binary-codec/src/coretypes.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
Field,
|
||||
TransactionType,
|
||||
LedgerEntryType,
|
||||
Type,
|
||||
TransactionResult,
|
||||
} from './enums'
|
||||
import * as types from './types'
|
||||
import * as binary from './binary'
|
||||
import { ShaMap } from './shamap'
|
||||
import * as ledgerHashes from './ledger-hashes'
|
||||
import * as hashes from './hashes'
|
||||
import { quality } from './quality'
|
||||
import { HashPrefix } from './hash-prefixes'
|
||||
|
||||
export {
|
||||
hashes,
|
||||
binary,
|
||||
ledgerHashes,
|
||||
Field,
|
||||
TransactionType,
|
||||
LedgerEntryType,
|
||||
Type,
|
||||
TransactionResult,
|
||||
quality,
|
||||
HashPrefix,
|
||||
ShaMap,
|
||||
types,
|
||||
}
|
||||
60
packages/ripple-binary-codec/src/enums/README.md
Normal file
60
packages/ripple-binary-codec/src/enums/README.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Definitions
|
||||
|
||||
## Types
|
||||
|
||||
These are the [types](https://xrpl.org/serialization.html#type-list) associated with a given Serialization Field. Each type has an arbitrary [type_code](https://xrpl.org/serialization.html#type-codes), with lower codes sorting first.
|
||||
|
||||
## Ledger Entry Types
|
||||
|
||||
Each ledger's state tree contain [ledger objects](https://xrpl.org/ledger-object-types.html), which represent all settings, balances, and relationships in the shared ledger.
|
||||
|
||||
## Fields
|
||||
|
||||
These are Serialization Fields (`sf`) [defined in rippled's SField.cpp](https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/impl/SField.cpp). Fields with undefined values are omitted before encoding.
|
||||
|
||||
### Key
|
||||
|
||||
The key is the string defined in the rippled source code, such as "LedgerEntry", "Transaction", etc.
|
||||
|
||||
### nth
|
||||
|
||||
`nth` is the sort code, meaning "nth of type." It is is combined with the type code in order to construct the Field ID of this field. The Field ID is only used for sorting the fields. Since there are multiple fields with the same data type, the `nth` is used to deterministically order each field among other fields of the same data type.
|
||||
|
||||
Each field has a Field ID, which is used to sort fields that have the same type as one another with lower codes sorting first.
|
||||
|
||||
- [Field definitions](https://github.com/ripple/rippled/blob/72e6005f562a8f0818bc94803d222ac9345e1e40/src/ripple/protocol/impl/SField.cpp#L72-L266)
|
||||
- [Constructing the `SField` field codes](https://github.com/ripple/rippled/blob/eaff9a0e6aec0ad077f118501791c7684debcfd5/src/ripple/protocol/SField.h#L95-L98)
|
||||
|
||||
For example, the `Account` field has sort code (nth) `1`, so it comes before the `Destination` field which has sort code `3`.
|
||||
|
||||
Sort code numbers are reused for fields of different types, but different fields of the same type never have the same sort code. When you combine the type code with the sort code, you get the field's unique _Field ID_.
|
||||
|
||||
The unique [Field ID](https://xrpl.org/serialization.html#field-ids) is prefixed before the field in the final serialized blob. The size of the Field ID is one to three bytes depending on the type code and the field codes it combines.
|
||||
|
||||
### isVLEncoded
|
||||
|
||||
If true, the field is Variable Length encoded and [length-prefixed](https://xrpl.org/serialization.html#length-prefixing). The variable-length encoded fields are `STI_VL`/`Blob`, `STI_ACCOUNT`/`AccountID`, and `STI_VECTOR256`/`Vector256`.
|
||||
|
||||
### isSerialized
|
||||
|
||||
Fields are serialized if they are not [one of these](https://github.com/ripple/rippled/blob/eaff9a0e6aec0ad077f118501791c7684debcfd5/src/ripple/protocol/impl/SField.cpp#L71-L78) or if they are not an SField.
|
||||
|
||||
- https://github.com/ripple/ripple-binary-codec/blob/14e76e68ead7e4bcd83c942dbdc9064d5a66869b/src/enums/definitions.json#L832
|
||||
- https://github.com/ripple/rippled/search?utf8=%E2%9C%93&q=taker_gets_funded&type=
|
||||
|
||||
### isSigningField
|
||||
|
||||
True unless the field is [specified with `SField::notSigning`](https://github.com/ripple/rippled/blob/eaff9a0e6aec0ad077f118501791c7684debcfd5/src/ripple/protocol/impl/SField.cpp#L198).
|
||||
|
||||
## Transaction Results
|
||||
|
||||
See:
|
||||
|
||||
- https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/TER.h
|
||||
- https://xrpl.org/transaction-results.html
|
||||
|
||||
TODO: Write a script to read rippled's source file and generate the necessary mapping.
|
||||
|
||||
## Transaction Types
|
||||
|
||||
See https://github.com/ripple/rippled/blob/develop/src/ripple/protocol/TxFormats.h
|
||||
1958
packages/ripple-binary-codec/src/enums/definitions.json
Normal file
1958
packages/ripple-binary-codec/src/enums/definitions.json
Normal file
File diff suppressed because it is too large
Load Diff
156
packages/ripple-binary-codec/src/enums/index.ts
Normal file
156
packages/ripple-binary-codec/src/enums/index.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import * as enums from './definitions.json'
|
||||
import { SerializedType } from '../types/serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
const TYPE_WIDTH = 2
|
||||
const LEDGER_ENTRY_WIDTH = 2
|
||||
const TRANSACTION_TYPE_WIDTH = 2
|
||||
const TRANSACTION_RESULT_WIDTH = 1
|
||||
|
||||
/*
|
||||
* @brief: Serialize a field based on type_code and Field.nth
|
||||
*/
|
||||
function fieldHeader(type: number, nth: number): Buffer {
|
||||
const header: Array<number> = []
|
||||
if (type < 16) {
|
||||
if (nth < 16) {
|
||||
header.push((type << 4) | nth)
|
||||
} else {
|
||||
header.push(type << 4, nth)
|
||||
}
|
||||
} else if (nth < 16) {
|
||||
header.push(nth, type)
|
||||
} else {
|
||||
header.push(0, type, nth)
|
||||
}
|
||||
return Buffer.from(header)
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief: Bytes, name, and ordinal representing one type, ledger_type, transaction type, or result
|
||||
*/
|
||||
class Bytes {
|
||||
readonly bytes: Uint8Array
|
||||
|
||||
constructor(
|
||||
readonly name: string,
|
||||
readonly ordinal: number,
|
||||
readonly ordinalWidth: number,
|
||||
) {
|
||||
this.bytes = Buffer.alloc(ordinalWidth)
|
||||
for (let i = 0; i < ordinalWidth; i++) {
|
||||
this.bytes[ordinalWidth - i - 1] = (ordinal >>> (i * 8)) & 0xff
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): string {
|
||||
return this.name
|
||||
}
|
||||
|
||||
toBytesSink(sink): void {
|
||||
sink.put(this.bytes)
|
||||
}
|
||||
|
||||
toBytes(): Uint8Array {
|
||||
return this.bytes
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief: Collection of Bytes objects, mapping bidirectionally
|
||||
*/
|
||||
class BytesLookup {
|
||||
constructor(types: Record<string, number>, readonly ordinalWidth: number) {
|
||||
Object.entries(types).forEach(([k, v]) => {
|
||||
this[k] = new Bytes(k, v, ordinalWidth)
|
||||
this[v.toString()] = this[k]
|
||||
})
|
||||
}
|
||||
|
||||
from(value: Bytes | string): Bytes {
|
||||
return value instanceof Bytes ? value : (this[value] as Bytes)
|
||||
}
|
||||
|
||||
fromParser(parser): Bytes {
|
||||
return this.from(parser.readUIntN(this.ordinalWidth).toString())
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* type FieldInfo is the type of the objects containing information about each field in definitions.json
|
||||
*/
|
||||
interface FieldInfo {
|
||||
nth: number
|
||||
isVLEncoded: boolean
|
||||
isSerialized: boolean
|
||||
isSigningField: boolean
|
||||
type: string
|
||||
}
|
||||
|
||||
interface FieldInstance {
|
||||
readonly nth: number
|
||||
readonly isVariableLengthEncoded: boolean
|
||||
readonly isSerialized: boolean
|
||||
readonly isSigningField: boolean
|
||||
readonly type: Bytes
|
||||
readonly ordinal: number
|
||||
readonly name: string
|
||||
readonly header: Buffer
|
||||
readonly associatedType: typeof SerializedType
|
||||
}
|
||||
|
||||
function buildField([name, info]: [string, FieldInfo]): FieldInstance {
|
||||
const typeOrdinal = enums.TYPES[info.type]
|
||||
const field = fieldHeader(typeOrdinal, info.nth)
|
||||
return {
|
||||
name: name,
|
||||
nth: info.nth,
|
||||
isVariableLengthEncoded: info.isVLEncoded,
|
||||
isSerialized: info.isSerialized,
|
||||
isSigningField: info.isSigningField,
|
||||
ordinal: (typeOrdinal << 16) | info.nth,
|
||||
type: new Bytes(info.type, typeOrdinal, TYPE_WIDTH),
|
||||
header: field,
|
||||
associatedType: SerializedType, // For later assignment in ./types/index.js
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @brief: The collection of all fields as defined in definitions.json
|
||||
*/
|
||||
class FieldLookup {
|
||||
constructor(fields: Array<[string, FieldInfo]>) {
|
||||
fields.forEach(([k, v]) => {
|
||||
this[k] = buildField([k, v])
|
||||
this[this[k].ordinal.toString()] = this[k]
|
||||
})
|
||||
}
|
||||
|
||||
fromString(value: string): FieldInstance {
|
||||
return this[value] as FieldInstance
|
||||
}
|
||||
}
|
||||
|
||||
const Type = new BytesLookup(enums.TYPES, TYPE_WIDTH)
|
||||
const LedgerEntryType = new BytesLookup(
|
||||
enums.LEDGER_ENTRY_TYPES,
|
||||
LEDGER_ENTRY_WIDTH,
|
||||
)
|
||||
const TransactionType = new BytesLookup(
|
||||
enums.TRANSACTION_TYPES,
|
||||
TRANSACTION_TYPE_WIDTH,
|
||||
)
|
||||
const TransactionResult = new BytesLookup(
|
||||
enums.TRANSACTION_RESULTS,
|
||||
TRANSACTION_RESULT_WIDTH,
|
||||
)
|
||||
const Field = new FieldLookup(enums.FIELDS as Array<[string, FieldInfo]>)
|
||||
|
||||
export {
|
||||
Field,
|
||||
FieldInstance,
|
||||
Type,
|
||||
LedgerEntryType,
|
||||
TransactionResult,
|
||||
TransactionType,
|
||||
}
|
||||
134
packages/ripple-binary-codec/src/enums/utils-renumber.ts
Normal file
134
packages/ripple-binary-codec/src/enums/utils-renumber.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Quick script to re-number values
|
||||
*/
|
||||
|
||||
const input = {
|
||||
temBAD_SEND_XRP_PATHS: -283,
|
||||
temBAD_SEQUENCE: -282,
|
||||
temBAD_SIGNATURE: -281,
|
||||
temBAD_SRC_ACCOUNT: -280,
|
||||
temBAD_TRANSFER_RATE: -279,
|
||||
temDST_IS_SRC: -278,
|
||||
temDST_NEEDED: -277,
|
||||
temINVALID: -276,
|
||||
temINVALID_FLAG: -275,
|
||||
temREDUNDANT: -274,
|
||||
temRIPPLE_EMPTY: -273,
|
||||
temDISABLED: -272,
|
||||
temBAD_SIGNER: -271,
|
||||
temBAD_QUORUM: -270,
|
||||
temBAD_WEIGHT: -269,
|
||||
temBAD_TICK_SIZE: -268,
|
||||
temINVALID_ACCOUNT_ID: -267,
|
||||
temCANNOT_PREAUTH_SELF: -266,
|
||||
|
||||
temUNCERTAIN: -265,
|
||||
temUNKNOWN: -264,
|
||||
|
||||
tefFAILURE: -199,
|
||||
tefALREADY: -198,
|
||||
tefBAD_ADD_AUTH: -197,
|
||||
tefBAD_AUTH: -196,
|
||||
tefBAD_LEDGER: -195,
|
||||
tefCREATED: -194,
|
||||
tefEXCEPTION: -193,
|
||||
tefINTERNAL: -192,
|
||||
tefNO_AUTH_REQUIRED: -191,
|
||||
tefPAST_SEQ: -190,
|
||||
tefWRONG_PRIOR: -189,
|
||||
tefMASTER_DISABLED: -188,
|
||||
tefMAX_LEDGER: -187,
|
||||
tefBAD_SIGNATURE: -186,
|
||||
tefBAD_QUORUM: -185,
|
||||
tefNOT_MULTI_SIGNING: -184,
|
||||
tefBAD_AUTH_MASTER: -183,
|
||||
tefINVARIANT_FAILED: -182,
|
||||
tefTOO_BIG: -181,
|
||||
|
||||
terRETRY: -99,
|
||||
terFUNDS_SPENT: -98,
|
||||
terINSUF_FEE_B: -97,
|
||||
terNO_ACCOUNT: -96,
|
||||
terNO_AUTH: -95,
|
||||
terNO_LINE: -94,
|
||||
terOWNERS: -93,
|
||||
terPRE_SEQ: -92,
|
||||
terLAST: -91,
|
||||
terNO_RIPPLE: -90,
|
||||
terQUEUED: -89,
|
||||
|
||||
tesSUCCESS: 0,
|
||||
|
||||
tecCLAIM: 100,
|
||||
tecPATH_PARTIAL: 101,
|
||||
tecUNFUNDED_ADD: 102,
|
||||
tecUNFUNDED_OFFER: 103,
|
||||
tecUNFUNDED_PAYMENT: 104,
|
||||
tecFAILED_PROCESSING: 105,
|
||||
tecDIR_FULL: 121,
|
||||
tecINSUF_RESERVE_LINE: 122,
|
||||
tecINSUF_RESERVE_OFFER: 123,
|
||||
tecNO_DST: 124,
|
||||
tecNO_DST_INSUF_XRP: 125,
|
||||
tecNO_LINE_INSUF_RESERVE: 126,
|
||||
tecNO_LINE_REDUNDANT: 127,
|
||||
tecPATH_DRY: 128,
|
||||
tecUNFUNDED: 129,
|
||||
tecNO_ALTERNATIVE_KEY: 130,
|
||||
tecNO_REGULAR_KEY: 131,
|
||||
tecOWNERS: 132,
|
||||
tecNO_ISSUER: 133,
|
||||
tecNO_AUTH: 134,
|
||||
tecNO_LINE: 135,
|
||||
tecINSUFF_FEE: 136,
|
||||
tecFROZEN: 137,
|
||||
tecNO_TARGET: 138,
|
||||
tecNO_PERMISSION: 139,
|
||||
tecNO_ENTRY: 140,
|
||||
tecINSUFFICIENT_RESERVE: 141,
|
||||
tecNEED_MASTER_KEY: 142,
|
||||
tecDST_TAG_NEEDED: 143,
|
||||
tecINTERNAL: 144,
|
||||
tecOVERSIZE: 145,
|
||||
tecCRYPTOCONDITION_ERROR: 146,
|
||||
tecINVARIANT_FAILED: 147,
|
||||
tecEXPIRED: 148,
|
||||
tecDUPLICATE: 149,
|
||||
tecKILLED: 150,
|
||||
tecHAS_OBLIGATIONS: 151,
|
||||
tecTOO_SOON: 152,
|
||||
}
|
||||
|
||||
let startingFromTemBADSENDXRPPATHS = -284
|
||||
|
||||
let startingFromTefFAILURE = -199
|
||||
|
||||
let startingFromTerRETRY = -99
|
||||
|
||||
const tesSUCCESS = 0
|
||||
|
||||
let startingFromTecCLAIM = 100
|
||||
|
||||
const startingFromTecDIRFULL = 121
|
||||
|
||||
let previousKey = 'tem'
|
||||
Object.keys(input).forEach((key) => {
|
||||
if (key.substring(0, 3) !== previousKey.substring(0, 3)) {
|
||||
console.log()
|
||||
previousKey = key
|
||||
}
|
||||
if (key.substring(0, 3) === 'tem') {
|
||||
console.log(` "${key}": ${startingFromTemBADSENDXRPPATHS++},`)
|
||||
} else if (key.substring(0, 3) === 'tef') {
|
||||
console.log(` "${key}": ${startingFromTefFAILURE++},`)
|
||||
} else if (key.substring(0, 3) === 'ter') {
|
||||
console.log(` "${key}": ${startingFromTerRETRY++},`)
|
||||
} else if (key.substring(0, 3) === 'tes') {
|
||||
console.log(` "${key}": ${tesSUCCESS},`)
|
||||
} else if (key.substring(0, 3) === 'tec') {
|
||||
if (key === 'tecDIR_FULL') {
|
||||
startingFromTecCLAIM = startingFromTecDIRFULL
|
||||
}
|
||||
console.log(` "${key}": ${startingFromTecCLAIM++},`)
|
||||
}
|
||||
})
|
||||
40
packages/ripple-binary-codec/src/hash-prefixes.ts
Normal file
40
packages/ripple-binary-codec/src/hash-prefixes.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Write a 32 bit integer to a Buffer
|
||||
*
|
||||
* @param uint32 32 bit integer to write to buffer
|
||||
* @returns a buffer with the bytes representation of uint32
|
||||
*/
|
||||
function bytes(uint32: number): Buffer {
|
||||
const result = Buffer.alloc(4)
|
||||
result.writeUInt32BE(uint32, 0)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps HashPrefix names to their byte representation
|
||||
*/
|
||||
const HashPrefix: Record<string, Buffer> = {
|
||||
transactionID: bytes(0x54584e00),
|
||||
// transaction plus metadata
|
||||
transaction: bytes(0x534e4400),
|
||||
// account state
|
||||
accountStateEntry: bytes(0x4d4c4e00),
|
||||
// inner node in tree
|
||||
innerNode: bytes(0x4d494e00),
|
||||
// ledger master data for signing
|
||||
ledgerHeader: bytes(0x4c575200),
|
||||
// inner transaction to sign
|
||||
transactionSig: bytes(0x53545800),
|
||||
// inner transaction to sign
|
||||
transactionMultiSig: bytes(0x534d5400),
|
||||
// validation for signing
|
||||
validation: bytes(0x56414c00),
|
||||
// proposal for signing
|
||||
proposal: bytes(0x50525000),
|
||||
// payment channel claim
|
||||
paymentChannelClaim: bytes(0x434c4d00),
|
||||
}
|
||||
|
||||
export { HashPrefix }
|
||||
77
packages/ripple-binary-codec/src/hashes.ts
Normal file
77
packages/ripple-binary-codec/src/hashes.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { HashPrefix } from './hash-prefixes'
|
||||
import * as createHash from 'create-hash'
|
||||
import { Hash256 } from './types/hash-256'
|
||||
import { BytesList } from './serdes/binary-serializer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Class for hashing with SHA512
|
||||
* @extends BytesList So SerializedTypes can write bytes to a Sha512Half
|
||||
*/
|
||||
class Sha512Half extends BytesList {
|
||||
private hash: createHash = createHash('sha512')
|
||||
|
||||
/**
|
||||
* Construct a new Sha512Hash and write bytes this.hash
|
||||
*
|
||||
* @param bytes bytes to write to this.hash
|
||||
* @returns the new Sha512Hash object
|
||||
*/
|
||||
static put(bytes: Buffer): Sha512Half {
|
||||
return new Sha512Half().put(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write bytes to an existing Sha512Hash
|
||||
*
|
||||
* @param bytes bytes to write to object
|
||||
* @returns the Sha512 object
|
||||
*/
|
||||
put(bytes: Buffer): Sha512Half {
|
||||
this.hash.update(bytes)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute SHA512 hash and slice in half
|
||||
*
|
||||
* @returns half of a SHA512 hash
|
||||
*/
|
||||
finish256(): Buffer {
|
||||
const bytes: Buffer = this.hash.digest()
|
||||
return bytes.slice(0, 32)
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Hash256 from the Sha512Half object
|
||||
*
|
||||
* @returns a Hash256 object
|
||||
*/
|
||||
finish(): Hash256 {
|
||||
return new Hash256(this.finish256())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* compute SHA512 hash of a list of bytes
|
||||
*
|
||||
* @param args zero or more arguments to hash
|
||||
* @returns the sha512half hash of the arguments.
|
||||
*/
|
||||
function sha512Half(...args: Buffer[]): Buffer {
|
||||
const hash = new Sha512Half()
|
||||
args.forEach((a) => hash.put(a))
|
||||
return hash.finish256()
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a transactionID from a Serialized Transaction
|
||||
*
|
||||
* @param serialized bytes to hash
|
||||
* @returns a Hash256 object
|
||||
*/
|
||||
function transactionID(serialized: Buffer): Hash256 {
|
||||
return new Hash256(sha512Half(HashPrefix.transactionID, serialized))
|
||||
}
|
||||
|
||||
export { Sha512Half, sha512Half, transactionID }
|
||||
112
packages/ripple-binary-codec/src/index.ts
Normal file
112
packages/ripple-binary-codec/src/index.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import * as assert from 'assert'
|
||||
import { quality, binary } from './coretypes'
|
||||
import { decodeLedgerData } from './ledger-hashes'
|
||||
import { ClaimObject } from './binary'
|
||||
import { JsonObject } from './types/serialized-type'
|
||||
const {
|
||||
signingData,
|
||||
signingClaimData,
|
||||
multiSigningData,
|
||||
binaryToJSON,
|
||||
serializeObject,
|
||||
} = binary
|
||||
|
||||
/**
|
||||
* Decode a transaction
|
||||
*
|
||||
* @param binary hex-string of the encoded transaction
|
||||
* @returns the JSON representation of the transaction
|
||||
*/
|
||||
function decode(binary: string): JsonObject {
|
||||
assert.ok(typeof binary === 'string', 'binary must be a hex string')
|
||||
return binaryToJSON(binary)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a transaction
|
||||
*
|
||||
* @param json The JSON representation of a transaction
|
||||
* @returns A hex-string of the encoded transaction
|
||||
*/
|
||||
function encode(json: object): string {
|
||||
assert.ok(typeof json === 'object')
|
||||
return serializeObject(json as JsonObject)
|
||||
.toString('hex')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a transaction and prepare for signing
|
||||
*
|
||||
* @param json JSON object representing the transaction
|
||||
* @param signer string representing the account to sign the transaction with
|
||||
* @returns a hex string of the encoded transaction
|
||||
*/
|
||||
function encodeForSigning(json: object): string {
|
||||
assert.ok(typeof json === 'object')
|
||||
return signingData(json as JsonObject)
|
||||
.toString('hex')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a transaction and prepare for signing with a claim
|
||||
*
|
||||
* @param json JSON object representing the transaction
|
||||
* @param signer string representing the account to sign the transaction with
|
||||
* @returns a hex string of the encoded transaction
|
||||
*/
|
||||
function encodeForSigningClaim(json: object): string {
|
||||
assert.ok(typeof json === 'object')
|
||||
return signingClaimData(json as ClaimObject)
|
||||
.toString('hex')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a transaction and prepare for multi-signing
|
||||
*
|
||||
* @param json JSON object representing the transaction
|
||||
* @param signer string representing the account to sign the transaction with
|
||||
* @returns a hex string of the encoded transaction
|
||||
*/
|
||||
function encodeForMultisigning(json: object, signer: string): string {
|
||||
assert.ok(typeof json === 'object')
|
||||
assert.equal(json['SigningPubKey'], '')
|
||||
return multiSigningData(json as JsonObject, signer)
|
||||
.toString('hex')
|
||||
.toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a quality value
|
||||
*
|
||||
* @param value string representation of a number
|
||||
* @returns a hex-string representing the quality
|
||||
*/
|
||||
function encodeQuality(value: string): string {
|
||||
assert.ok(typeof value === 'string')
|
||||
return quality.encode(value).toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a quality value
|
||||
*
|
||||
* @param value hex-string of a quality
|
||||
* @returns a string representing the quality
|
||||
*/
|
||||
function decodeQuality(value: string): string {
|
||||
assert.ok(typeof value === 'string')
|
||||
return quality.decode(value).toString()
|
||||
}
|
||||
|
||||
export = {
|
||||
decode,
|
||||
encode,
|
||||
encodeForSigning,
|
||||
encodeForSigningClaim,
|
||||
encodeForMultisigning,
|
||||
encodeQuality,
|
||||
decodeQuality,
|
||||
decodeLedgerData,
|
||||
}
|
||||
181
packages/ripple-binary-codec/src/ledger-hashes.ts
Normal file
181
packages/ripple-binary-codec/src/ledger-hashes.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import * as assert from 'assert'
|
||||
import { ShaMap, ShaMapNode, ShaMapLeaf } from './shamap'
|
||||
import { HashPrefix } from './hash-prefixes'
|
||||
import { Sha512Half } from './hashes'
|
||||
import { BinarySerializer, serializeObject } from './binary'
|
||||
import { Hash256 } from './types/hash-256'
|
||||
import { STObject } from './types/st-object'
|
||||
import { UInt64 } from './types/uint-64'
|
||||
import { UInt32 } from './types/uint-32'
|
||||
import { UInt8 } from './types/uint-8'
|
||||
import { BinaryParser } from './serdes/binary-parser'
|
||||
import { JsonObject } from './types/serialized-type'
|
||||
import * as bigInt from 'big-integer'
|
||||
|
||||
/**
|
||||
* Computes the hash of a list of objects
|
||||
*
|
||||
* @param itemizer Converts an item into a format that can be added to SHAMap
|
||||
* @param itemsJson Array of items to add to a SHAMap
|
||||
* @returns the hash of the SHAMap
|
||||
*/
|
||||
function computeHash(
|
||||
itemizer: (item: JsonObject) => [Hash256?, ShaMapNode?, ShaMapLeaf?],
|
||||
itemsJson: Array<JsonObject>,
|
||||
): Hash256 {
|
||||
const map = new ShaMap()
|
||||
itemsJson.forEach((item) => map.addItem(...itemizer(item)))
|
||||
return map.hash()
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing a transaction item
|
||||
*/
|
||||
interface transactionItemObject extends JsonObject {
|
||||
hash: string
|
||||
metaData: JsonObject
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a transaction into an index and an item
|
||||
*
|
||||
* @param json transaction with metadata
|
||||
* @returns a tuple of index and item to be added to SHAMap
|
||||
*/
|
||||
function transactionItemizer(
|
||||
json: transactionItemObject,
|
||||
): [Hash256, ShaMapNode, undefined] {
|
||||
assert.ok(json.hash)
|
||||
const index = Hash256.from(json.hash)
|
||||
const item = {
|
||||
hashPrefix() {
|
||||
return HashPrefix.transaction
|
||||
},
|
||||
toBytesSink(sink) {
|
||||
const serializer = new BinarySerializer(sink)
|
||||
serializer.writeLengthEncoded(STObject.from(json))
|
||||
serializer.writeLengthEncoded(STObject.from(json.metaData))
|
||||
},
|
||||
} as ShaMapNode
|
||||
return [index, item, undefined]
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing an entry item
|
||||
*/
|
||||
interface entryItemObject extends JsonObject {
|
||||
index: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an entry to a pair Hash256 and ShaMapNode
|
||||
*
|
||||
* @param json JSON describing a ledger entry item
|
||||
* @returns a tuple of index and item to be added to SHAMap
|
||||
*/
|
||||
function entryItemizer(
|
||||
json: entryItemObject,
|
||||
): [Hash256, ShaMapNode, undefined] {
|
||||
const index = Hash256.from(json.index)
|
||||
const bytes = serializeObject(json)
|
||||
const item = {
|
||||
hashPrefix() {
|
||||
return HashPrefix.accountStateEntry
|
||||
},
|
||||
toBytesSink(sink) {
|
||||
sink.put(bytes)
|
||||
},
|
||||
} as ShaMapNode
|
||||
return [index, item, undefined]
|
||||
}
|
||||
|
||||
/**
|
||||
* Function computing the hash of a transaction tree
|
||||
*
|
||||
* @param param An array of transaction objects to hash
|
||||
* @returns A Hash256 object
|
||||
*/
|
||||
function transactionTreeHash(param: Array<JsonObject>): Hash256 {
|
||||
const itemizer = transactionItemizer as (
|
||||
json: JsonObject,
|
||||
) => [Hash256, ShaMapNode, undefined]
|
||||
return computeHash(itemizer, param)
|
||||
}
|
||||
|
||||
/**
|
||||
* Function computing the hash of accountState
|
||||
*
|
||||
* @param param A list of accountStates hash
|
||||
* @returns A Hash256 object
|
||||
*/
|
||||
function accountStateHash(param: Array<JsonObject>): Hash256 {
|
||||
const itemizer = entryItemizer as (
|
||||
json: JsonObject,
|
||||
) => [Hash256, ShaMapNode, undefined]
|
||||
return computeHash(itemizer, param)
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface describing a ledger header
|
||||
*/
|
||||
interface ledgerObject {
|
||||
ledger_index: number
|
||||
total_coins: string | number | bigInt.BigInteger
|
||||
parent_hash: string
|
||||
transaction_hash: string
|
||||
account_hash: string
|
||||
parent_close_time: number
|
||||
close_time: number
|
||||
close_time_resolution: number
|
||||
close_flags: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and hash a ledger header
|
||||
*
|
||||
* @param header a ledger header
|
||||
* @returns the hash of header
|
||||
*/
|
||||
function ledgerHash(header: ledgerObject): Hash256 {
|
||||
const hash = new Sha512Half()
|
||||
hash.put(HashPrefix.ledgerHeader)
|
||||
assert.ok(header.parent_close_time !== undefined)
|
||||
assert.ok(header.close_flags !== undefined)
|
||||
|
||||
UInt32.from<number>(header.ledger_index).toBytesSink(hash)
|
||||
UInt64.from<bigInt.BigInteger>(
|
||||
bigInt(String(header.total_coins)),
|
||||
).toBytesSink(hash)
|
||||
Hash256.from<string>(header.parent_hash).toBytesSink(hash)
|
||||
Hash256.from<string>(header.transaction_hash).toBytesSink(hash)
|
||||
Hash256.from<string>(header.account_hash).toBytesSink(hash)
|
||||
UInt32.from<number>(header.parent_close_time).toBytesSink(hash)
|
||||
UInt32.from<number>(header.close_time).toBytesSink(hash)
|
||||
UInt8.from<number>(header.close_time_resolution).toBytesSink(hash)
|
||||
UInt8.from<number>(header.close_flags).toBytesSink(hash)
|
||||
return hash.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a serialized ledger header
|
||||
*
|
||||
* @param binary A serialized ledger header
|
||||
* @returns A JSON object describing a ledger header
|
||||
*/
|
||||
function decodeLedgerData(binary: string): object {
|
||||
assert.ok(typeof binary === 'string', 'binary must be a hex string')
|
||||
const parser = new BinaryParser(binary)
|
||||
return {
|
||||
ledger_index: parser.readUInt32(),
|
||||
total_coins: parser.readType(UInt64).valueOf().toString(),
|
||||
parent_hash: parser.readType(Hash256).toHex(),
|
||||
transaction_hash: parser.readType(Hash256).toHex(),
|
||||
account_hash: parser.readType(Hash256).toHex(),
|
||||
parent_close_time: parser.readUInt32(),
|
||||
close_time: parser.readUInt32(),
|
||||
close_time_resolution: parser.readUInt8(),
|
||||
close_flags: parser.readUInt8(),
|
||||
}
|
||||
}
|
||||
|
||||
export { accountStateHash, transactionTreeHash, ledgerHash, decodeLedgerData }
|
||||
39
packages/ripple-binary-codec/src/quality.ts
Normal file
39
packages/ripple-binary-codec/src/quality.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { coreTypes } from './types'
|
||||
import { Decimal } from 'decimal.js'
|
||||
import * as bigInt from 'big-integer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* class for encoding and decoding quality
|
||||
*/
|
||||
class quality {
|
||||
/**
|
||||
* Encode quality amount
|
||||
*
|
||||
* @param arg string representation of an amount
|
||||
* @returns Serialized quality
|
||||
*/
|
||||
static encode(quality: string): Buffer {
|
||||
const decimal = new Decimal(quality)
|
||||
const exponent = decimal.e - 15
|
||||
const qualityString = decimal.times(`1e${-exponent}`).abs().toString()
|
||||
const bytes = coreTypes.UInt64.from(bigInt(qualityString)).toBytes()
|
||||
bytes[0] = exponent + 100
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode quality amount
|
||||
*
|
||||
* @param arg hex-string denoting serialized quality
|
||||
* @returns deserialized quality
|
||||
*/
|
||||
static decode(quality: string): Decimal {
|
||||
const bytes = Buffer.from(quality, 'hex').slice(-8)
|
||||
const exponent = bytes[0] - 100
|
||||
const mantissa = new Decimal(`0x${bytes.slice(1).toString('hex')}`)
|
||||
return mantissa.times(`1e${exponent}`)
|
||||
}
|
||||
}
|
||||
|
||||
export { quality }
|
||||
206
packages/ripple-binary-codec/src/serdes/binary-parser.ts
Normal file
206
packages/ripple-binary-codec/src/serdes/binary-parser.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import * as assert from 'assert'
|
||||
import { Field, FieldInstance } from '../enums'
|
||||
import { SerializedType } from '../types/serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* BinaryParser is used to compute fields and values from a HexString
|
||||
*/
|
||||
class BinaryParser {
|
||||
private bytes: Buffer
|
||||
|
||||
/**
|
||||
* Initialize bytes to a hex string
|
||||
*
|
||||
* @param hexBytes a hex string
|
||||
*/
|
||||
constructor(hexBytes: string) {
|
||||
this.bytes = Buffer.from(hexBytes, 'hex')
|
||||
}
|
||||
|
||||
/**
|
||||
* Peek the first byte of the BinaryParser
|
||||
*
|
||||
* @returns The first byte of the BinaryParser
|
||||
*/
|
||||
peek(): number {
|
||||
assert.ok(this.bytes.byteLength !== 0)
|
||||
return this.bytes[0]
|
||||
}
|
||||
|
||||
/**
|
||||
* Consume the first n bytes of the BinaryParser
|
||||
*
|
||||
* @param n the number of bytes to skip
|
||||
*/
|
||||
skip(n: number): void {
|
||||
assert.ok(n <= this.bytes.byteLength)
|
||||
this.bytes = this.bytes.slice(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* read the first n bytes from the BinaryParser
|
||||
*
|
||||
* @param n The number of bytes to read
|
||||
* @return The bytes
|
||||
*/
|
||||
read(n: number): Buffer {
|
||||
assert.ok(n <= this.bytes.byteLength)
|
||||
|
||||
const slice = this.bytes.slice(0, n)
|
||||
this.skip(n)
|
||||
return slice
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an integer of given size
|
||||
*
|
||||
* @param n The number of bytes to read
|
||||
* @return The number represented by those bytes
|
||||
*/
|
||||
readUIntN(n: number): number {
|
||||
assert.ok(0 < n && n <= 4, 'invalid n')
|
||||
return this.read(n).reduce((a, b) => (a << 8) | b) >>> 0
|
||||
}
|
||||
|
||||
readUInt8(): number {
|
||||
return this.readUIntN(1)
|
||||
}
|
||||
|
||||
readUInt16(): number {
|
||||
return this.readUIntN(2)
|
||||
}
|
||||
|
||||
readUInt32(): number {
|
||||
return this.readUIntN(4)
|
||||
}
|
||||
|
||||
size(): number {
|
||||
return this.bytes.byteLength
|
||||
}
|
||||
|
||||
end(customEnd?: number): boolean {
|
||||
const length = this.bytes.byteLength
|
||||
return length === 0 || (customEnd !== undefined && length <= customEnd)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads variable length encoded bytes
|
||||
*
|
||||
* @return The variable length bytes
|
||||
*/
|
||||
readVariableLength(): Buffer {
|
||||
return this.read(this.readVariableLengthLength())
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the length of the variable length encoded bytes
|
||||
*
|
||||
* @return The length of the variable length encoded bytes
|
||||
*/
|
||||
readVariableLengthLength(): number {
|
||||
const b1 = this.readUInt8()
|
||||
if (b1 <= 192) {
|
||||
return b1
|
||||
} else if (b1 <= 240) {
|
||||
const b2 = this.readUInt8()
|
||||
return 193 + (b1 - 193) * 256 + b2
|
||||
} else if (b1 <= 254) {
|
||||
const b2 = this.readUInt8()
|
||||
const b3 = this.readUInt8()
|
||||
return 12481 + (b1 - 241) * 65536 + b2 * 256 + b3
|
||||
}
|
||||
throw new Error('Invalid variable length indicator')
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the field ordinal from the BinaryParser
|
||||
*
|
||||
* @return Field ordinal
|
||||
*/
|
||||
readFieldOrdinal(): number {
|
||||
let type = this.readUInt8()
|
||||
let nth = type & 15
|
||||
type >>= 4
|
||||
|
||||
if (type === 0) {
|
||||
type = this.readUInt8()
|
||||
if (type === 0 || type < 16) {
|
||||
throw new Error('Cannot read FieldOrdinal, type_code out of range')
|
||||
}
|
||||
}
|
||||
|
||||
if (nth === 0) {
|
||||
nth = this.readUInt8()
|
||||
if (nth === 0 || nth < 16) {
|
||||
throw new Error('Cannot read FieldOrdinal, field_code out of range')
|
||||
}
|
||||
}
|
||||
|
||||
return (type << 16) | nth
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the field from the BinaryParser
|
||||
*
|
||||
* @return The field represented by the bytes at the head of the BinaryParser
|
||||
*/
|
||||
readField(): FieldInstance {
|
||||
return Field.fromString(this.readFieldOrdinal().toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a given type from the BinaryParser
|
||||
*
|
||||
* @param type The type that you want to read from the BinaryParser
|
||||
* @return The instance of that type read from the BinaryParser
|
||||
*/
|
||||
readType(type: typeof SerializedType): SerializedType {
|
||||
return type.fromParser(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type associated with a given field
|
||||
*
|
||||
* @param field The field that you wan to get the type of
|
||||
* @return The type associated with the given field
|
||||
*/
|
||||
typeForField(field: FieldInstance): typeof SerializedType {
|
||||
return field.associatedType
|
||||
}
|
||||
|
||||
/**
|
||||
* Read value of the type specified by field from the BinaryParser
|
||||
*
|
||||
* @param field The field that you want to get the associated value for
|
||||
* @return The value associated with the given field
|
||||
*/
|
||||
readFieldValue(field: FieldInstance): SerializedType {
|
||||
const type = this.typeForField(field)
|
||||
if (!type) {
|
||||
throw new Error(`unsupported: (${field.name}, ${field.type.name})`)
|
||||
}
|
||||
const sizeHint = field.isVariableLengthEncoded
|
||||
? this.readVariableLengthLength()
|
||||
: undefined
|
||||
const value = type.fromParser(this, sizeHint)
|
||||
if (value === undefined) {
|
||||
throw new Error(
|
||||
`fromParser for (${field.name}, ${field.type.name}) -> undefined `,
|
||||
)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next field and value from the BinaryParser
|
||||
*
|
||||
* @return The field and value
|
||||
*/
|
||||
readFieldAndValue(): [FieldInstance, SerializedType] {
|
||||
const field = this.readField()
|
||||
return [field, this.readFieldValue(field)]
|
||||
}
|
||||
}
|
||||
|
||||
export { BinaryParser }
|
||||
156
packages/ripple-binary-codec/src/serdes/binary-serializer.ts
Normal file
156
packages/ripple-binary-codec/src/serdes/binary-serializer.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
import * as assert from 'assert'
|
||||
import { FieldInstance } from '../enums'
|
||||
import { SerializedType } from '../types/serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Bytes list is a collection of buffer objects
|
||||
*/
|
||||
class BytesList {
|
||||
private bytesArray: Array<Buffer> = []
|
||||
|
||||
/**
|
||||
* Get the total number of bytes in the BytesList
|
||||
*
|
||||
* @return the number of bytes
|
||||
*/
|
||||
public getLength(): number {
|
||||
return Buffer.concat(this.bytesArray).byteLength
|
||||
}
|
||||
|
||||
/**
|
||||
* Put bytes in the BytesList
|
||||
*
|
||||
* @param bytesArg A Buffer
|
||||
* @return this BytesList
|
||||
*/
|
||||
public put(bytesArg: Buffer): BytesList {
|
||||
const bytes = Buffer.from(bytesArg) // Temporary, to catch instances of Uint8Array being passed in
|
||||
this.bytesArray.push(bytes)
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Write this BytesList to the back of another bytes list
|
||||
*
|
||||
* @param list The BytesList to write to
|
||||
*/
|
||||
public toBytesSink(list: BytesList): void {
|
||||
list.put(this.toBytes())
|
||||
}
|
||||
|
||||
public toBytes(): Buffer {
|
||||
return Buffer.concat(this.bytesArray)
|
||||
}
|
||||
|
||||
toHex(): string {
|
||||
return this.toBytes().toString('hex').toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BinarySerializer is used to write fields and values to buffers
|
||||
*/
|
||||
class BinarySerializer {
|
||||
private sink: BytesList = new BytesList()
|
||||
|
||||
constructor(sink: BytesList) {
|
||||
this.sink = sink
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value to this BinarySerializer
|
||||
*
|
||||
* @param value a SerializedType value
|
||||
*/
|
||||
write(value: SerializedType): void {
|
||||
value.toBytesSink(this.sink)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write bytes to this BinarySerializer
|
||||
*
|
||||
* @param bytes the bytes to write
|
||||
*/
|
||||
put(bytes: Buffer): void {
|
||||
this.sink.put(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a value of a given type to this BinarySerializer
|
||||
*
|
||||
* @param type the type to write
|
||||
* @param value a value of that type
|
||||
*/
|
||||
writeType(type: typeof SerializedType, value: SerializedType): void {
|
||||
this.write(type.from(value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Write BytesList to this BinarySerializer
|
||||
*
|
||||
* @param bl BytesList to write to BinarySerializer
|
||||
*/
|
||||
writeBytesList(bl: BytesList): void {
|
||||
bl.toBytesSink(this.sink)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the header of Variable Length encoded bytes
|
||||
*
|
||||
* @param length the length of the bytes
|
||||
*/
|
||||
private encodeVariableLength(length: number): Buffer {
|
||||
const lenBytes = Buffer.alloc(3)
|
||||
if (length <= 192) {
|
||||
lenBytes[0] = length
|
||||
return lenBytes.slice(0, 1)
|
||||
} else if (length <= 12480) {
|
||||
length -= 193
|
||||
lenBytes[0] = 193 + (length >>> 8)
|
||||
lenBytes[1] = length & 0xff
|
||||
return lenBytes.slice(0, 2)
|
||||
} else if (length <= 918744) {
|
||||
length -= 12481
|
||||
lenBytes[0] = 241 + (length >>> 16)
|
||||
lenBytes[1] = (length >> 8) & 0xff
|
||||
lenBytes[2] = length & 0xff
|
||||
return lenBytes.slice(0, 3)
|
||||
}
|
||||
throw new Error('Overflow error')
|
||||
}
|
||||
|
||||
/**
|
||||
* Write field and value to BinarySerializer
|
||||
*
|
||||
* @param field field to write to BinarySerializer
|
||||
* @param value value to write to BinarySerializer
|
||||
*/
|
||||
writeFieldAndValue(field: FieldInstance, value: SerializedType): void {
|
||||
const associatedValue = field.associatedType.from(value)
|
||||
assert.ok(associatedValue.toBytesSink !== undefined)
|
||||
assert.ok(field.name !== undefined)
|
||||
|
||||
this.sink.put(field.header)
|
||||
|
||||
if (field.isVariableLengthEncoded) {
|
||||
this.writeLengthEncoded(associatedValue)
|
||||
} else {
|
||||
associatedValue.toBytesSink(this.sink)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a variable length encoded value to the BinarySerializer
|
||||
*
|
||||
* @param value length encoded value to write to BytesList
|
||||
*/
|
||||
public writeLengthEncoded(value: SerializedType): void {
|
||||
const bytes = new BytesList()
|
||||
value.toBytesSink(bytes)
|
||||
this.put(this.encodeVariableLength(bytes.getLength()))
|
||||
this.writeBytesList(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
export { BytesList, BinarySerializer }
|
||||
182
packages/ripple-binary-codec/src/shamap.ts
Normal file
182
packages/ripple-binary-codec/src/shamap.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { strict as assert } from 'assert'
|
||||
import { coreTypes } from './types'
|
||||
import { HashPrefix } from './hash-prefixes'
|
||||
import { Sha512Half } from './hashes'
|
||||
import { Hash256 } from './types/hash-256'
|
||||
import { BytesList } from './serdes/binary-serializer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Abstract class describing a SHAMapNode
|
||||
*/
|
||||
abstract class ShaMapNode {
|
||||
abstract hashPrefix(): Buffer
|
||||
abstract isLeaf(): boolean
|
||||
abstract isInner(): boolean
|
||||
abstract toBytesSink(list: BytesList): void
|
||||
abstract hash(): Hash256
|
||||
}
|
||||
|
||||
/**
|
||||
* Class describing a Leaf of SHAMap
|
||||
*/
|
||||
class ShaMapLeaf extends ShaMapNode {
|
||||
constructor(public index: Hash256, public item?: ShaMapNode) {
|
||||
super()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true as ShaMapLeaf is a leaf node
|
||||
*/
|
||||
isLeaf(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns false as ShaMapLeaf is not an inner node
|
||||
*/
|
||||
isInner(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prefix of the this.item
|
||||
*
|
||||
* @returns The hash prefix, unless this.item is undefined, then it returns an empty Buffer
|
||||
*/
|
||||
hashPrefix(): Buffer {
|
||||
return this.item === undefined ? Buffer.alloc(0) : this.item.hashPrefix()
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash the bytes representation of this
|
||||
*
|
||||
* @returns hash of this.item concatenated with this.index
|
||||
*/
|
||||
hash(): Hash256 {
|
||||
const hash = Sha512Half.put(this.hashPrefix())
|
||||
this.toBytesSink(hash)
|
||||
return hash.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bytes representation of this to a BytesList
|
||||
* @param list BytesList to write bytes to
|
||||
*/
|
||||
toBytesSink(list: BytesList): void {
|
||||
if (this.item !== undefined) {
|
||||
this.item.toBytesSink(list)
|
||||
}
|
||||
this.index.toBytesSink(list)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining an Inner Node of a SHAMap
|
||||
*/
|
||||
class ShaMapInner extends ShaMapNode {
|
||||
private slotBits = 0
|
||||
private branches: Array<ShaMapNode> = Array(16)
|
||||
|
||||
constructor(private depth: number = 0) {
|
||||
super()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true as ShaMapInner is an inner node
|
||||
*/
|
||||
isInner(): boolean {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns false as ShaMapInner is not a leaf node
|
||||
*/
|
||||
isLeaf(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hash prefix for this node
|
||||
*
|
||||
* @returns hash prefix describing an inner node
|
||||
*/
|
||||
hashPrefix(): Buffer {
|
||||
return HashPrefix.innerNode
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a branch of this node to be another node
|
||||
*
|
||||
* @param slot Slot to add branch to this.branches
|
||||
* @param branch Branch to add
|
||||
*/
|
||||
setBranch(slot: number, branch: ShaMapNode): void {
|
||||
this.slotBits = this.slotBits | (1 << slot)
|
||||
this.branches[slot] = branch
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns true if node is empty
|
||||
*/
|
||||
empty(): boolean {
|
||||
return this.slotBits === 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the hash of this node
|
||||
*
|
||||
* @returns The hash of this node
|
||||
*/
|
||||
hash(): Hash256 {
|
||||
if (this.empty()) {
|
||||
return coreTypes.Hash256.ZERO_256
|
||||
}
|
||||
const hash = Sha512Half.put(this.hashPrefix())
|
||||
this.toBytesSink(hash)
|
||||
return hash.finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the bytes representation of this node to a BytesList
|
||||
*
|
||||
* @param list BytesList to write bytes to
|
||||
*/
|
||||
toBytesSink(list: BytesList): void {
|
||||
for (let i = 0; i < this.branches.length; i++) {
|
||||
const branch = this.branches[i]
|
||||
const hash = branch ? branch.hash() : coreTypes.Hash256.ZERO_256
|
||||
hash.toBytesSink(list)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add item to the SHAMap
|
||||
*
|
||||
* @param index Hash of the index of the item being inserted
|
||||
* @param item Item to insert in the map
|
||||
* @param leaf Leaf node to insert when branch doesn't exist
|
||||
*/
|
||||
addItem(index?: Hash256, item?: ShaMapNode, leaf?: ShaMapLeaf): void {
|
||||
assert.ok(index !== undefined)
|
||||
const nibble = index.nibblet(this.depth)
|
||||
const existing = this.branches[nibble]
|
||||
|
||||
if (existing === undefined) {
|
||||
this.setBranch(nibble, leaf || new ShaMapLeaf(index, item))
|
||||
} else if (existing instanceof ShaMapLeaf) {
|
||||
const newInner = new ShaMapInner(this.depth + 1)
|
||||
newInner.addItem(existing.index, undefined, existing)
|
||||
newInner.addItem(index, item, leaf)
|
||||
this.setBranch(nibble, newInner)
|
||||
} else if (existing instanceof ShaMapInner) {
|
||||
existing.addItem(index, item, leaf)
|
||||
} else {
|
||||
throw new Error('invalid ShaMap.addItem call')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ShaMap extends ShaMapInner {}
|
||||
|
||||
export { ShaMap, ShaMapNode, ShaMapLeaf }
|
||||
86
packages/ripple-binary-codec/src/types/account-id.ts
Normal file
86
packages/ripple-binary-codec/src/types/account-id.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import {
|
||||
decodeAccountID,
|
||||
encodeAccountID,
|
||||
isValidXAddress,
|
||||
xAddressToClassicAddress,
|
||||
} from 'ripple-address-codec'
|
||||
import { Hash160 } from './hash-160'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
const HEX_REGEX = /^[A-F0-9]{40}$/
|
||||
|
||||
/**
|
||||
* Class defining how to encode and decode an AccountID
|
||||
*/
|
||||
class AccountID extends Hash160 {
|
||||
static readonly defaultAccountID: AccountID = new AccountID(Buffer.alloc(20))
|
||||
|
||||
constructor(bytes?: Buffer) {
|
||||
super(bytes ?? AccountID.defaultAccountID.bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how to construct an AccountID
|
||||
*
|
||||
* @param value either an existing AccountID, a hex-string, or a base58 r-Address
|
||||
* @returns an AccountID object
|
||||
*/
|
||||
static from<T extends Hash160 | string>(value: T): AccountID {
|
||||
if (value instanceof AccountID) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
if (value === '') {
|
||||
return new AccountID()
|
||||
}
|
||||
|
||||
return HEX_REGEX.test(value)
|
||||
? new AccountID(Buffer.from(value, 'hex'))
|
||||
: this.fromBase58(value)
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct AccountID from value given')
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how to build an AccountID from a base58 r-Address
|
||||
*
|
||||
* @param value a base58 r-Address
|
||||
* @returns an AccountID object
|
||||
*/
|
||||
static fromBase58(value: string): AccountID {
|
||||
if (isValidXAddress(value)) {
|
||||
const classic = xAddressToClassicAddress(value)
|
||||
|
||||
if (classic.tag !== false)
|
||||
throw new Error('Only allowed to have tag on Account or Destination')
|
||||
|
||||
value = classic.classicAddress
|
||||
}
|
||||
|
||||
return new AccountID(Buffer.from(decodeAccountID(value)))
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of toJSON
|
||||
*
|
||||
* @returns the base58 string for this AccountID
|
||||
*/
|
||||
toJSON(): string {
|
||||
return this.toBase58()
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how to encode AccountID into a base58 address
|
||||
*
|
||||
* @returns the base58 string defined by this.bytes
|
||||
*/
|
||||
toBase58(): string {
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
return encodeAccountID(this.bytes as any)
|
||||
/* eslint-enable @typescript-eslint/no-explicit-any */
|
||||
}
|
||||
}
|
||||
|
||||
export { AccountID }
|
||||
256
packages/ripple-binary-codec/src/types/amount.ts
Normal file
256
packages/ripple-binary-codec/src/types/amount.ts
Normal file
@@ -0,0 +1,256 @@
|
||||
import { Decimal } from 'decimal.js'
|
||||
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
|
||||
import { AccountID } from './account-id'
|
||||
import { Currency } from './currency'
|
||||
import { JsonObject, SerializedType } from './serialized-type'
|
||||
import * as bigInt from 'big-integer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Constants for validating amounts
|
||||
*/
|
||||
const MIN_IOU_EXPONENT = -96
|
||||
const MAX_IOU_EXPONENT = 80
|
||||
const MAX_IOU_PRECISION = 16
|
||||
const MAX_DROPS = new Decimal('1e17')
|
||||
const MIN_XRP = new Decimal('1e-6')
|
||||
const mask = bigInt(0x00000000ffffffff)
|
||||
|
||||
/**
|
||||
* decimal.js configuration for Amount IOUs
|
||||
*/
|
||||
Decimal.config({
|
||||
toExpPos: MAX_IOU_EXPONENT + MAX_IOU_PRECISION,
|
||||
toExpNeg: MIN_IOU_EXPONENT - MAX_IOU_PRECISION,
|
||||
})
|
||||
|
||||
/**
|
||||
* Interface for JSON objects that represent amounts
|
||||
*/
|
||||
interface AmountObject extends JsonObject {
|
||||
value: string
|
||||
currency: string
|
||||
issuer: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for AmountObject
|
||||
*/
|
||||
function isAmountObject(arg): arg is AmountObject {
|
||||
const keys = Object.keys(arg).sort()
|
||||
return (
|
||||
keys.length === 3 &&
|
||||
keys[0] === 'currency' &&
|
||||
keys[1] === 'issuer' &&
|
||||
keys[2] === 'value'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing/Deserializing Amounts
|
||||
*/
|
||||
class Amount extends SerializedType {
|
||||
static defaultAmount: Amount = new Amount(
|
||||
Buffer.from('4000000000000000', 'hex'),
|
||||
)
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? Amount.defaultAmount.bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an amount from an IOU or string amount
|
||||
*
|
||||
* @param value An Amount, object representing an IOU, or a string
|
||||
* representing an integer amount
|
||||
* @returns An Amount object
|
||||
*/
|
||||
static from<T extends Amount | AmountObject | string>(value: T): Amount {
|
||||
if (value instanceof Amount) {
|
||||
return value
|
||||
}
|
||||
|
||||
let amount = Buffer.alloc(8)
|
||||
if (typeof value === 'string') {
|
||||
Amount.assertXrpIsValid(value)
|
||||
|
||||
const number = bigInt(value)
|
||||
|
||||
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
|
||||
intBuf[0].writeUInt32BE(Number(number.shiftRight(32)), 0)
|
||||
intBuf[1].writeUInt32BE(Number(number.and(mask)), 0)
|
||||
|
||||
amount = Buffer.concat(intBuf)
|
||||
|
||||
amount[0] |= 0x40
|
||||
|
||||
return new Amount(amount)
|
||||
}
|
||||
|
||||
if (isAmountObject(value)) {
|
||||
const number = new Decimal(value.value)
|
||||
Amount.assertIouIsValid(number)
|
||||
|
||||
if (number.isZero()) {
|
||||
amount[0] |= 0x80
|
||||
} else {
|
||||
const integerNumberString = number
|
||||
.times(`1e${-(number.e - 15)}`)
|
||||
.abs()
|
||||
.toString()
|
||||
|
||||
const num = bigInt(integerNumberString)
|
||||
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
|
||||
intBuf[0].writeUInt32BE(Number(num.shiftRight(32)), 0)
|
||||
intBuf[1].writeUInt32BE(Number(num.and(mask)), 0)
|
||||
|
||||
amount = Buffer.concat(intBuf)
|
||||
|
||||
amount[0] |= 0x80
|
||||
|
||||
if (number.gt(new Decimal(0))) {
|
||||
amount[0] |= 0x40
|
||||
}
|
||||
|
||||
const exponent = number.e - 15
|
||||
const exponentByte = 97 + exponent
|
||||
amount[0] |= exponentByte >>> 2
|
||||
amount[1] |= (exponentByte & 0x03) << 6
|
||||
}
|
||||
|
||||
const currency = Currency.from(value.currency).toBytes()
|
||||
const issuer = AccountID.from(value.issuer).toBytes()
|
||||
return new Amount(Buffer.concat([amount, currency, issuer]))
|
||||
}
|
||||
|
||||
throw new Error('Invalid type to construct an Amount')
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an amount from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to read the Amount from
|
||||
* @returns An Amount object
|
||||
*/
|
||||
static fromParser(parser: BinaryParser): Amount {
|
||||
const isXRP = parser.peek() & 0x80
|
||||
const numBytes = isXRP ? 48 : 8
|
||||
return new Amount(parser.read(numBytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON representation of this Amount
|
||||
*
|
||||
* @returns the JSON interpretation of this.bytes
|
||||
*/
|
||||
toJSON(): AmountObject | string {
|
||||
if (this.isNative()) {
|
||||
const bytes = this.bytes
|
||||
const isPositive = bytes[0] & 0x40
|
||||
const sign = isPositive ? '' : '-'
|
||||
bytes[0] &= 0x3f
|
||||
|
||||
const msb = bigInt(bytes.slice(0, 4).readUInt32BE(0))
|
||||
const lsb = bigInt(bytes.slice(4).readUInt32BE(0))
|
||||
const num = msb.shiftLeft(32).or(lsb)
|
||||
|
||||
return `${sign}${num.toString()}`
|
||||
} else {
|
||||
const parser = new BinaryParser(this.toString())
|
||||
const mantissa = parser.read(8)
|
||||
const currency = Currency.fromParser(parser) as Currency
|
||||
const issuer = AccountID.fromParser(parser) as AccountID
|
||||
|
||||
const b1 = mantissa[0]
|
||||
const b2 = mantissa[1]
|
||||
|
||||
const isPositive = b1 & 0x40
|
||||
const sign = isPositive ? '' : '-'
|
||||
const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97
|
||||
|
||||
mantissa[0] = 0
|
||||
mantissa[1] &= 0x3f
|
||||
const value = new Decimal(`${sign}0x${mantissa.toString('hex')}`).times(
|
||||
`1e${exponent}`,
|
||||
)
|
||||
Amount.assertIouIsValid(value)
|
||||
|
||||
return {
|
||||
value: value.toString(),
|
||||
currency: currency.toJSON(),
|
||||
issuer: issuer.toJSON(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate XRP amount
|
||||
*
|
||||
* @param amount String representing XRP amount
|
||||
* @returns void, but will throw if invalid amount
|
||||
*/
|
||||
private static assertXrpIsValid(amount: string): void {
|
||||
if (amount.indexOf('.') !== -1) {
|
||||
throw new Error(`${amount.toString()} is an illegal amount`)
|
||||
}
|
||||
|
||||
const decimal = new Decimal(amount)
|
||||
if (!decimal.isZero()) {
|
||||
if (decimal.lt(MIN_XRP) || decimal.gt(MAX_DROPS)) {
|
||||
throw new Error(`${amount.toString()} is an illegal amount`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate IOU.value amount
|
||||
*
|
||||
* @param decimal Decimal.js object representing IOU.value
|
||||
* @returns void, but will throw if invalid amount
|
||||
*/
|
||||
private static assertIouIsValid(decimal: Decimal): void {
|
||||
if (!decimal.isZero()) {
|
||||
const p = decimal.precision()
|
||||
const e = decimal.e - 15
|
||||
if (
|
||||
p > MAX_IOU_PRECISION ||
|
||||
e > MAX_IOU_EXPONENT ||
|
||||
e < MIN_IOU_EXPONENT
|
||||
) {
|
||||
throw new Error('Decimal precision out of range')
|
||||
}
|
||||
this.verifyNoDecimal(decimal)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the value after being multiplied by the exponent does not
|
||||
* contain a decimal.
|
||||
*
|
||||
* @param decimal a Decimal object
|
||||
* @returns a string of the object without a decimal
|
||||
*/
|
||||
private static verifyNoDecimal(decimal: Decimal): void {
|
||||
const integerNumberString = decimal
|
||||
.times(`1e${-(decimal.e - 15)}`)
|
||||
.abs()
|
||||
.toString()
|
||||
|
||||
if (integerNumberString.indexOf('.') !== -1) {
|
||||
throw new Error('Decimal place found in integerNumberString')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this amount is in units of Native Currency(XRP)
|
||||
*
|
||||
* @returns true if Native (XRP)
|
||||
*/
|
||||
private isNative(): boolean {
|
||||
return (this.bytes[0] & 0x80) === 0
|
||||
}
|
||||
}
|
||||
|
||||
export { Amount, AmountObject }
|
||||
43
packages/ripple-binary-codec/src/types/blob.ts
Normal file
43
packages/ripple-binary-codec/src/types/blob.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { SerializedType } from './serialized-type'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Variable length encoded type
|
||||
*/
|
||||
class Blob extends SerializedType {
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines how to read a Blob from a BinaryParser
|
||||
*
|
||||
* @param parser The binary parser to read the Blob from
|
||||
* @param hint The length of the blob, computed by readVariableLengthLength() and passed in
|
||||
* @returns A Blob object
|
||||
*/
|
||||
static fromParser(parser: BinaryParser, hint: number): Blob {
|
||||
return new Blob(parser.read(hint))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Blob object from a hex-string
|
||||
*
|
||||
* @param value existing Blob object or a hex-string
|
||||
* @returns A Blob object
|
||||
*/
|
||||
static from<T extends Blob | string>(value: T): Blob {
|
||||
if (value instanceof Blob) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return new Blob(Buffer.from(value, 'hex'))
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct Blob from value given')
|
||||
}
|
||||
}
|
||||
|
||||
export { Blob }
|
||||
139
packages/ripple-binary-codec/src/types/currency.ts
Normal file
139
packages/ripple-binary-codec/src/types/currency.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import { Hash160 } from './hash-160'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
const ISO_REGEX = /^[A-Z0-9]{3}$/
|
||||
const HEX_REGEX = /^[A-F0-9]{40}$/
|
||||
|
||||
/**
|
||||
* Convert an ISO code to a currency bytes representation
|
||||
*/
|
||||
function isoToBytes(iso: string): Buffer {
|
||||
const bytes = Buffer.alloc(20)
|
||||
if (iso !== 'XRP') {
|
||||
const isoBytes = iso.split('').map((c) => c.charCodeAt(0))
|
||||
bytes.set(isoBytes, 12)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if ISO is a valid iso code
|
||||
*/
|
||||
function isIsoCode(iso: string): boolean {
|
||||
return ISO_REGEX.test(iso)
|
||||
}
|
||||
|
||||
function isoCodeFromHex(code: Buffer): string | null {
|
||||
const iso = code.toString()
|
||||
if (iso === 'XRP') {
|
||||
throw new Error(
|
||||
'Disallowed currency code: to indicate the currency XRP you must use 20 bytes of 0s',
|
||||
)
|
||||
}
|
||||
if (isIsoCode(iso)) {
|
||||
return iso
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if hex is a valid hex-string
|
||||
*/
|
||||
function isHex(hex: string): boolean {
|
||||
return HEX_REGEX.test(hex)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a string is a valid representation of a currency
|
||||
*/
|
||||
function isStringRepresentation(input: string): boolean {
|
||||
return input.length === 3 || isHex(input)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if a Buffer is a valid representation of a currency
|
||||
*/
|
||||
function isBytesArray(bytes: Buffer): boolean {
|
||||
return bytes.byteLength === 20
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that a value is a valid representation of a currency
|
||||
*/
|
||||
function isValidRepresentation(input: Buffer | string): boolean {
|
||||
return input instanceof Buffer
|
||||
? isBytesArray(input)
|
||||
: isStringRepresentation(input)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate bytes from a string or buffer representation of a currency
|
||||
*/
|
||||
function bytesFromRepresentation(input: string): Buffer {
|
||||
if (!isValidRepresentation(input)) {
|
||||
throw new Error(`Unsupported Currency representation: ${input}`)
|
||||
}
|
||||
return input.length === 3 ? isoToBytes(input) : Buffer.from(input, 'hex')
|
||||
}
|
||||
|
||||
/**
|
||||
* Class defining how to encode and decode Currencies
|
||||
*/
|
||||
class Currency extends Hash160 {
|
||||
static readonly XRP = new Currency(Buffer.alloc(20))
|
||||
private readonly _iso: string | null
|
||||
|
||||
constructor(byteBuf: Buffer) {
|
||||
super(byteBuf ?? Currency.XRP.bytes)
|
||||
const code = this.bytes.slice(12, 15)
|
||||
|
||||
if (this.bytes[0] !== 0) {
|
||||
this._iso = null
|
||||
} else if (code.toString('hex') === '000000') {
|
||||
this._iso = 'XRP'
|
||||
} else {
|
||||
this._iso = isoCodeFromHex(code)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the ISO code of this currency
|
||||
*
|
||||
* @returns ISO code if it exists, else null
|
||||
*/
|
||||
iso(): string | null {
|
||||
return this._iso
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a Currency object
|
||||
*
|
||||
* @param val Currency object or a string representation of a currency
|
||||
*/
|
||||
static from<T extends Hash160 | string>(value: T): Currency {
|
||||
if (value instanceof Currency) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return new Currency(bytesFromRepresentation(value))
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct Currency from value given')
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the JSON representation of a currency
|
||||
*
|
||||
* @returns JSON representation
|
||||
*/
|
||||
toJSON(): string {
|
||||
const iso = this.iso()
|
||||
if (iso !== null) {
|
||||
return iso
|
||||
}
|
||||
return this.bytes.toString('hex').toUpperCase()
|
||||
}
|
||||
}
|
||||
|
||||
export { Currency }
|
||||
16
packages/ripple-binary-codec/src/types/hash-128.ts
Normal file
16
packages/ripple-binary-codec/src/types/hash-128.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Hash } from './hash'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Hash with a width of 128 bits
|
||||
*/
|
||||
class Hash128 extends Hash {
|
||||
static readonly width = 16
|
||||
static readonly ZERO_128: Hash128 = new Hash128(Buffer.alloc(Hash128.width))
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? Hash128.ZERO_128.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
export { Hash128 }
|
||||
20
packages/ripple-binary-codec/src/types/hash-160.ts
Normal file
20
packages/ripple-binary-codec/src/types/hash-160.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Hash } from './hash'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Hash with a width of 160 bits
|
||||
*/
|
||||
class Hash160 extends Hash {
|
||||
static readonly width = 20
|
||||
static readonly ZERO_160: Hash160 = new Hash160(Buffer.alloc(Hash160.width))
|
||||
|
||||
constructor(bytes?: Buffer) {
|
||||
if (bytes && bytes.byteLength === 0) {
|
||||
bytes = Hash160.ZERO_160.bytes
|
||||
}
|
||||
|
||||
super(bytes ?? Hash160.ZERO_160.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
export { Hash160 }
|
||||
16
packages/ripple-binary-codec/src/types/hash-256.ts
Normal file
16
packages/ripple-binary-codec/src/types/hash-256.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Hash } from './hash'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Hash with a width of 256 bits
|
||||
*/
|
||||
class Hash256 extends Hash {
|
||||
static readonly width = 32
|
||||
static readonly ZERO_256 = new Hash256(Buffer.alloc(Hash256.width))
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? Hash256.ZERO_256.bytes)
|
||||
}
|
||||
}
|
||||
|
||||
export { Hash256 }
|
||||
81
packages/ripple-binary-codec/src/types/hash.ts
Normal file
81
packages/ripple-binary-codec/src/types/hash.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { Comparable } from './serialized-type'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Base class defining how to encode and decode hashes
|
||||
*/
|
||||
class Hash extends Comparable {
|
||||
static readonly width: number
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes)
|
||||
if (this.bytes.byteLength !== (this.constructor as typeof Hash).width) {
|
||||
throw new Error(`Invalid Hash length ${this.bytes.byteLength}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Hash object from an existing Hash object or a hex-string
|
||||
*
|
||||
* @param value A hash object or hex-string of a hash
|
||||
*/
|
||||
static from<T extends Hash | string>(value: T): Hash {
|
||||
if (value instanceof this) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === 'string') {
|
||||
return new this(Buffer.from(value, 'hex'))
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct Hash from given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Hash object from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to read the hash from
|
||||
* @param hint length of the bytes to read, optional
|
||||
*/
|
||||
static fromParser(parser: BinaryParser, hint?: number): Hash {
|
||||
return new this(parser.read(hint ?? this.width))
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded operator for comparing two hash objects
|
||||
*
|
||||
* @param other The Hash to compare this to
|
||||
*/
|
||||
compareTo(other: Hash): number {
|
||||
return this.bytes.compare(
|
||||
(this.constructor as typeof Hash).from(other).bytes,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns the hex-string representation of this Hash
|
||||
*/
|
||||
toString(): string {
|
||||
return this.toHex()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns four bits at the specified depth within a hash
|
||||
*
|
||||
* @param depth The depth of the four bits
|
||||
* @returns The number represented by the four bits
|
||||
*/
|
||||
nibblet(depth: number): number {
|
||||
const byteIx = depth > 0 ? (depth / 2) | 0 : 0
|
||||
let b = this.bytes[byteIx]
|
||||
if (depth % 2 === 0) {
|
||||
b = (b & 0xf0) >>> 4
|
||||
} else {
|
||||
b = b & 0x0f
|
||||
}
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
export { Hash }
|
||||
49
packages/ripple-binary-codec/src/types/index.ts
Normal file
49
packages/ripple-binary-codec/src/types/index.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Field,
|
||||
TransactionResult,
|
||||
TransactionType,
|
||||
LedgerEntryType,
|
||||
} from '../enums'
|
||||
import { AccountID } from './account-id'
|
||||
import { Amount } from './amount'
|
||||
import { Blob } from './blob'
|
||||
import { Currency } from './currency'
|
||||
import { Hash128 } from './hash-128'
|
||||
import { Hash160 } from './hash-160'
|
||||
import { Hash256 } from './hash-256'
|
||||
import { PathSet } from './path-set'
|
||||
import { STArray } from './st-array'
|
||||
import { STObject } from './st-object'
|
||||
import { UInt16 } from './uint-16'
|
||||
import { UInt32 } from './uint-32'
|
||||
import { UInt64 } from './uint-64'
|
||||
import { UInt8 } from './uint-8'
|
||||
import { Vector256 } from './vector-256'
|
||||
|
||||
const coreTypes = {
|
||||
AccountID,
|
||||
Amount,
|
||||
Blob,
|
||||
Currency,
|
||||
Hash128,
|
||||
Hash160,
|
||||
Hash256,
|
||||
PathSet,
|
||||
STArray,
|
||||
STObject,
|
||||
UInt8,
|
||||
UInt16,
|
||||
UInt32,
|
||||
UInt64,
|
||||
Vector256,
|
||||
}
|
||||
|
||||
Object.values(Field).forEach((field) => {
|
||||
field.associatedType = coreTypes[field.type.name]
|
||||
})
|
||||
|
||||
Field['TransactionType'].associatedType = TransactionType
|
||||
Field['TransactionResult'].associatedType = TransactionResult
|
||||
Field['LedgerEntryType'].associatedType = LedgerEntryType
|
||||
|
||||
export { coreTypes }
|
||||
290
packages/ripple-binary-codec/src/types/path-set.ts
Normal file
290
packages/ripple-binary-codec/src/types/path-set.ts
Normal file
@@ -0,0 +1,290 @@
|
||||
import { AccountID } from './account-id'
|
||||
import { Currency } from './currency'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { SerializedType, JsonObject } from './serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Constants for separating Paths in a PathSet
|
||||
*/
|
||||
const PATHSET_END_BYTE = 0x00
|
||||
const PATH_SEPARATOR_BYTE = 0xff
|
||||
|
||||
/**
|
||||
* Constant for masking types of a Hop
|
||||
*/
|
||||
const TYPE_ACCOUNT = 0x01
|
||||
const TYPE_CURRENCY = 0x10
|
||||
const TYPE_ISSUER = 0x20
|
||||
|
||||
/**
|
||||
* The object representation of a Hop, an issuer AccountID, an account AccountID, and a Currency
|
||||
*/
|
||||
interface HopObject extends JsonObject {
|
||||
issuer?: string
|
||||
account?: string
|
||||
currency?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeGuard for HopObject
|
||||
*/
|
||||
function isHopObject(arg): arg is HopObject {
|
||||
return (
|
||||
arg.issuer !== undefined ||
|
||||
arg.account !== undefined ||
|
||||
arg.currency !== undefined
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* TypeGuard for PathSet
|
||||
*/
|
||||
function isPathSet(arg): arg is Array<Array<HopObject>> {
|
||||
return (
|
||||
(Array.isArray(arg) && arg.length === 0) ||
|
||||
(Array.isArray(arg) && Array.isArray(arg[0]) && arg[0].length === 0) ||
|
||||
(Array.isArray(arg) && Array.isArray(arg[0]) && isHopObject(arg[0][0]))
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize and Deserialize a Hop
|
||||
*/
|
||||
class Hop extends SerializedType {
|
||||
/**
|
||||
* Create a Hop from a HopObject
|
||||
*
|
||||
* @param value Either a hop or HopObject to create a hop with
|
||||
* @returns a Hop
|
||||
*/
|
||||
static from(value: Hop | HopObject): Hop {
|
||||
if (value instanceof Hop) {
|
||||
return value
|
||||
}
|
||||
|
||||
const bytes: Array<Buffer> = [Buffer.from([0])]
|
||||
|
||||
if (value.account) {
|
||||
bytes.push(AccountID.from(value.account).toBytes())
|
||||
bytes[0][0] |= TYPE_ACCOUNT
|
||||
}
|
||||
|
||||
if (value.currency) {
|
||||
bytes.push(Currency.from(value.currency).toBytes())
|
||||
bytes[0][0] |= TYPE_CURRENCY
|
||||
}
|
||||
|
||||
if (value.issuer) {
|
||||
bytes.push(AccountID.from(value.issuer).toBytes())
|
||||
bytes[0][0] |= TYPE_ISSUER
|
||||
}
|
||||
|
||||
return new Hop(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Hop from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to read the Hop from
|
||||
* @returns a Hop
|
||||
*/
|
||||
static fromParser(parser: BinaryParser): Hop {
|
||||
const type = parser.readUInt8()
|
||||
const bytes: Array<Buffer> = [Buffer.from([type])]
|
||||
|
||||
if (type & TYPE_ACCOUNT) {
|
||||
bytes.push(parser.read(AccountID.width))
|
||||
}
|
||||
|
||||
if (type & TYPE_CURRENCY) {
|
||||
bytes.push(parser.read(Currency.width))
|
||||
}
|
||||
|
||||
if (type & TYPE_ISSUER) {
|
||||
bytes.push(parser.read(AccountID.width))
|
||||
}
|
||||
|
||||
return new Hop(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON interpretation of this hop
|
||||
*
|
||||
* @returns a HopObject, an JS object with optional account, issuer, and currency
|
||||
*/
|
||||
toJSON(): HopObject {
|
||||
const hopParser = new BinaryParser(this.bytes.toString('hex'))
|
||||
const type = hopParser.readUInt8()
|
||||
|
||||
let account, currency, issuer
|
||||
if (type & TYPE_ACCOUNT) {
|
||||
account = (AccountID.fromParser(hopParser) as AccountID).toJSON()
|
||||
}
|
||||
|
||||
if (type & TYPE_CURRENCY) {
|
||||
currency = (Currency.fromParser(hopParser) as Currency).toJSON()
|
||||
}
|
||||
|
||||
if (type & TYPE_ISSUER) {
|
||||
issuer = (AccountID.fromParser(hopParser) as AccountID).toJSON()
|
||||
}
|
||||
|
||||
const result: HopObject = {}
|
||||
if (account) {
|
||||
result.account = account
|
||||
}
|
||||
|
||||
if (issuer) {
|
||||
result.issuer = issuer
|
||||
}
|
||||
|
||||
if (currency) {
|
||||
result.currency = currency
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* get a number representing the type of this hop
|
||||
*
|
||||
* @returns a number to be bitwise and-ed with TYPE_ constants to describe the types in the hop
|
||||
*/
|
||||
type(): number {
|
||||
return this.bytes[0]
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing/deserializing Paths
|
||||
*/
|
||||
class Path extends SerializedType {
|
||||
/**
|
||||
* construct a Path from an array of Hops
|
||||
*
|
||||
* @param value Path or array of HopObjects to construct a Path
|
||||
* @returns the Path
|
||||
*/
|
||||
static from(value: Path | Array<HopObject>): Path {
|
||||
if (value instanceof Path) {
|
||||
return value
|
||||
}
|
||||
|
||||
const bytes: Array<Buffer> = []
|
||||
value.forEach((hop: HopObject) => {
|
||||
bytes.push(Hop.from(hop).toBytes())
|
||||
})
|
||||
|
||||
return new Path(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a Path from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to read Path from
|
||||
* @returns the Path represented by the bytes read from the BinaryParser
|
||||
*/
|
||||
static fromParser(parser: BinaryParser): Path {
|
||||
const bytes: Array<Buffer> = []
|
||||
while (!parser.end()) {
|
||||
bytes.push(Hop.fromParser(parser).toBytes())
|
||||
|
||||
if (
|
||||
parser.peek() === PATHSET_END_BYTE ||
|
||||
parser.peek() === PATH_SEPARATOR_BYTE
|
||||
) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return new Path(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON representation of this Path
|
||||
*
|
||||
* @returns an Array of HopObject constructed from this.bytes
|
||||
*/
|
||||
toJSON(): Array<HopObject> {
|
||||
const json: Array<HopObject> = []
|
||||
const pathParser = new BinaryParser(this.toString())
|
||||
|
||||
while (!pathParser.end()) {
|
||||
json.push(Hop.fromParser(pathParser).toJSON())
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize and Serialize the PathSet type
|
||||
*/
|
||||
class PathSet extends SerializedType {
|
||||
/**
|
||||
* Construct a PathSet from an Array of Arrays representing paths
|
||||
*
|
||||
* @param value A PathSet or Array of Array of HopObjects
|
||||
* @returns the PathSet constructed from value
|
||||
*/
|
||||
static from<T extends PathSet | Array<Array<HopObject>>>(value: T): PathSet {
|
||||
if (value instanceof PathSet) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (isPathSet(value)) {
|
||||
const bytes: Array<Buffer> = []
|
||||
|
||||
value.forEach((path: Array<HopObject>) => {
|
||||
bytes.push(Path.from(path).toBytes())
|
||||
bytes.push(Buffer.from([PATH_SEPARATOR_BYTE]))
|
||||
})
|
||||
|
||||
bytes[bytes.length - 1] = Buffer.from([PATHSET_END_BYTE])
|
||||
|
||||
return new PathSet(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct PathSet from given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a PathSet from a BinaryParser
|
||||
*
|
||||
* @param parser A BinaryParser to read PathSet from
|
||||
* @returns the PathSet read from parser
|
||||
*/
|
||||
static fromParser(parser: BinaryParser): PathSet {
|
||||
const bytes: Array<Buffer> = []
|
||||
|
||||
while (!parser.end()) {
|
||||
bytes.push(Path.fromParser(parser).toBytes())
|
||||
bytes.push(parser.read(1))
|
||||
|
||||
if (bytes[bytes.length - 1][0] == PATHSET_END_BYTE) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return new PathSet(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON representation of this PathSet
|
||||
*
|
||||
* @returns an Array of Array of HopObjects, representing this PathSet
|
||||
*/
|
||||
toJSON(): Array<Array<HopObject>> {
|
||||
const json: Array<Array<HopObject>> = []
|
||||
const pathParser = new BinaryParser(this.toString())
|
||||
|
||||
while (!pathParser.end()) {
|
||||
json.push(Path.fromParser(pathParser).toJSON())
|
||||
pathParser.skip(1)
|
||||
}
|
||||
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
export { PathSet }
|
||||
116
packages/ripple-binary-codec/src/types/serialized-type.ts
Normal file
116
packages/ripple-binary-codec/src/types/serialized-type.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
import { BytesList } from '../serdes/binary-serializer'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import * as bigInt from 'big-integer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
type JSON = string | number | boolean | null | undefined | JSON[] | JsonObject
|
||||
|
||||
type JsonObject = { [key: string]: JSON }
|
||||
|
||||
/**
|
||||
* The base class for all binary-codec types
|
||||
*/
|
||||
class SerializedType {
|
||||
protected readonly bytes: Buffer = Buffer.alloc(0)
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
this.bytes = bytes ?? Buffer.alloc(0)
|
||||
}
|
||||
|
||||
static fromParser(parser: BinaryParser, hint?: number): SerializedType {
|
||||
throw new Error('fromParser not implemented')
|
||||
return this.fromParser(parser, hint)
|
||||
}
|
||||
|
||||
static from(
|
||||
value: SerializedType | JSON | bigInt.BigInteger,
|
||||
): SerializedType {
|
||||
throw new Error('from not implemented')
|
||||
return this.from(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the bytes representation of a SerializedType to a BytesList
|
||||
*
|
||||
* @param list The BytesList to write SerializedType bytes to
|
||||
*/
|
||||
toBytesSink(list: BytesList): void {
|
||||
list.put(this.bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hex representation of a SerializedType's bytes
|
||||
*
|
||||
* @returns hex String of this.bytes
|
||||
*/
|
||||
toHex(): string {
|
||||
return this.toBytes().toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bytes representation of a SerializedType
|
||||
*
|
||||
* @returns A buffer of the bytes
|
||||
*/
|
||||
toBytes(): Buffer {
|
||||
if (this.bytes) {
|
||||
return this.bytes
|
||||
}
|
||||
const bytes = new BytesList()
|
||||
this.toBytesSink(bytes)
|
||||
return bytes.toBytes()
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON representation of a SerializedType
|
||||
*
|
||||
* @returns any type, if not overloaded returns hexString representation of bytes
|
||||
*/
|
||||
toJSON(): JSON {
|
||||
return this.toHex()
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns hexString representation of this.bytes
|
||||
*/
|
||||
toString(): string {
|
||||
return this.toHex()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for SerializedTypes that are comparable
|
||||
*/
|
||||
class Comparable extends SerializedType {
|
||||
lt(other: Comparable): boolean {
|
||||
return this.compareTo(other) < 0
|
||||
}
|
||||
|
||||
eq(other: Comparable): boolean {
|
||||
return this.compareTo(other) === 0
|
||||
}
|
||||
|
||||
gt(other: Comparable): boolean {
|
||||
return this.compareTo(other) > 0
|
||||
}
|
||||
|
||||
gte(other: Comparable): boolean {
|
||||
return this.compareTo(other) > -1
|
||||
}
|
||||
|
||||
lte(other: Comparable): boolean {
|
||||
return this.compareTo(other) < 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload this method to define how two Comparable SerializedTypes are compared
|
||||
*
|
||||
* @param other The comparable object to compare this to
|
||||
* @returns A number denoting the relationship of this and other
|
||||
*/
|
||||
compareTo(other: Comparable): number {
|
||||
throw new Error(`cannot compare ${this.toString()} and ${other.toString()}`)
|
||||
}
|
||||
}
|
||||
|
||||
export { SerializedType, Comparable, JSON, JsonObject }
|
||||
99
packages/ripple-binary-codec/src/types/st-array.ts
Normal file
99
packages/ripple-binary-codec/src/types/st-array.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
import { SerializedType, JsonObject } from './serialized-type'
|
||||
import { STObject } from './st-object'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
const ARRAY_END_MARKER = Buffer.from([0xf1])
|
||||
const ARRAY_END_MARKER_NAME = 'ArrayEndMarker'
|
||||
|
||||
const OBJECT_END_MARKER = Buffer.from([0xe1])
|
||||
|
||||
/**
|
||||
* TypeGuard for Array<JsonObject>
|
||||
*/
|
||||
function isObjects(args): args is Array<JsonObject> {
|
||||
return (
|
||||
Array.isArray(args) && (args.length === 0 || typeof args[0] === 'object')
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing and deserializing Arrays of Objects
|
||||
*/
|
||||
class STArray extends SerializedType {
|
||||
/**
|
||||
* Construct an STArray from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to parse an STArray from
|
||||
* @returns An STArray Object
|
||||
*/
|
||||
static fromParser(parser: BinaryParser): STArray {
|
||||
const bytes: Array<Buffer> = []
|
||||
|
||||
while (!parser.end()) {
|
||||
const field = parser.readField()
|
||||
if (field.name === ARRAY_END_MARKER_NAME) {
|
||||
break
|
||||
}
|
||||
|
||||
bytes.push(
|
||||
field.header,
|
||||
parser.readFieldValue(field).toBytes(),
|
||||
OBJECT_END_MARKER,
|
||||
)
|
||||
}
|
||||
|
||||
bytes.push(ARRAY_END_MARKER)
|
||||
return new STArray(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an STArray from an Array of JSON Objects
|
||||
*
|
||||
* @param value STArray or Array of Objects to parse into an STArray
|
||||
* @returns An STArray object
|
||||
*/
|
||||
static from<T extends STArray | Array<JsonObject>>(value: T): STArray {
|
||||
if (value instanceof STArray) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (isObjects(value)) {
|
||||
const bytes: Array<Buffer> = []
|
||||
value.forEach((obj) => {
|
||||
bytes.push(STObject.from(obj).toBytes())
|
||||
})
|
||||
|
||||
bytes.push(ARRAY_END_MARKER)
|
||||
return new STArray(Buffer.concat(bytes))
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct STArray from value given')
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the JSON representation of this.bytes
|
||||
*
|
||||
* @returns An Array of JSON objects
|
||||
*/
|
||||
toJSON(): Array<JsonObject> {
|
||||
const result: Array<JsonObject> = []
|
||||
|
||||
const arrayParser = new BinaryParser(this.toString())
|
||||
|
||||
while (!arrayParser.end()) {
|
||||
const field = arrayParser.readField()
|
||||
if (field.name === ARRAY_END_MARKER_NAME) {
|
||||
break
|
||||
}
|
||||
|
||||
const outer = {}
|
||||
outer[field.name] = STObject.fromParser(arrayParser).toJSON()
|
||||
result.push(outer)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export { STArray }
|
||||
159
packages/ripple-binary-codec/src/types/st-object.ts
Normal file
159
packages/ripple-binary-codec/src/types/st-object.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
import { Field, FieldInstance } from '../enums'
|
||||
import { SerializedType, JsonObject } from './serialized-type'
|
||||
import { xAddressToClassicAddress, isValidXAddress } from 'ripple-address-codec'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { BinarySerializer, BytesList } from '../serdes/binary-serializer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
const OBJECT_END_MARKER_BYTE = Buffer.from([0xe1])
|
||||
const OBJECT_END_MARKER = 'ObjectEndMarker'
|
||||
const ST_OBJECT = 'STObject'
|
||||
const DESTINATION = 'Destination'
|
||||
const ACCOUNT = 'Account'
|
||||
const SOURCE_TAG = 'SourceTag'
|
||||
const DEST_TAG = 'DestinationTag'
|
||||
|
||||
/**
|
||||
* Break down an X-Address into an account and a tag
|
||||
*
|
||||
* @param field Name of field
|
||||
* @param xAddress X-Address corresponding to the field
|
||||
*/
|
||||
function handleXAddress(field: string, xAddress: string): JsonObject {
|
||||
const decoded = xAddressToClassicAddress(xAddress)
|
||||
|
||||
let tagName
|
||||
if (field === DESTINATION) tagName = DEST_TAG
|
||||
else if (field === ACCOUNT) tagName = SOURCE_TAG
|
||||
else if (decoded.tag !== false)
|
||||
throw new Error(`${field} cannot have an associated tag`)
|
||||
|
||||
return decoded.tag !== false
|
||||
? { [field]: decoded.classicAddress, [tagName]: decoded.tag }
|
||||
: { [field]: decoded.classicAddress }
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that two objects don't both have the same tag fields
|
||||
*
|
||||
* @param obj1 First object to check for tags
|
||||
* @param obj2 Second object to check for tags
|
||||
* @throws When both objects have SourceTag or DestinationTag
|
||||
*/
|
||||
function checkForDuplicateTags(obj1: JsonObject, obj2: JsonObject): void {
|
||||
if (!(obj1[SOURCE_TAG] === undefined || obj2[SOURCE_TAG] === undefined))
|
||||
throw new Error('Cannot have Account X-Address and SourceTag')
|
||||
if (!(obj1[DEST_TAG] === undefined || obj2[DEST_TAG] === undefined))
|
||||
throw new Error('Cannot have Destination X-Address and DestinationTag')
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for Serializing/Deserializing objects
|
||||
*/
|
||||
class STObject extends SerializedType {
|
||||
/**
|
||||
* Construct a STObject from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to read STObject from
|
||||
* @returns A STObject object
|
||||
*/
|
||||
static fromParser(parser: BinaryParser): STObject {
|
||||
const list: BytesList = new BytesList()
|
||||
const bytes: BinarySerializer = new BinarySerializer(list)
|
||||
|
||||
while (!parser.end()) {
|
||||
const field = parser.readField()
|
||||
if (field.name === OBJECT_END_MARKER) {
|
||||
break
|
||||
}
|
||||
|
||||
const associatedValue = parser.readFieldValue(field)
|
||||
|
||||
bytes.writeFieldAndValue(field, associatedValue)
|
||||
if (field.type.name === ST_OBJECT) {
|
||||
bytes.put(OBJECT_END_MARKER_BYTE)
|
||||
}
|
||||
}
|
||||
|
||||
return new STObject(list.toBytes())
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a STObject from a JSON object
|
||||
*
|
||||
* @param value An object to include
|
||||
* @param filter optional, denote which field to include in serialized object
|
||||
* @returns a STObject object
|
||||
*/
|
||||
static from<T extends STObject | JsonObject>(
|
||||
value: T,
|
||||
filter?: (...any) => boolean,
|
||||
): STObject {
|
||||
if (value instanceof STObject) {
|
||||
return value
|
||||
}
|
||||
|
||||
const list: BytesList = new BytesList()
|
||||
const bytes: BinarySerializer = new BinarySerializer(list)
|
||||
|
||||
const xAddressDecoded = Object.entries(value).reduce((acc, [key, val]) => {
|
||||
let handled: JsonObject | undefined = undefined
|
||||
if (val && isValidXAddress(val.toString())) {
|
||||
handled = handleXAddress(key, val.toString())
|
||||
checkForDuplicateTags(handled, value)
|
||||
}
|
||||
return Object.assign(acc, handled ?? { [key]: val })
|
||||
}, {})
|
||||
|
||||
let sorted = Object.keys(xAddressDecoded)
|
||||
.map((f: string): FieldInstance => Field[f] as FieldInstance)
|
||||
.filter(
|
||||
(f: FieldInstance): boolean =>
|
||||
f !== undefined &&
|
||||
xAddressDecoded[f.name] !== undefined &&
|
||||
f.isSerialized,
|
||||
)
|
||||
.sort((a, b) => {
|
||||
return a.ordinal - b.ordinal
|
||||
})
|
||||
|
||||
if (filter !== undefined) {
|
||||
sorted = sorted.filter(filter)
|
||||
}
|
||||
|
||||
sorted.forEach((field) => {
|
||||
const associatedValue = field.associatedType.from(
|
||||
xAddressDecoded[field.name],
|
||||
)
|
||||
|
||||
bytes.writeFieldAndValue(field, associatedValue)
|
||||
if (field.type.name === ST_OBJECT) {
|
||||
bytes.put(OBJECT_END_MARKER_BYTE)
|
||||
}
|
||||
})
|
||||
|
||||
return new STObject(list.toBytes())
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the JSON interpretation of this.bytes
|
||||
*
|
||||
* @returns a JSON object
|
||||
*/
|
||||
toJSON(): JsonObject {
|
||||
const objectParser = new BinaryParser(this.toString())
|
||||
const accumulator = {}
|
||||
|
||||
while (!objectParser.end()) {
|
||||
const field = objectParser.readField()
|
||||
if (field.name === OBJECT_END_MARKER) {
|
||||
break
|
||||
}
|
||||
accumulator[field.name] = objectParser.readFieldValue(field).toJSON()
|
||||
}
|
||||
|
||||
return accumulator
|
||||
}
|
||||
}
|
||||
|
||||
export { STObject }
|
||||
49
packages/ripple-binary-codec/src/types/uint-16.ts
Normal file
49
packages/ripple-binary-codec/src/types/uint-16.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { UInt } from './uint'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Derived UInt class for serializing/deserializing 16 bit UInt
|
||||
*/
|
||||
class UInt16 extends UInt {
|
||||
protected static readonly width: number = 16 / 8 // 2
|
||||
static readonly defaultUInt16: UInt16 = new UInt16(Buffer.alloc(UInt16.width))
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? UInt16.defaultUInt16.bytes)
|
||||
}
|
||||
|
||||
static fromParser(parser: BinaryParser): UInt {
|
||||
return new UInt16(parser.read(UInt16.width))
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a UInt16 object from a number
|
||||
*
|
||||
* @param val UInt16 object or number
|
||||
*/
|
||||
static from<T extends UInt16 | number>(val: T): UInt16 {
|
||||
if (val instanceof UInt16) {
|
||||
return val
|
||||
}
|
||||
|
||||
if (typeof val === 'number') {
|
||||
const buf = Buffer.alloc(UInt16.width)
|
||||
buf.writeUInt16BE(val, 0)
|
||||
return new UInt16(buf)
|
||||
}
|
||||
|
||||
throw new Error('Can not construct UInt16 with given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value of a UInt16 object
|
||||
*
|
||||
* @returns the number represented by this.bytes
|
||||
*/
|
||||
valueOf(): number {
|
||||
return this.bytes.readUInt16BE(0)
|
||||
}
|
||||
}
|
||||
|
||||
export { UInt16 }
|
||||
56
packages/ripple-binary-codec/src/types/uint-32.ts
Normal file
56
packages/ripple-binary-codec/src/types/uint-32.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { UInt } from './uint'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Derived UInt class for serializing/deserializing 32 bit UInt
|
||||
*/
|
||||
class UInt32 extends UInt {
|
||||
protected static readonly width: number = 32 / 8 // 4
|
||||
static readonly defaultUInt32: UInt32 = new UInt32(Buffer.alloc(UInt32.width))
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? UInt32.defaultUInt32.bytes)
|
||||
}
|
||||
|
||||
static fromParser(parser: BinaryParser): UInt {
|
||||
return new UInt32(parser.read(UInt32.width))
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a UInt32 object from a number
|
||||
*
|
||||
* @param val UInt32 object or number
|
||||
*/
|
||||
static from<T extends UInt32 | number | string>(val: T): UInt32 {
|
||||
if (val instanceof UInt32) {
|
||||
return val
|
||||
}
|
||||
|
||||
const buf = Buffer.alloc(UInt32.width)
|
||||
|
||||
if (typeof val === 'string') {
|
||||
const num = Number.parseInt(val)
|
||||
buf.writeUInt32BE(num, 0)
|
||||
return new UInt32(buf)
|
||||
}
|
||||
|
||||
if (typeof val === 'number') {
|
||||
buf.writeUInt32BE(val, 0)
|
||||
return new UInt32(buf)
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct UInt32 from given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value of a UInt32 object
|
||||
*
|
||||
* @returns the number represented by this.bytes
|
||||
*/
|
||||
valueOf(): number {
|
||||
return this.bytes.readUInt32BE(0)
|
||||
}
|
||||
}
|
||||
|
||||
export { UInt32 }
|
||||
105
packages/ripple-binary-codec/src/types/uint-64.ts
Normal file
105
packages/ripple-binary-codec/src/types/uint-64.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { UInt } from './uint'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import * as bigInt from 'big-integer'
|
||||
import { isInstance } from 'big-integer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/
|
||||
const mask = bigInt(0x00000000ffffffff)
|
||||
|
||||
/**
|
||||
* Derived UInt class for serializing/deserializing 64 bit UInt
|
||||
*/
|
||||
class UInt64 extends UInt {
|
||||
protected static readonly width: number = 64 / 8 // 8
|
||||
static readonly defaultUInt64: UInt64 = new UInt64(Buffer.alloc(UInt64.width))
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? UInt64.defaultUInt64.bytes)
|
||||
}
|
||||
|
||||
static fromParser(parser: BinaryParser): UInt {
|
||||
return new UInt64(parser.read(UInt64.width))
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a UInt64 object
|
||||
*
|
||||
* @param val A UInt64, hex-string, bigInt, or number
|
||||
* @returns A UInt64 object
|
||||
*/
|
||||
static from<T extends UInt64 | string | bigInt.BigInteger | number>(
|
||||
val: T,
|
||||
): UInt64 {
|
||||
if (val instanceof UInt64) {
|
||||
return val
|
||||
}
|
||||
|
||||
let buf = Buffer.alloc(UInt64.width)
|
||||
|
||||
if (typeof val === 'number') {
|
||||
if (val < 0) {
|
||||
throw new Error('value must be an unsigned integer')
|
||||
}
|
||||
|
||||
const number = bigInt(val)
|
||||
|
||||
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
|
||||
intBuf[0].writeUInt32BE(Number(number.shiftRight(32)), 0)
|
||||
intBuf[1].writeUInt32BE(Number(number.and(mask)), 0)
|
||||
|
||||
return new UInt64(Buffer.concat(intBuf))
|
||||
}
|
||||
|
||||
if (typeof val === 'string') {
|
||||
if (!HEX_REGEX.test(val)) {
|
||||
throw new Error(`${val} is not a valid hex-string`)
|
||||
}
|
||||
|
||||
const strBuf = val.padStart(16, '0')
|
||||
buf = Buffer.from(strBuf, 'hex')
|
||||
return new UInt64(buf)
|
||||
}
|
||||
|
||||
if (isInstance(val)) {
|
||||
const intBuf = [Buffer.alloc(4), Buffer.alloc(4)]
|
||||
intBuf[0].writeUInt32BE(Number(val.shiftRight(bigInt(32))), 0)
|
||||
intBuf[1].writeUInt32BE(Number(val.and(mask)), 0)
|
||||
|
||||
return new UInt64(Buffer.concat(intBuf))
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct UInt64 from given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* The JSON representation of a UInt64 object
|
||||
*
|
||||
* @returns a hex-string
|
||||
*/
|
||||
toJSON(): string {
|
||||
return this.bytes.toString('hex').toUpperCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the UInt64
|
||||
*
|
||||
* @returns the number represented buy this.bytes
|
||||
*/
|
||||
valueOf(): bigInt.BigInteger {
|
||||
const msb = bigInt(this.bytes.slice(0, 4).readUInt32BE(0))
|
||||
const lsb = bigInt(this.bytes.slice(4).readUInt32BE(0))
|
||||
return msb.shiftLeft(bigInt(32)).or(lsb)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bytes representation of the UInt64 object
|
||||
*
|
||||
* @returns 8 bytes representing the UInt64
|
||||
*/
|
||||
toBytes(): Buffer {
|
||||
return this.bytes
|
||||
}
|
||||
}
|
||||
|
||||
export { UInt64 }
|
||||
49
packages/ripple-binary-codec/src/types/uint-8.ts
Normal file
49
packages/ripple-binary-codec/src/types/uint-8.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { UInt } from './uint'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Derived UInt class for serializing/deserializing 8 bit UInt
|
||||
*/
|
||||
class UInt8 extends UInt {
|
||||
protected static readonly width: number = 8 / 8 // 1
|
||||
static readonly defaultUInt8: UInt8 = new UInt8(Buffer.alloc(UInt8.width))
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes ?? UInt8.defaultUInt8.bytes)
|
||||
}
|
||||
|
||||
static fromParser(parser: BinaryParser): UInt {
|
||||
return new UInt8(parser.read(UInt8.width))
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a UInt8 object from a number
|
||||
*
|
||||
* @param val UInt8 object or number
|
||||
*/
|
||||
static from<T extends UInt8 | number>(val: T): UInt8 {
|
||||
if (val instanceof UInt8) {
|
||||
return val
|
||||
}
|
||||
|
||||
if (typeof val === 'number') {
|
||||
const buf = Buffer.alloc(UInt8.width)
|
||||
buf.writeUInt8(val, 0)
|
||||
return new UInt8(buf)
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct UInt8 from given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* get the value of a UInt8 object
|
||||
*
|
||||
* @returns the number represented by this.bytes
|
||||
*/
|
||||
valueOf(): number {
|
||||
return this.bytes.readUInt8(0)
|
||||
}
|
||||
}
|
||||
|
||||
export { UInt8 }
|
||||
57
packages/ripple-binary-codec/src/types/uint.ts
Normal file
57
packages/ripple-binary-codec/src/types/uint.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as bigInt from 'big-integer'
|
||||
import { Comparable } from './serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* Compare numbers and bigInts n1 and n2
|
||||
*
|
||||
* @param n1 First object to compare
|
||||
* @param n2 Second object to compare
|
||||
* @returns -1, 0, or 1, depending on how the two objects compare
|
||||
*/
|
||||
function compare(
|
||||
n1: number | bigInt.BigInteger,
|
||||
n2: number | bigInt.BigInteger,
|
||||
): number {
|
||||
return n1 < n2 ? -1 : n1 == n2 ? 0 : 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for serializing and deserializing unsigned integers.
|
||||
*/
|
||||
abstract class UInt extends Comparable {
|
||||
protected static width: number
|
||||
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload of compareTo for Comparable
|
||||
*
|
||||
* @param other other UInt to compare this to
|
||||
* @returns -1, 0, or 1 depending on how the objects relate to each other
|
||||
*/
|
||||
compareTo(other: UInt): number {
|
||||
return compare(this.valueOf(), other.valueOf())
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UInt object to JSON
|
||||
*
|
||||
* @returns number or string represented by this.bytes
|
||||
*/
|
||||
toJSON(): number | string {
|
||||
const val = this.valueOf()
|
||||
return typeof val === 'number' ? val : val.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the value of the UInt represented by this.bytes
|
||||
*
|
||||
* @returns the value
|
||||
*/
|
||||
abstract valueOf(): number | bigInt.BigInteger
|
||||
}
|
||||
|
||||
export { UInt }
|
||||
84
packages/ripple-binary-codec/src/types/vector-256.ts
Normal file
84
packages/ripple-binary-codec/src/types/vector-256.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { SerializedType } from './serialized-type'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { Hash256 } from './hash-256'
|
||||
import { BytesList } from '../serdes/binary-serializer'
|
||||
import { Buffer } from 'buffer/'
|
||||
|
||||
/**
|
||||
* TypeGuard for Array<string>
|
||||
*/
|
||||
function isStrings(arg): arg is Array<string> {
|
||||
return Array.isArray(arg) && (arg.length === 0 || typeof arg[0] === 'string')
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing and deserializing vectors of Hash256
|
||||
*/
|
||||
class Vector256 extends SerializedType {
|
||||
constructor(bytes: Buffer) {
|
||||
super(bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Vector256 from a BinaryParser
|
||||
*
|
||||
* @param parser BinaryParser to
|
||||
* @param hint length of the vector, in bytes, optional
|
||||
* @returns a Vector256 object
|
||||
*/
|
||||
static fromParser(parser: BinaryParser, hint?: number): Vector256 {
|
||||
const bytesList = new BytesList()
|
||||
const bytes = hint ?? parser.size()
|
||||
const hashes = bytes / 32
|
||||
for (let i = 0; i < hashes; i++) {
|
||||
Hash256.fromParser(parser).toBytesSink(bytesList)
|
||||
}
|
||||
return new Vector256(bytesList.toBytes())
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a Vector256 object from an array of hashes
|
||||
*
|
||||
* @param value A Vector256 object or array of hex-strings representing Hash256's
|
||||
* @returns a Vector256 object
|
||||
*/
|
||||
static from<T extends Vector256 | Array<string>>(value: T): Vector256 {
|
||||
if (value instanceof Vector256) {
|
||||
return value
|
||||
}
|
||||
|
||||
if (isStrings(value)) {
|
||||
const bytesList = new BytesList()
|
||||
value.forEach((hash) => {
|
||||
Hash256.from(hash).toBytesSink(bytesList)
|
||||
})
|
||||
return new Vector256(bytesList.toBytes())
|
||||
}
|
||||
|
||||
throw new Error('Cannot construct Vector256 from given value')
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an Array of hex-strings represented by this.bytes
|
||||
*
|
||||
* @returns An Array of strings representing the Hash256 objects
|
||||
*/
|
||||
toJSON(): Array<string> {
|
||||
if (this.bytes.byteLength % 32 !== 0) {
|
||||
throw new Error('Invalid bytes for Vector256')
|
||||
}
|
||||
|
||||
const result: Array<string> = []
|
||||
for (let i = 0; i < this.bytes.byteLength; i += 32) {
|
||||
result.push(
|
||||
this.bytes
|
||||
.slice(i, i + 32)
|
||||
.toString('hex')
|
||||
.toUpperCase(),
|
||||
)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
export { Vector256 }
|
||||
43
packages/ripple-binary-codec/test/amount.test.js
Normal file
43
packages/ripple-binary-codec/test/amount.test.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { loadFixture } = require('./utils')
|
||||
const { coreTypes } = require('../dist/types')
|
||||
const { Amount } = coreTypes
|
||||
const fixtures = loadFixture('data-driven-tests.json')
|
||||
|
||||
function amountErrorTests() {
|
||||
fixtures.values_tests
|
||||
.filter((obj) => obj.type === 'Amount')
|
||||
.forEach((f) => {
|
||||
// We only want these with errors
|
||||
if (!f.error) {
|
||||
return
|
||||
}
|
||||
const testName =
|
||||
`${JSON.stringify(f.test_json)}\n\tis invalid ` + `because: ${f.error}`
|
||||
it(testName, () => {
|
||||
expect(() => {
|
||||
Amount.from(f.test_json)
|
||||
JSON.stringify(f.test_json)
|
||||
}).toThrow()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('Amount', function () {
|
||||
it('can be parsed from', function () {
|
||||
expect(Amount.from('1000000') instanceof Amount).toBe(true)
|
||||
expect(Amount.from('1000000').toJSON()).toEqual('1000000')
|
||||
const fixture = {
|
||||
value: '1',
|
||||
issuer: '0000000000000000000000000000000000000000',
|
||||
currency: 'USD',
|
||||
}
|
||||
const amt = Amount.from(fixture)
|
||||
const rewritten = {
|
||||
value: '1',
|
||||
issuer: 'rrrrrrrrrrrrrrrrrrrrrhoLvTp',
|
||||
currency: 'USD',
|
||||
}
|
||||
expect(amt.toJSON()).toEqual(rewritten)
|
||||
})
|
||||
amountErrorTests()
|
||||
})
|
||||
45
packages/ripple-binary-codec/test/binary-json.test.js
Normal file
45
packages/ripple-binary-codec/test/binary-json.test.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const fixtures = require('./fixtures/codec-fixtures.json')
|
||||
const { decode, encode, decodeLedgerData } = require('../dist')
|
||||
|
||||
function json(object) {
|
||||
return JSON.stringify(object)
|
||||
}
|
||||
|
||||
function truncateForDisplay(longStr) {
|
||||
return `${longStr.slice(0, 10)} ... ${longStr.slice(-10)}`
|
||||
}
|
||||
|
||||
describe('ripple-binary-codec', function () {
|
||||
function makeSuite(name, entries) {
|
||||
describe(name, function () {
|
||||
entries.forEach((t, testN) => {
|
||||
test(`${name}[${testN}] can encode ${truncateForDisplay(
|
||||
json(t.json),
|
||||
)} to ${truncateForDisplay(t.binary)}`, () => {
|
||||
expect(encode(t.json)).toEqual(t.binary)
|
||||
})
|
||||
test(`${name}[${testN}] can decode ${truncateForDisplay(
|
||||
t.binary,
|
||||
)} to ${truncateForDisplay(json(t.json))}`, () => {
|
||||
const decoded = decode(t.binary)
|
||||
expect(decoded).toEqual(t.json)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
makeSuite('transactions', fixtures.transactions)
|
||||
makeSuite('accountState', fixtures.accountState)
|
||||
|
||||
describe('ledgerData', function () {
|
||||
if (fixtures.ledgerData) {
|
||||
fixtures.ledgerData.forEach((t, testN) => {
|
||||
test(`ledgerData[${testN}] can decode ${t.binary} to ${json(
|
||||
t.json,
|
||||
)}`, () => {
|
||||
const decoded = decodeLedgerData(t.binary)
|
||||
expect(t.json).toEqual(decoded)
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
396
packages/ripple-binary-codec/test/binary-parser.test.js
Normal file
396
packages/ripple-binary-codec/test/binary-parser.test.js
Normal file
@@ -0,0 +1,396 @@
|
||||
const { coreTypes } = require('../dist/types')
|
||||
const Decimal = require('decimal.js')
|
||||
|
||||
const { encodeAccountID } = require('ripple-address-codec')
|
||||
const { binary } = require('../dist/coretypes')
|
||||
const { Amount, Hash160 } = coreTypes
|
||||
const { makeParser, readJSON } = binary
|
||||
const { Field, TransactionType } = require('./../dist/enums')
|
||||
const { parseHexOnly, hexOnly, loadFixture } = require('./utils')
|
||||
const fixtures = loadFixture('data-driven-tests.json')
|
||||
const { BytesList } = require('../dist/serdes/binary-serializer')
|
||||
const { Buffer } = require('buffer/')
|
||||
|
||||
const __ = hexOnly
|
||||
function toJSON(v) {
|
||||
return v.toJSON ? v.toJSON() : v
|
||||
}
|
||||
|
||||
function assertEqualAmountJSON(actual, expected) {
|
||||
expect(typeof actual === typeof expected).toBe(true)
|
||||
if (typeof actual === 'string') {
|
||||
expect(actual).toEqual(expected)
|
||||
return
|
||||
}
|
||||
expect(actual.currency).toEqual(expected.currency)
|
||||
expect(actual.issuer).toEqual(expected.issuer)
|
||||
expect(
|
||||
actual.value === expected.value ||
|
||||
new Decimal(actual.value).equals(new Decimal(expected.value)),
|
||||
).toBe(true)
|
||||
}
|
||||
|
||||
function basicApiTests() {
|
||||
const bytes = parseHexOnly('00,01020304,0506', Uint8Array)
|
||||
test('can read slices of bytes', () => {
|
||||
const parser = makeParser(bytes)
|
||||
expect(parser.bytes instanceof Buffer).toBe(true)
|
||||
const read1 = parser.read(1)
|
||||
expect(read1 instanceof Buffer).toBe(true)
|
||||
expect(read1).toEqual(Buffer.from([0]))
|
||||
expect(parser.read(4)).toEqual(Buffer.from([1, 2, 3, 4]))
|
||||
expect(parser.read(2)).toEqual(Buffer.from([5, 6]))
|
||||
expect(() => parser.read(1)).toThrow()
|
||||
})
|
||||
test('can read a Uint32 at full', () => {
|
||||
const parser = makeParser('FFFFFFFF')
|
||||
expect(parser.readUInt32()).toEqual(0xffffffff)
|
||||
})
|
||||
}
|
||||
|
||||
function transactionParsingTests() {
|
||||
const transaction = {
|
||||
json: {
|
||||
Account: 'raD5qJMAShLeHZXf9wjUmo6vRK4arj9cF3',
|
||||
Fee: '10',
|
||||
Flags: 0,
|
||||
Sequence: 103929,
|
||||
SigningPubKey:
|
||||
'028472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F418D6A7166',
|
||||
TakerGets: {
|
||||
currency: 'ILS',
|
||||
issuer: 'rNPRNzBB92BVpAhhZr4iXDTveCgV5Pofm9',
|
||||
value: '1694.768',
|
||||
},
|
||||
TakerPays: '98957503520',
|
||||
TransactionType: 'OfferCreate',
|
||||
TxnSignature: __(`
|
||||
304502202ABE08D5E78D1E74A4C18F2714F64E87B8BD57444AF
|
||||
A5733109EB3C077077520022100DB335EE97386E4C0591CAC02
|
||||
4D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C`),
|
||||
},
|
||||
binary: __(`
|
||||
120007220000000024000195F964400000170A53AC2065D5460561E
|
||||
C9DE000000000000000000000000000494C53000000000092D70596
|
||||
8936C419CE614BF264B5EEB1CEA47FF468400000000000000A73210
|
||||
28472865AF4CB32AA285834B57576B7290AA8C31B459047DB27E16F
|
||||
418D6A71667447304502202ABE08D5E78D1E74A4C18F2714F64E87B
|
||||
8BD57444AFA5733109EB3C077077520022100DB335EE97386E4C059
|
||||
1CAC024D50E9230D8F171EEB901B5E5E4BD6D1E0AEF98C811439408
|
||||
A69F0895E62149CFCC006FB89FA7D1E6E5D`),
|
||||
}
|
||||
|
||||
const tx_json = transaction.json
|
||||
// These tests are basically development logs
|
||||
|
||||
test('can be done with low level apis', () => {
|
||||
const parser = makeParser(transaction.binary)
|
||||
|
||||
expect(parser.readField()).toEqual(Field.TransactionType)
|
||||
expect(parser.readUInt16()).toEqual(7)
|
||||
expect(parser.readField()).toEqual(Field.Flags)
|
||||
expect(parser.readUInt32()).toEqual(0)
|
||||
expect(parser.readField()).toEqual(Field.Sequence)
|
||||
expect(parser.readUInt32()).toEqual(103929)
|
||||
expect(parser.readField()).toEqual(Field.TakerPays)
|
||||
parser.read(8)
|
||||
expect(parser.readField()).toEqual(Field.TakerGets)
|
||||
// amount value
|
||||
expect(parser.read(8)).not.toBe([])
|
||||
// amount currency
|
||||
expect(Hash160.fromParser(parser)).not.toBe([])
|
||||
expect(encodeAccountID(parser.read(20))).toEqual(tx_json.TakerGets.issuer)
|
||||
expect(parser.readField()).toEqual(Field.Fee)
|
||||
expect(parser.read(8)).not.toEqual([])
|
||||
expect(parser.readField()).toEqual(Field.SigningPubKey)
|
||||
expect(parser.readVariableLengthLength()).toBe(33)
|
||||
expect(parser.read(33).toString('hex').toUpperCase()).toEqual(
|
||||
tx_json.SigningPubKey,
|
||||
)
|
||||
expect(parser.readField()).toEqual(Field.TxnSignature)
|
||||
expect(parser.readVariableLength().toString('hex').toUpperCase()).toEqual(
|
||||
tx_json.TxnSignature,
|
||||
)
|
||||
expect(parser.readField()).toEqual(Field.Account)
|
||||
expect(encodeAccountID(parser.readVariableLength())).toEqual(
|
||||
tx_json.Account,
|
||||
)
|
||||
expect(parser.end()).toBe(true)
|
||||
})
|
||||
|
||||
test('can be done with high level apis', () => {
|
||||
const parser = makeParser(transaction.binary)
|
||||
function readField() {
|
||||
return parser.readFieldAndValue()
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.TransactionType)
|
||||
expect(value).toEqual(TransactionType.OfferCreate)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.Flags)
|
||||
expect(value.valueOf()).toEqual(0)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.Sequence)
|
||||
expect(value.valueOf()).toEqual(103929)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.TakerPays)
|
||||
expect(value.isNative()).toEqual(true)
|
||||
expect(value.toJSON()).toEqual('98957503520')
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.TakerGets)
|
||||
expect(value.isNative()).toEqual(false)
|
||||
expect(value.toJSON().issuer).toEqual(tx_json.TakerGets.issuer)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.Fee)
|
||||
expect(value.isNative()).toEqual(true)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.SigningPubKey)
|
||||
expect(value.toJSON()).toEqual(tx_json.SigningPubKey)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.TxnSignature)
|
||||
expect(value.toJSON()).toEqual(tx_json.TxnSignature)
|
||||
}
|
||||
{
|
||||
const [field, value] = readField()
|
||||
expect(field).toEqual(Field.Account)
|
||||
expect(value.toJSON()).toEqual(tx_json.Account)
|
||||
}
|
||||
expect(parser.end()).toBe(true)
|
||||
})
|
||||
|
||||
test('can be done with higher level apis', () => {
|
||||
const parser = makeParser(transaction.binary)
|
||||
const jsonFromBinary = readJSON(parser)
|
||||
expect(jsonFromBinary).toEqual(tx_json)
|
||||
})
|
||||
|
||||
test('readJSON (binary.decode) does not return STObject ', () => {
|
||||
const parser = makeParser(transaction.binary)
|
||||
const jsonFromBinary = readJSON(parser)
|
||||
expect(jsonFromBinary instanceof coreTypes.STObject).toBe(false)
|
||||
expect(jsonFromBinary instanceof Object).toBe(true)
|
||||
expect(jsonFromBinary.prototype).toBe(undefined)
|
||||
})
|
||||
}
|
||||
|
||||
function amountParsingTests() {
|
||||
fixtures.values_tests
|
||||
.filter((obj) => obj.type === 'Amount')
|
||||
.forEach((f, i) => {
|
||||
if (f.error) {
|
||||
return
|
||||
}
|
||||
const parser = makeParser(f.expected_hex)
|
||||
const testName = `values_tests[${i}] parses ${f.expected_hex.slice(
|
||||
0,
|
||||
16,
|
||||
)}...
|
||||
as ${JSON.stringify(f.test_json)}`
|
||||
test(testName, () => {
|
||||
const value = parser.readType(Amount)
|
||||
// May not actually be in canonical form. The fixtures are to be used
|
||||
// also for json -> binary;
|
||||
const json = toJSON(value)
|
||||
assertEqualAmountJSON(json, f.test_json)
|
||||
if (f.exponent) {
|
||||
const exponent = new Decimal(json.value)
|
||||
expect(exponent.e - 15).toEqual(f.exponent)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function fieldParsingTests() {
|
||||
fixtures.fields_tests.forEach((f, i) => {
|
||||
const parser = makeParser(f.expected_hex)
|
||||
test(`fields[${i}]: parses ${f.expected_hex} as ${f.name}`, () => {
|
||||
const field = parser.readField()
|
||||
expect(field.name).toEqual(f.name)
|
||||
expect(field.type.name).toEqual(f.type_name)
|
||||
})
|
||||
})
|
||||
test('Field throws when type code out of range', () => {
|
||||
const parser = makeParser('0101')
|
||||
expect(() => parser.readField()).toThrow(
|
||||
new Error('Cannot read FieldOrdinal, type_code out of range'),
|
||||
)
|
||||
})
|
||||
test('Field throws when field code out of range', () => {
|
||||
const parser = makeParser('1001')
|
||||
expect(() => parser.readFieldOrdinal()).toThrowError(
|
||||
new Error('Cannot read FieldOrdinal, field_code out of range'),
|
||||
)
|
||||
})
|
||||
test('Field throws when both type and field code out of range', () => {
|
||||
const parser = makeParser('000101')
|
||||
expect(() => parser.readFieldOrdinal()).toThrowError(
|
||||
new Error('Cannot read FieldOrdinal, type_code out of range'),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function assertRecyclable(json, forField) {
|
||||
const Type = forField.associatedType
|
||||
const recycled = Type.from(json).toJSON()
|
||||
expect(recycled).toEqual(json)
|
||||
const sink = new BytesList()
|
||||
Type.from(recycled).toBytesSink(sink)
|
||||
const recycledAgain = makeParser(sink.toHex()).readType(Type).toJSON()
|
||||
expect(recycledAgain).toEqual(json)
|
||||
}
|
||||
|
||||
function nestedObjectTests() {
|
||||
fixtures.whole_objects.forEach((f, i) => {
|
||||
test(`whole_objects[${i}]: can parse blob into
|
||||
${JSON.stringify(
|
||||
f.tx_json,
|
||||
)}`, /* */ () => {
|
||||
const parser = makeParser(f.blob_with_no_signing)
|
||||
let ix = 0
|
||||
while (!parser.end()) {
|
||||
const [field, value] = parser.readFieldAndValue()
|
||||
const expected = f.fields[ix]
|
||||
const expectedJSON = expected[1].json
|
||||
const expectedField = expected[0]
|
||||
const actual = toJSON(value)
|
||||
|
||||
try {
|
||||
expect(actual).toEqual(expectedJSON)
|
||||
} catch (e) {
|
||||
throw new Error(`${e} ${field} a: ${actual} e: ${expectedJSON}`)
|
||||
}
|
||||
expect(field.name).toEqual(expectedField)
|
||||
assertRecyclable(actual, field)
|
||||
ix++
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function pathSetBinaryTests() {
|
||||
const bytes = __(
|
||||
`1200002200000000240000002E2E00004BF161D4C71AFD498D00000000000000
|
||||
0000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA0
|
||||
6594D168400000000000000A69D446F8038585E9400000000000000000000000
|
||||
00425443000000000078CA21A6014541AB7B26C3929B9E0CD8C284D61C732103
|
||||
A4665B1F0B7AE2BCA12E2DB80A192125BBEA660F80E9CEE137BA444C1B0769EC
|
||||
7447304502205A964536805E35785C659D1F9670D057749AE39668175D6AA75D
|
||||
25B218FE682E0221009252C0E5DDD5F2712A48F211669DE17B54113918E0D2C2
|
||||
66F818095E9339D7D3811478CA21A6014541AB7B26C3929B9E0CD8C284D61C83
|
||||
140A20B3C85F482532A9578DBB3950B85CA06594D1011231585E1F3BD02A15D6
|
||||
185F8BB9B57CC60DEDDB37C10000000000000000000000004254430000000000
|
||||
585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D694C
|
||||
8531CDEECBE84F33670000000000000000000000004254430000000000E4FE68
|
||||
7C90257D3D2D694C8531CDEECBE84F3367310A20B3C85F482532A9578DBB3950
|
||||
B85CA06594D100000000000000000000000042544300000000000A20B3C85F48
|
||||
2532A9578DBB3950B85CA06594D1300000000000000000000000005553440000
|
||||
0000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A15
|
||||
D6185F8BB9B57CC60DEDDB37C100000000000000000000000042544300000000
|
||||
00585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C131E4FE687C90257D3D2D69
|
||||
4C8531CDEECBE84F33670000000000000000000000004254430000000000E4FE
|
||||
687C90257D3D2D694C8531CDEECBE84F33673115036E2D3F5437A83E5AC3CAEE
|
||||
34FF2C21DEB618000000000000000000000000425443000000000015036E2D3F
|
||||
5437A83E5AC3CAEE34FF2C21DEB6183000000000000000000000000055534400
|
||||
000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF31585E1F3BD02A
|
||||
15D6185F8BB9B57CC60DEDDB37C1000000000000000000000000425443000000
|
||||
0000585E1F3BD02A15D6185F8BB9B57CC60DEDDB37C13157180C769B66D942EE
|
||||
69E6DCC940CA48D82337AD000000000000000000000000425443000000000057
|
||||
180C769B66D942EE69E6DCC940CA48D82337AD10000000000000000000000000
|
||||
00000000000000003000000000000000000000000055534400000000000A20B3
|
||||
C85F482532A9578DBB3950B85CA06594D100`,
|
||||
)
|
||||
|
||||
const expectedJSON = [
|
||||
[
|
||||
{
|
||||
account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
|
||||
currency: 'BTC',
|
||||
issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
|
||||
},
|
||||
{
|
||||
account: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
|
||||
currency: 'BTC',
|
||||
issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
|
||||
},
|
||||
{
|
||||
account: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
currency: 'BTC',
|
||||
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
},
|
||||
{
|
||||
currency: 'USD',
|
||||
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
|
||||
currency: 'BTC',
|
||||
issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
|
||||
},
|
||||
{
|
||||
account: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
|
||||
currency: 'BTC',
|
||||
issuer: 'rM1oqKtfh1zgjdAgbFmaRm3btfGBX25xVo',
|
||||
},
|
||||
{
|
||||
account: 'rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi',
|
||||
currency: 'BTC',
|
||||
issuer: 'rpvfJ4mR6QQAeogpXEKnuyGBx8mYCSnYZi',
|
||||
},
|
||||
{
|
||||
currency: 'USD',
|
||||
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
account: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
|
||||
currency: 'BTC',
|
||||
issuer: 'r9hEDb4xBGRfBCcX3E4FirDWQBAYtpxC8K',
|
||||
},
|
||||
{
|
||||
account: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn',
|
||||
currency: 'BTC',
|
||||
issuer: 'r3AWbdp2jQLXLywJypdoNwVSvr81xs3uhn',
|
||||
},
|
||||
{ currency: 'XRP' },
|
||||
{
|
||||
currency: 'USD',
|
||||
issuer: 'rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B',
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
test('works with long paths', () => {
|
||||
const parser = makeParser(bytes)
|
||||
const txn = readJSON(parser)
|
||||
expect(txn.Paths).toEqual(expectedJSON)
|
||||
// TODO: this should go elsewhere
|
||||
expect(coreTypes.PathSet.from(txn.Paths).toJSON()).toEqual(expectedJSON)
|
||||
})
|
||||
}
|
||||
|
||||
describe('Binary Parser', function () {
|
||||
describe('pathSetBinaryTests', () => pathSetBinaryTests())
|
||||
describe('nestedObjectTests', () => nestedObjectTests())
|
||||
describe('fieldParsingTests', () => fieldParsingTests())
|
||||
describe('amountParsingTests', () => amountParsingTests())
|
||||
describe('transactionParsingTests', () => transactionParsingTests())
|
||||
describe('basicApiTests', () => basicApiTests())
|
||||
})
|
||||
289
packages/ripple-binary-codec/test/binary-serializer.test.js
Normal file
289
packages/ripple-binary-codec/test/binary-serializer.test.js
Normal file
@@ -0,0 +1,289 @@
|
||||
const { binary } = require('../dist/coretypes')
|
||||
const { encode, decode } = require('../dist')
|
||||
const { makeParser, BytesList, BinarySerializer } = binary
|
||||
const { coreTypes } = require('../dist/types')
|
||||
const { UInt8, UInt16, UInt32, UInt64, STObject } = coreTypes
|
||||
const bigInt = require('big-integer')
|
||||
const { Buffer } = require('buffer/')
|
||||
|
||||
const { loadFixture } = require('./utils')
|
||||
const fixtures = loadFixture('data-driven-tests.json')
|
||||
const deliverMinTx = require('./fixtures/delivermin-tx.json')
|
||||
const deliverMinTxBinary = require('./fixtures/delivermin-tx-binary.json')
|
||||
const SignerListSet = {
|
||||
tx: require('./fixtures/signerlistset-tx.json'),
|
||||
binary: require('./fixtures/signerlistset-tx-binary.json'),
|
||||
meta: require('./fixtures/signerlistset-tx-meta-binary.json'),
|
||||
}
|
||||
const DepositPreauth = {
|
||||
tx: require('./fixtures/deposit-preauth-tx.json'),
|
||||
binary: require('./fixtures/deposit-preauth-tx-binary.json'),
|
||||
meta: require('./fixtures/deposit-preauth-tx-meta-binary.json'),
|
||||
}
|
||||
const Escrow = {
|
||||
create: {
|
||||
tx: require('./fixtures/escrow-create-tx.json'),
|
||||
binary: require('./fixtures/escrow-create-binary.json'),
|
||||
},
|
||||
finish: {
|
||||
tx: require('./fixtures/escrow-finish-tx.json'),
|
||||
binary: require('./fixtures/escrow-finish-binary.json'),
|
||||
meta: require('./fixtures/escrow-finish-meta-binary.json'),
|
||||
},
|
||||
cancel: {
|
||||
tx: require('./fixtures/escrow-cancel-tx.json'),
|
||||
binary: require('./fixtures/escrow-cancel-binary.json'),
|
||||
},
|
||||
}
|
||||
const PaymentChannel = {
|
||||
create: {
|
||||
tx: require('./fixtures/payment-channel-create-tx.json'),
|
||||
binary: require('./fixtures/payment-channel-create-binary.json'),
|
||||
},
|
||||
fund: {
|
||||
tx: require('./fixtures/payment-channel-fund-tx.json'),
|
||||
binary: require('./fixtures/payment-channel-fund-binary.json'),
|
||||
},
|
||||
claim: {
|
||||
tx: require('./fixtures/payment-channel-claim-tx.json'),
|
||||
binary: require('./fixtures/payment-channel-claim-binary.json'),
|
||||
},
|
||||
}
|
||||
|
||||
const Ticket = {
|
||||
create: {
|
||||
tx: require('./fixtures/ticket-create-tx.json'),
|
||||
binary: require('./fixtures/ticket-create-binary.json'),
|
||||
},
|
||||
}
|
||||
|
||||
let json_undefined = {
|
||||
TakerPays: '223174650',
|
||||
Account: 'rPk2dXr27rMw9G5Ej9ad2Tt7RJzGy8ycBp',
|
||||
TransactionType: 'OfferCreate',
|
||||
Memos: [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: '584D4D2076616C7565',
|
||||
MemoData: '322E3230393635',
|
||||
MemoFormat: undefined,
|
||||
},
|
||||
},
|
||||
],
|
||||
Fee: '15',
|
||||
OfferSequence: undefined,
|
||||
TakerGets: {
|
||||
currency: 'XMM',
|
||||
value: '100',
|
||||
issuer: 'rExAPEZvbkZqYPuNcZ7XEBLENEshsWDQc8',
|
||||
},
|
||||
Flags: 524288,
|
||||
Sequence: undefined,
|
||||
LastLedgerSequence: 6220135,
|
||||
}
|
||||
|
||||
let json_omitted = {
|
||||
TakerPays: '223174650',
|
||||
Account: 'rPk2dXr27rMw9G5Ej9ad2Tt7RJzGy8ycBp',
|
||||
TransactionType: 'OfferCreate',
|
||||
Memos: [
|
||||
{
|
||||
Memo: {
|
||||
MemoType: '584D4D2076616C7565',
|
||||
MemoData: '322E3230393635',
|
||||
},
|
||||
},
|
||||
],
|
||||
Fee: '15',
|
||||
TakerGets: {
|
||||
currency: 'XMM',
|
||||
value: '100',
|
||||
issuer: 'rExAPEZvbkZqYPuNcZ7XEBLENEshsWDQc8',
|
||||
},
|
||||
Flags: 524288,
|
||||
LastLedgerSequence: 6220135,
|
||||
}
|
||||
|
||||
const NegativeUNL = require('./fixtures/negative-unl.json')
|
||||
|
||||
function bytesListTest() {
|
||||
const list = new BytesList()
|
||||
.put(Buffer.from([0]))
|
||||
.put(Buffer.from([2, 3]))
|
||||
.put(Buffer.from([4, 5]))
|
||||
test('is an Array<Buffer>', function () {
|
||||
expect(Array.isArray(list.bytesArray)).toBe(true)
|
||||
expect(list.bytesArray[0] instanceof Buffer).toBe(true)
|
||||
})
|
||||
test('keeps track of the length itself', function () {
|
||||
expect(list.getLength()).toBe(5)
|
||||
})
|
||||
test('can join all arrays into one via toBytes', function () {
|
||||
const joined = list.toBytes()
|
||||
expect(joined).toHaveLength(5)
|
||||
expect(joined).toEqual(Buffer.from([0, 2, 3, 4, 5]))
|
||||
})
|
||||
}
|
||||
|
||||
function assertRecycles(blob) {
|
||||
const parser = makeParser(blob)
|
||||
const so = parser.readType(STObject)
|
||||
const out = new BytesList()
|
||||
so.toBytesSink(out)
|
||||
const hex = out.toHex()
|
||||
expect(hex).toEqual(blob)
|
||||
expect(hex + ':').not.toEqual(blob)
|
||||
}
|
||||
|
||||
function nestedObjectTests() {
|
||||
fixtures.whole_objects.forEach((f, i) => {
|
||||
test(`whole_objects[${i}]: can parse blob and dump out same blob`, () => {
|
||||
assertRecycles(f.blob_with_no_signing)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function check(type, n, expected) {
|
||||
test(`Uint${type.width * 8} serializes ${n} as ${expected}`, function () {
|
||||
const bl = new BytesList()
|
||||
const serializer = new BinarySerializer(bl)
|
||||
if (expected === 'throws') {
|
||||
expect(() => serializer.writeType(type, n)).toThrow()
|
||||
return
|
||||
}
|
||||
serializer.writeType(type, n)
|
||||
expect(bl.toBytes()).toEqual(Buffer.from(expected))
|
||||
})
|
||||
}
|
||||
|
||||
check(UInt8, 5, [5])
|
||||
check(UInt16, 5, [0, 5])
|
||||
check(UInt32, 5, [0, 0, 0, 5])
|
||||
check(UInt32, 0xffffffff, [255, 255, 255, 255])
|
||||
check(UInt8, 0xfeffffff, 'throws')
|
||||
check(UInt16, 0xfeffffff, 'throws')
|
||||
check(UInt16, 0xfeffffff, 'throws')
|
||||
check(UInt64, 0xfeffffff, [0, 0, 0, 0, 254, 255, 255, 255])
|
||||
check(UInt64, -1, 'throws')
|
||||
check(UInt64, 0, [0, 0, 0, 0, 0, 0, 0, 0])
|
||||
check(UInt64, 1, [0, 0, 0, 0, 0, 0, 0, 1])
|
||||
check(UInt64, bigInt(1), [0, 0, 0, 0, 0, 0, 0, 1])
|
||||
|
||||
function deliverMinTest() {
|
||||
test('can serialize DeliverMin', () => {
|
||||
expect(encode(deliverMinTx)).toEqual(deliverMinTxBinary)
|
||||
})
|
||||
}
|
||||
|
||||
function SignerListSetTest() {
|
||||
test('can serialize SignerListSet', () => {
|
||||
expect(encode(SignerListSet.tx)).toEqual(SignerListSet.binary)
|
||||
})
|
||||
test('can serialize SignerListSet metadata', () => {
|
||||
expect(encode(SignerListSet.tx.meta)).toEqual(SignerListSet.meta)
|
||||
})
|
||||
}
|
||||
|
||||
function DepositPreauthTest() {
|
||||
test('can serialize DepositPreauth', () => {
|
||||
expect(encode(DepositPreauth.tx)).toEqual(DepositPreauth.binary)
|
||||
})
|
||||
test('can serialize DepositPreauth metadata', () => {
|
||||
expect(encode(DepositPreauth.tx.meta)).toEqual(DepositPreauth.meta)
|
||||
})
|
||||
}
|
||||
|
||||
function EscrowTest() {
|
||||
test('can serialize EscrowCreate', () => {
|
||||
expect(encode(Escrow.create.tx)).toEqual(Escrow.create.binary)
|
||||
})
|
||||
test('can serialize EscrowFinish', () => {
|
||||
expect(encode(Escrow.finish.tx)).toEqual(Escrow.finish.binary)
|
||||
expect(encode(Escrow.finish.tx.meta)).toEqual(Escrow.finish.meta)
|
||||
})
|
||||
test('can serialize EscrowCancel', () => {
|
||||
expect(encode(Escrow.cancel.tx)).toEqual(Escrow.cancel.binary)
|
||||
})
|
||||
}
|
||||
|
||||
function PaymentChannelTest() {
|
||||
test('can serialize PaymentChannelCreate', () => {
|
||||
expect(encode(PaymentChannel.create.tx)).toEqual(
|
||||
PaymentChannel.create.binary,
|
||||
)
|
||||
})
|
||||
test('can serialize PaymentChannelFund', () => {
|
||||
expect(encode(PaymentChannel.fund.tx)).toEqual(PaymentChannel.fund.binary)
|
||||
})
|
||||
test('can serialize PaymentChannelClaim', () => {
|
||||
expect(encode(PaymentChannel.claim.tx)).toEqual(PaymentChannel.claim.binary)
|
||||
})
|
||||
}
|
||||
|
||||
function NegativeUNLTest() {
|
||||
test('can serialize NegativeUNL', () => {
|
||||
expect(encode(NegativeUNL.tx)).toEqual(NegativeUNL.binary)
|
||||
})
|
||||
test('can deserialize NegativeUNL', () => {
|
||||
expect(decode(NegativeUNL.binary)).toEqual(NegativeUNL.tx)
|
||||
})
|
||||
}
|
||||
|
||||
function omitUndefinedTest() {
|
||||
test('omits fields with undefined value', () => {
|
||||
let encodedOmitted = encode(json_omitted)
|
||||
let encodedUndefined = encode(json_undefined)
|
||||
expect(encodedOmitted).toEqual(encodedUndefined)
|
||||
expect(decode(encodedOmitted)).toEqual(decode(encodedUndefined))
|
||||
})
|
||||
}
|
||||
|
||||
function ticketTest() {
|
||||
test('can serialize TicketCreate', () => {
|
||||
expect(encode(Ticket.create.tx)).toEqual(Ticket.create.binary)
|
||||
})
|
||||
}
|
||||
|
||||
function nfTokenTest() {
|
||||
const fixtures = require('./fixtures/nf-token.json')
|
||||
|
||||
for (const txName of Object.keys(fixtures)) {
|
||||
test(`can serialize transaction ${txName}`, () => {
|
||||
expect(encode(fixtures[txName].tx.json)).toEqual(
|
||||
fixtures[txName].tx.binary,
|
||||
)
|
||||
})
|
||||
|
||||
test(`can deserialize transaction ${txName}`, () => {
|
||||
expect(decode(fixtures[txName].tx.binary)).toEqual(
|
||||
fixtures[txName].tx.json,
|
||||
)
|
||||
})
|
||||
|
||||
test(`can serialize meta ${txName}`, () => {
|
||||
expect(encode(fixtures[txName].meta.json)).toEqual(
|
||||
fixtures[txName].meta.binary,
|
||||
)
|
||||
})
|
||||
|
||||
test(`can deserialize meta ${txName}`, () => {
|
||||
expect(decode(fixtures[txName].meta.binary)).toEqual(
|
||||
fixtures[txName].meta.json,
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
describe('Binary Serialization', function () {
|
||||
describe('nestedObjectTests', nestedObjectTests)
|
||||
describe('BytesList', bytesListTest)
|
||||
describe('DeliverMin', deliverMinTest)
|
||||
describe('DepositPreauth', DepositPreauthTest)
|
||||
describe('SignerListSet', SignerListSetTest)
|
||||
describe('Escrow', EscrowTest)
|
||||
describe('PaymentChannel', PaymentChannelTest)
|
||||
describe('NegativeUNLTest', NegativeUNLTest)
|
||||
describe('OmitUndefined', omitUndefinedTest)
|
||||
describe('TicketTest', ticketTest)
|
||||
describe('NFToken', nfTokenTest)
|
||||
})
|
||||
BIN
packages/ripple-binary-codec/test/fixtures/account-tx-transactions.db
vendored
Normal file
BIN
packages/ripple-binary-codec/test/fixtures/account-tx-transactions.db
vendored
Normal file
Binary file not shown.
4466
packages/ripple-binary-codec/test/fixtures/codec-fixtures.json
vendored
Normal file
4466
packages/ripple-binary-codec/test/fixtures/codec-fixtures.json
vendored
Normal file
File diff suppressed because one or more lines are too long
2933
packages/ripple-binary-codec/test/fixtures/data-driven-tests.json
vendored
Normal file
2933
packages/ripple-binary-codec/test/fixtures/data-driven-tests.json
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1
packages/ripple-binary-codec/test/fixtures/delivermin-tx-binary.json
vendored
Normal file
1
packages/ripple-binary-codec/test/fixtures/delivermin-tx-binary.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"1200002280020000240000689E201B010BF0E361D4950EA99C657EF800000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1684000000000002AF8694000000000003A986AD40485B690F28E8000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D173210254D771E2A30552D1F347F5B88EC87513843F8BC1A408E70A4175B2E3C325FD3C7446304402202A4965FCF0571B7308971956864B1949C2BD924B5A41B5E8DAF00C91C64F964502207FD3BEB7C165BD1F10E6E7C443742BD686F8E102A89B502A4A495F4C29EC5C488114EAAA52373B59DCFBFD3476049AA6408AA22EAA898314EAAA52373B59DCFBFD3476049AA6408AA22EAA8901123000000000000000000000000055534400000000000A20B3C85F482532A9578DBB3950B85CA06594D1FF01FDF050193BEDEAA9074764B961405D31E66AC0E9300000000000000000000000005553440000000000FDF050193BEDEAA9074764B961405D31E66AC0E901FDF050193BEDEAA9074764B961405D31E66AC0E9FF300000000000000000000000005553440000000000DD39C650A96EDA48334E70CC4A85B8B2E8502CD301DD39C650A96EDA48334E70CC4A85B8B2E8502CD3017C44F934D7A5FEEBD1530570CDB83D1D8EF1F37E00"
|
||||
98
packages/ripple-binary-codec/test/fixtures/delivermin-tx.json
vendored
Normal file
98
packages/ripple-binary-codec/test/fixtures/delivermin-tx.json
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"Account": "r4PowrZ7KZw83oWDYxzY82ht2kgDmFUpB7",
|
||||
"Amount": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "5.927096147083"
|
||||
},
|
||||
"DeliverMin": {
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"value": "0.012729190692"
|
||||
},
|
||||
"Destination": "r4PowrZ7KZw83oWDYxzY82ht2kgDmFUpB7",
|
||||
"Fee": "11000",
|
||||
"Flags": 2147614720,
|
||||
"LastLedgerSequence": 17559779,
|
||||
"Paths": [
|
||||
[
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"account": "rQ96qm46YsRX2F7SSCQxToR2ybRuUYsZ4R",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
},
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rQ96qm46YsRX2F7SSCQxToR2ybRuUYsZ4R",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
},
|
||||
{
|
||||
"account": "rQ96qm46YsRX2F7SSCQxToR2ybRuUYsZ4R",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"currency": "USD",
|
||||
"issuer": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 48,
|
||||
"type_hex": "0000000000000030"
|
||||
},
|
||||
{
|
||||
"account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
},
|
||||
{
|
||||
"account": "rULnR9YhAkj9HrcxAcudzBhaXRSqT7zJkq",
|
||||
"type": 1,
|
||||
"type_hex": "0000000000000001"
|
||||
}
|
||||
]
|
||||
],
|
||||
"SendMax": "15000",
|
||||
"Sequence": 26782,
|
||||
"SigningPubKey": "0254D771E2A30552D1F347F5B88EC87513843F8BC1A408E70A4175B2E3C325FD3C",
|
||||
"TransactionType": "Payment",
|
||||
"TxnSignature": "304402202A4965FCF0571B7308971956864B1949C2BD924B5A41B5E8DAF00C91C64F964502207FD3BEB7C165BD1F10E6E7C443742BD686F8E102A89B502A4A495F4C29EC5C48",
|
||||
"date": 502912010,
|
||||
"hash": "0FB10DF664F33840ABC68A8BBE78178359C55AC1AFC83DB468CE69C4A86E3EAC",
|
||||
"inLedger": 17559773,
|
||||
"ledger_index": 17559773,
|
||||
"meta": {
|
||||
"AffectedNodes": [
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Account": "r4PowrZ7KZw83oWDYxzY82ht2kgDmFUpB7",
|
||||
"Balance": "1064773000",
|
||||
"Flags": 65536,
|
||||
"OwnerCount": 9,
|
||||
"Sequence": 26783
|
||||
},
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "0D7F8ADAA3269E9C8B2AA9CDE31BC57E2E52133F36516D3DF576DBCE6E405BDC",
|
||||
"PreviousFields": {
|
||||
"Balance": "1064784000",
|
||||
"Sequence": 26782
|
||||
},
|
||||
"PreviousTxnID": "E028797D6C7AE9B84C6930452D427D5E40CE23E199E8DF0534DAAE5277A76CC1",
|
||||
"PreviousTxnLgrSeq": 17537115
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 12,
|
||||
"TransactionResult": "tecPATH_PARTIAL"
|
||||
},
|
||||
"validated": true
|
||||
}
|
||||
1
packages/ripple-binary-codec/test/fixtures/deposit-preauth-tx-binary.json
vendored
Normal file
1
packages/ripple-binary-codec/test/fixtures/deposit-preauth-tx-binary.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"1200132280000000240000004168400000000000000A732103EB1E2603E7571D6144684996C10DA75063D6E2F3B3FDABE38B857C1BE9578A5574473045022100B0A5672E3E09FA3AF8CF1DCC1D8C881F58B39212D6FDC42CCF30E5400D0EFD9F02202DDD9517D9409D1D9A529B8AEA7DE13AE4CDF96BB6D18FA0D9732DBFC887348D81148A928D14A643F388AC0D26BAF9755B07EB0A2B44851486FFE2A17E861BA0FE9A3ED8352F895D80E789E0"
|
||||
1
packages/ripple-binary-codec/test/fixtures/deposit-preauth-tx-meta-binary.json
vendored
Normal file
1
packages/ripple-binary-codec/test/fixtures/deposit-preauth-tx-meta-binary.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"201C00000000F8E51100612500AE5F59558107CEE99D556326ACD4662CA10A24550240D9F933E55435A0C0DB3B06DD343E56146AAAF7A266D8A92DFFEAB1A71B6523534F27820873F4E213014B23398867D2E624000000412D00000007624000000249CB5DD0E1E7220000000024000000422D00000008624000000249CB5DC681148A928D14A643F388AC0D26BAF9755B07EB0A2B44E1E1E311007056C2D0317AD266B93CB3B36AEB0ABB673B0AFFAB134809CCACFD7158F539603C3AE881148A928D14A643F388AC0D26BAF9755B07EB0A2B44851486FFE2A17E861BA0FE9A3ED8352F895D80E789E0E1E1E511006456CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82ECE7220000000058CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC82148A928D14A643F388AC0D26BAF9755B07EB0A2B44E1E1F1031000"
|
||||
58
packages/ripple-binary-codec/test/fixtures/deposit-preauth-tx.json
vendored
Normal file
58
packages/ripple-binary-codec/test/fixtures/deposit-preauth-tx.json
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM",
|
||||
"Authorize": "rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 65,
|
||||
"SigningPubKey": "03EB1E2603E7571D6144684996C10DA75063D6E2F3B3FDABE38B857C1BE9578A55",
|
||||
"TransactionType": "DepositPreauth",
|
||||
"TxnSignature": "3045022100B0A5672E3E09FA3AF8CF1DCC1D8C881F58B39212D6FDC42CCF30E5400D0EFD9F02202DDD9517D9409D1D9A529B8AEA7DE13AE4CDF96BB6D18FA0D9732DBFC887348D",
|
||||
"hash": "B5D94C027C846171B2F5D4C2D126E88580BF369986A155C1890352F5BC4D7AF9",
|
||||
"meta": {
|
||||
"AffectedNodes": [
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM",
|
||||
"Balance": "9827999174",
|
||||
"Flags": 0,
|
||||
"OwnerCount": 8,
|
||||
"Sequence": 66
|
||||
},
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"LedgerIndex": "146AAAF7A266D8A92DFFEAB1A71B6523534F27820873F4E213014B23398867D2",
|
||||
"PreviousFields": {
|
||||
"Balance": "9827999184",
|
||||
"OwnerCount": 7,
|
||||
"Sequence": 65
|
||||
},
|
||||
"PreviousTxnID": "8107CEE99D556326ACD4662CA10A24550240D9F933E55435A0C0DB3B06DD343E",
|
||||
"PreviousTxnLgrSeq": 11427673
|
||||
}
|
||||
},
|
||||
{
|
||||
"CreatedNode": {
|
||||
"LedgerEntryType": "DepositPreauth",
|
||||
"LedgerIndex": "C2D0317AD266B93CB3B36AEB0ABB673B0AFFAB134809CCACFD7158F539603C3A",
|
||||
"NewFields": {
|
||||
"Account": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM",
|
||||
"Authorize": "rDJFnv5sEfp42LMFiX3mVQKczpFTdxYDzM"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ModifiedNode": {
|
||||
"FinalFields": {
|
||||
"Flags": 0,
|
||||
"Owner": "rDd6FpNbeY2CrQajSmP178BmNGusmQiYMM",
|
||||
"RootIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC"
|
||||
},
|
||||
"LedgerEntryType": "DirectoryNode",
|
||||
"LedgerIndex": "CD08416851CA53E9649408118A4908E01E43436ED950886D1B1E66F4B68B82EC"
|
||||
}
|
||||
}
|
||||
],
|
||||
"TransactionIndex": 0,
|
||||
"TransactionResult": "tesSUCCESS"
|
||||
}
|
||||
}
|
||||
1
packages/ripple-binary-codec/test/fixtures/escrow-cancel-binary.json
vendored
Normal file
1
packages/ripple-binary-codec/test/fixtures/escrow-cancel-binary.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
"1200042019000000198114EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA8214EE5F7CF61504C7CF7E0C22562EB19CC7ACB0FCBA"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user