mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 21:15:47 +00:00
Compare commits
545 Commits
0.9.2-rc6
...
0.13.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d50034265 | ||
|
|
64f451e904 | ||
|
|
b1b47d7d91 | ||
|
|
a93c580c92 | ||
|
|
030e2786d1 | ||
|
|
56bbe1d437 | ||
|
|
1fff5ea6dc | ||
|
|
ad9956375f | ||
|
|
4787e5d29a | ||
|
|
194f76d57f | ||
|
|
d2ee5cb0bc | ||
|
|
040298db2c | ||
|
|
b4e6d4b98c | ||
|
|
a2b31e2677 | ||
|
|
e997c44a18 | ||
|
|
b27011fb38 | ||
|
|
b477eb238b | ||
|
|
02bc256225 | ||
|
|
9f7293127c | ||
|
|
61a0ea7eac | ||
|
|
51ef93e23b | ||
|
|
ab11220e27 | ||
|
|
b23d588747 | ||
|
|
a71dc28523 | ||
|
|
2de0e13ec5 | ||
|
|
d0c922ed13 | ||
|
|
8c82ebec79 | ||
|
|
ddd83d10fa | ||
|
|
031375e701 | ||
|
|
4bc285313c | ||
|
|
f42dd69b53 | ||
|
|
1dae06fdd8 | ||
|
|
ff29247b9e | ||
|
|
d9eca203ed | ||
|
|
4a85182a79 | ||
|
|
055d275f0a | ||
|
|
eb7bbe5715 | ||
|
|
8d5752883f | ||
|
|
2b5f356de8 | ||
|
|
c9610900fd | ||
|
|
79b1a65a7b | ||
|
|
df21b9453f | ||
|
|
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 | ||
|
|
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 | ||
|
|
c31798c7f8 | ||
|
|
064c5e9e50 | ||
|
|
077a53475d | ||
|
|
32ca23a00b | ||
|
|
1a7cdd7d04 | ||
|
|
85befa467b | ||
|
|
9b956e57ac | ||
|
|
93c0c49002 | ||
|
|
44954621e0 | ||
|
|
96e5d484da | ||
|
|
4efe0b920e | ||
|
|
d158cc7fc1 | ||
|
|
034cd4eaf6 | ||
|
|
d5f3d90486 | ||
|
|
99e076b7dd | ||
|
|
8f9aec83f2 | ||
|
|
87a8745a64 | ||
|
|
2ab51d7a8a | ||
|
|
3e0f43e44e | ||
|
|
6a763fab18 | ||
|
|
3fe6726789 | ||
|
|
5b4deabd90 | ||
|
|
84bc7dd4aa | ||
|
|
3960b4e11f | ||
|
|
141215fc38 | ||
|
|
2446e2f6da | ||
|
|
81a9bc0739 | ||
|
|
64e86f403e | ||
|
|
77f1351e5b | ||
|
|
47a87f3a92 | ||
|
|
d2df75dc25 | ||
|
|
d92fbfb7aa | ||
|
|
1b3be55711 | ||
|
|
8d98e443c5 | ||
|
|
46121edd62 | ||
|
|
1a32536ac8 | ||
|
|
85bf6891f9 | ||
|
|
1ccca1c4ec | ||
|
|
22cd70e53f | ||
|
|
d8aad1444e | ||
|
|
621dfd9ca5 | ||
|
|
2ad6a1a77e | ||
|
|
8f37438a08 | ||
|
|
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 |
14
.flowconfig
Normal file
14
.flowconfig
Normal file
@@ -0,0 +1,14 @@
|
||||
[ignore]
|
||||
.*/src/api/.*
|
||||
.*/src/core/.*
|
||||
.*/dist/.*
|
||||
.*/test/fixtures/.*
|
||||
.*/node_modules/flow-bin/.*
|
||||
|
||||
[include]
|
||||
./node_modules/
|
||||
|
||||
[libs]
|
||||
|
||||
[options]
|
||||
module.system=node
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -17,7 +17,7 @@
|
||||
|
||||
# Ignore object files.
|
||||
*.o
|
||||
build/*.js
|
||||
build/
|
||||
tags
|
||||
bin/rippled
|
||||
Debug/*.*
|
||||
@@ -25,6 +25,7 @@ Release/*.*
|
||||
|
||||
# Ignore locally installed node_modules
|
||||
node_modules
|
||||
!test/node_modules
|
||||
|
||||
# Ignore tmp directory.
|
||||
tmp
|
||||
@@ -52,3 +53,11 @@ npm-debug.log
|
||||
|
||||
# Ignore dist folder, build for bower
|
||||
dist/
|
||||
|
||||
# Ignore flow output directory
|
||||
out/
|
||||
|
||||
# Ignore perf test cache
|
||||
scripts/cache
|
||||
|
||||
eslintrc
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
deploy
|
||||
lib-cov
|
||||
coverage.html
|
||||
src
|
||||
dist/bower
|
||||
|
||||
16
.travis.yml
16
.travis.yml
@@ -1,13 +1,9 @@
|
||||
sudo: false # use faster docker containers
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
script: npm test --coverage
|
||||
after_success:
|
||||
- npm run coveralls
|
||||
- "0.12"
|
||||
before_script:
|
||||
- sh -c "git log | head -12"
|
||||
script: bin/ci.sh
|
||||
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
|
||||
email: false
|
||||
|
||||
207
Gulpfile.js
207
Gulpfile.js
@@ -1,163 +1,126 @@
|
||||
/* eslint-disable no-var, no-param-reassign */
|
||||
/* these eslint rules are disabled because gulp does not support babel yet */
|
||||
'use strict';
|
||||
var _ = require('lodash');
|
||||
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({
|
||||
function webpackConfig(extension, overrides) {
|
||||
overrides = overrides || {};
|
||||
var defaults = {
|
||||
cache: true,
|
||||
entry: './src/js/ripple/index.js',
|
||||
entry: './src/index.js',
|
||||
output: {
|
||||
library: 'ripple',
|
||||
path: './build/',
|
||||
filename: [ 'ripple-', '.js' ].join(pkg.version)
|
||||
filename: ['ripple-', extension].join(pkg.version)
|
||||
},
|
||||
}, callback);
|
||||
module: {
|
||||
loaders: [{
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader?optional=runtime'
|
||||
}]
|
||||
}
|
||||
};
|
||||
return _.assign({}, defaults, overrides);
|
||||
}
|
||||
|
||||
gulp.task('build', function(callback) {
|
||||
webpack(webpackConfig('.js'), callback);
|
||||
});
|
||||
|
||||
gulp.task('bower-build', [ 'build' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '.js' ].join(pkg.version))
|
||||
gulp.task('build-min', ['build'], function() {
|
||||
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', function(callback) {
|
||||
var configOverrides = {debug: true, devtool: 'eval'};
|
||||
webpack(webpackConfig('-debug.js', configOverrides), callback);
|
||||
});
|
||||
|
||||
/**
|
||||
* Generate a WebPack external for a given unavailable module which replaces
|
||||
* that module's constructor with an error-thrower
|
||||
*/
|
||||
|
||||
function buildUseError(cons) {
|
||||
return ('var {<CONS>:function(){throw new Error('
|
||||
+ '"Class is unavailable in this build: <CONS>")}}')
|
||||
.replace(new RegExp('<CONS>', 'g'), cons);
|
||||
}
|
||||
|
||||
gulp.task('build-core', function(callback) {
|
||||
var configOverrides = {
|
||||
cache: false,
|
||||
entry: './src/remote.js',
|
||||
externals: [{
|
||||
'./transaction': buildUseError('Transaction'),
|
||||
'./orderbook': buildUseError('OrderBook'),
|
||||
'./account': buildUseError('Account'),
|
||||
'./serializedobject': buildUseError('SerializedObject')
|
||||
}],
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin()
|
||||
]
|
||||
};
|
||||
webpack(webpackConfig('-core.js', configOverrides), callback);
|
||||
});
|
||||
|
||||
gulp.task('bower-build', ['build'], function() {
|
||||
return gulp.src(['./build/ripple-', '.js'].join(pkg.version))
|
||||
.pipe(rename('ripple.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-min', [ 'build-min' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '-min.js' ].join(pkg.version))
|
||||
gulp.task('bower-build-min', ['build-min'], function() {
|
||||
return gulp.src(['./build/ripple-', '-min.js'].join(pkg.version))
|
||||
.pipe(rename('ripple-min.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-build-debug', [ 'build-debug' ], function(callback) {
|
||||
return gulp.src([ './build/ripple-', '-debug.js' ].join(pkg.version))
|
||||
gulp.task('bower-build-debug', ['build-debug'], function() {
|
||||
return gulp.src(['./build/ripple-', '-debug.js'].join(pkg.version))
|
||||
.pipe(rename('ripple-debug.js'))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower-version', function() {
|
||||
gulp.src('./dist/bower.json')
|
||||
gulp.src('./dist/bower/bower.json')
|
||||
.pipe(bump({version: pkg.version}))
|
||||
.pipe(gulp.dest('./dist/'));
|
||||
.pipe(gulp.dest('./dist/bower'));
|
||||
});
|
||||
|
||||
gulp.task('bower', ['bower-build', 'bower-build-min', 'bower-build-debug',
|
||||
'bower-version']);
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch('src/*', ['build-debug']);
|
||||
});
|
||||
|
||||
gulp.task('version-bump', function() {
|
||||
if (!argv.type) {
|
||||
throw new Error("No type found, pass it in using the --type argument");
|
||||
throw new Error('No type found, pass it in using the --type argument');
|
||||
}
|
||||
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({type:argv.type}))
|
||||
.pipe(bump({type: argv.type}))
|
||||
.pipe(gulp.dest('./'));
|
||||
});
|
||||
|
||||
gulp.task('version-beta', function() {
|
||||
gulp.src('./package.json')
|
||||
.pipe(bump({version: pkg.version+'-beta'}))
|
||||
.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']);
|
||||
gulp.task('default', ['build', 'build-debug', 'build-min']);
|
||||
|
||||
200
HISTORY.md
200
HISTORY.md
@@ -1,8 +1,204 @@
|
||||
##0.12.15
|
||||
|
||||
+ [Add offer autobridging](https://github.com/ripple/ripple-lib/commit/c7bbce83719c1e8c6a4fae5ca850e7515db1a4a5)
|
||||
|
||||
+ [Prevent crash when listening for "model" events on the OrderBook class](https://github.com/ripple/ripple-lib/commit/5824c3cb7cb6bd834d6e037f69943aebf3d83351)
|
||||
|
||||
+ [Fix empty order edgecase](https://github.com/ripple/ripple-lib/commit/64809d9ae23dc24f47accd4b4788b48f49880d3e)
|
||||
|
||||
+ [Fix AutobridgeCalculator (RT-3445)](https://github.com/ripple/ripple-lib/commit/1fff5ea6dcbcee856536df26f3b9cf1aec3c3b55)
|
||||
|
||||
+ [Update sjcl and delete custom ripemd160, montgomery, and jacobi](https://github.com/ripple/ripple-lib/commit/50cda426eb83599c38c0b725e1524a01fc415da2)
|
||||
|
||||
+ [Fix transaction summary for transactions that fail with remoteError](https://github.com/ripple/ripple-lib/commit/5e714f6143464d7912f42537acaa553b88eaf6dc)
|
||||
|
||||
+ [Fix serializedobject append for excessively large bytes length](https://github.com/ripple/ripple-lib/commit/e93f1ab6f4aaad347450aee75a169af0faa2121c)
|
||||
|
||||
+ [Switch to sjcl npm module](https://github.com/ripple/ripple-lib/commit/9a502580fd89ec6a9aa55f4e5847f6a4a2cb5bba)
|
||||
|
||||
+ [Add babel transpiler](https://github.com/ripple/ripple-lib/commit/398f8d001f758bf575b959537a17e79e4042d17b)
|
||||
|
||||
+ [Remove unused float.js and wallet.js](https://github.com/ripple/ripple-lib/commit/d4a4b5f4fbbf09677a59ce81bace35c6426a2fda)
|
||||
|
||||
+ [Remove config singleton to reduce global state](https://github.com/ripple/ripple-lib/commit/c655c2a20ee5d150a4b5a1b6717b9fb81f636025)
|
||||
|
||||
##0.12.4
|
||||
|
||||
+ [Improve entropy security](https://github.com/ripple/ripple-lib/commit/c7ba822320880037796f57876d1abb4e525648ed)
|
||||
|
||||
+ [Remove unused crypt.js file](https://github.com/ripple/ripple-lib/commit/1f68eba1461bca03a4d22872450d15ae5a185334)
|
||||
|
||||
##0.12.3
|
||||
|
||||
+ [Add getLedgerSequence to Remote](https://github.com/ripple/ripple-lib/commit/d09548d04d3238fca653d482ec1d5faa7254559a)
|
||||
|
||||
+ [Improve randomness when generating ECDSA signatures](https://github.com/ripple/ripple-lib/commit/fe7e30b737ead6e71adfa466f5835ba546feab31)
|
||||
|
||||
+ [Improve SerializedObject.append performance](https://github.com/ripple/ripple-lib/commit/f7c35b118ebba549a64bcaa1a0629385ec6dbf6f)
|
||||
|
||||
+ [Add `Amount.scale`. Multiply an amount’s value by a scale factor](https://github.com/ripple/ripple-lib/commit/74dac97b368493056474468520f05671f458a69f)
|
||||
|
||||
|
||||
##0.12.2
|
||||
|
||||
+ [Check that stack trace is available, fixes logging in browser](https://github.com/ripple/ripple-lib/commit/53cae3a66d48e88e8a6bbb96d6489ce7b9e22975)
|
||||
|
||||
|
||||
##0.12.1
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
+ [Removed support for parsing native amounts in floating point format](https://github.com/ripple/ripple-lib/commit/e80cd1ff55deae9cd5b0ae85be957f86856b887e)
|
||||
|
||||
|
||||
**Changes**
|
||||
|
||||
+ [Fix taker pays funded calculation](https://github.com/ripple/ripple-lib/commit/5af824f5cf46c7b9caa58ee0a757bf854d26c8dc)
|
||||
|
||||
+ [Fix order funded amount calculation](https://github.com/ripple/ripple-lib/commit/b2cdb1a6aed968b1f306e8dadbd4b7ca37e5aa03)
|
||||
|
||||
+ [Fix handling of quality in order book](https://github.com/ripple/ripple-lib/commit/2a5a8b498da60df738ba18d5c265f34771e8a1af)
|
||||
|
||||
+ [Fix currency parsing of non-alphanumeric and no-currency currencies](https://github.com/ripple/ripple-lib/commit/2166bb2e88eae8d5f1aba77338f69e8a9edf6a6f)
|
||||
|
||||
+ [Add Amount.strict_mode for toggling range validation](https://github.com/ripple/ripple-lib/commit/b5ed8f59a7dab1a17491618b8d9193646c314fb4)
|
||||
|
||||
+ [Add filename and line number to log, use log.warn() for deprecations](https://github.com/ripple/ripple-lib/commit/90329d3d73f1a76675063655b407513e32dc048b)
|
||||
|
||||
+ [Add GlobalFreeze and NoFreeze flags](https://github.com/ripple/ripple-lib/commit/e2ed2bdbf6f01c7d4d690c2cf0b83fba94558dd7)
|
||||
|
||||
+ [Fix handling of falsy parameters in requestLedger](https://github.com/ripple/ripple-lib/commit/6023efed41b7812b3bab660a1c0dc9f0a21000b9)
|
||||
|
||||
+ [Fix Base:decode](https://github.com/ripple/ripple-lib/commit/719f39c01c6941d9a650aa94f95617793dd53ea0)
|
||||
|
||||
+ [Fix Amount: clone in ratio_human, product_human](https://github.com/ripple/ripple-lib/commit/19e17a8431550cf156b1ad669a19dedfe4e28e4a)
|
||||
|
||||
+ [Fix Amount.to_human for very small numbers](https://github.com/ripple/ripple-lib/commit/6abfa759aa09d68074ac558d96c4b126a7cd1719)
|
||||
|
||||
+ [Refactor base conversion](https://github.com/ripple/ripple-lib/commit/f2b63fa4a80663eb29472bc6bb1aea8159f1f205)
|
||||
|
||||
+ [Update binary transaction format](https://github.com/ripple/ripple-lib/commit/8e134918fb4c22983320a3102f955e4568bb1dfb)
|
||||
|
||||
+ [Add DefaultRipple account flag](https://github.com/ripple/ripple-lib/commit/3e249902c4cf25b4da5e75048c84ae391be83b10)
|
||||
|
||||
+ [Remove `Features` field requirement in `SetFee` transaction format](https://github.com/ripple/ripple-lib/commit/a20a649013646710c078d4ce1e210f87c7fe74fe)
|
||||
|
||||
+ [Remove `RegularKey` field requirement in `SetRegularKey` transaction format](https://github.com/ripple/ripple-lib/commit/c275174f27877ba8f389eb4efe969feb514d6e46)
|
||||
|
||||
|
||||
##0.12.0
|
||||
|
||||
**Breaking Changes**
|
||||
|
||||
+ REMOVED Remote storage interface
|
||||
+ REMOVED Remote `ping` configuration
|
||||
+ REMOVED Old/deprecated Remote server configuration (websocket_ip, websocket_port)
|
||||
+ REMOVED browser `online` reconnect listener
|
||||
- [Cleanup, deprecations - 2833a7b6](https://github.com/ripple/ripple-lib/commit/2833a7b66e696dab427464625077f9b93092d0d5)
|
||||
|
||||
+ Remove `jsbn` and use `bignumber.js` instead for big number math
|
||||
+ The `allow_nan` flag has been removed. Results for invalid amounts will always be `NaN`
|
||||
- [Refactor to use bignumber.js - d025b4a0](https://github.com/ripple/ripple-lib/commit/d025b4a0c3a98a6de27a1bee9573c85347bcd66b)
|
||||
- [Handle invalid input in parse_human - c8f18c8c](https://github.com/ripple/ripple-lib/commit/c8f18c8c8590b7b48e370e0325b6677b7720294f)
|
||||
- [Check for null in isNumber - b86790c8](https://github.com/ripple/ripple-lib/commit/b86790c8543c239a532fd7697d4652829019d385)
|
||||
- [Cleanup amount.js - d0fb291c](https://github.com/ripple/ripple-lib/commit/d0fb291c4e330193a244902156f1d74730da357d)
|
||||
|
||||
|
||||
**Changes**
|
||||
|
||||
+ [Add deprecation warnings to request constructors. The first argument to request constructor functions should be an object containing request properties](https://github.com/ripple/ripple-lib/commit/35d76b3520934285f80059c1badd6c522539104c)
|
||||
|
||||
+ [Fix taker_gets_funded exceeding offer.TakerGets](https://github.com/ripple/ripple-lib/commit/b19ecb4482b589d575382b7a5d0480b963383bb1)
|
||||
|
||||
+ [Fix unsymmetric memo serializing](https://github.com/ripple/ripple-lib/commit/1ed36fabdbd54f4d31078c2b0eaa3becc0fe2821)
|
||||
|
||||
+ [Fix IOU value passed to `Amount.from_json()`](https://github.com/ripple/ripple-lib/commit/fd1b64393dffb3d1819cd40b8d43df43a4db042d)
|
||||
|
||||
+ [Update transaction binary parsing to account for XRP delivered amounts](https://github.com/ripple/ripple-lib/commit/35a346a674e6ee1e1e495db93700d55984efc7dd)
|
||||
|
||||
+ [Bumped dependencies](https://github.com/ripple/ripple-lib/commit/f9bc7cc746b44b24b61bbe260ae2e9d9617286da)
|
||||
|
||||
|
||||
|
||||
##0.11.0
|
||||
|
||||
+ [Track the funded status of an order based on cumulative account orders](https://github.com/ripple/ripple-lib/commit/67d39737a4d5e0fcd9d9b47b9083ee00e5a9e652) and [67d3973](https://github.com/ripple/ripple-lib/commit/b6b99dde022e1e14c4797e454b1d7fca50e49482)
|
||||
|
||||
+ Remove blobvault client from ripple-lib, use the [`ripple-vault-client`](https://github.com/ripple/ripple-vault-client) instead [9b3d62b7](https://github.com/ripple/ripple-lib/commit/9b3d62b765c4c25beae6eb0fa57ef3a07f2581b1)
|
||||
|
||||
+ [Add support for `ledger` option in requestBookOffers](https://github.com/ripple/ripple-lib/commit/34c0677c453c409ef0a5b351959abdc176d3bacb)
|
||||
|
||||
+ [Add support for `limit` option in requestBookOffers](https://github.com/ripple/ripple-lib/commit/d1d4452217c878d0b377d24830b4cd8b3162f6e0)
|
||||
|
||||
+ [Add `ledgerSelect` request constructor in `Remote`](https://github.com/ripple/ripple-lib/commit/98f40abfc3aa74dec5067a2d90002756cc8acd01)
|
||||
|
||||
+ [Default to binary data for commands that accept the binary flag](https://github.com/ripple/ripple-lib/commit/7cb113fcbcfc1e3e9830a999148b3e78df3387cc)
|
||||
|
||||
+ [Fix metadata account check](https://github.com/ripple/ripple-lib/commit/3f61598d6c87e3cc877af60e2d515f9eff73dfe1)
|
||||
|
||||
+ [Double check `tes` code before emitting `success`](https://github.com/ripple/ripple-lib/commit/97a8c874903eb7309d8f755955ac80872f670582)
|
||||
|
||||
+ [Decrease redundancy in binary account_tx parsing](https://github.com/ripple/ripple-lib/commit/0aba638e6e7f4f6e22cb6424eed3897ebad90a5a)
|
||||
|
||||
+ [Abort server connection on unrecoverable TLS error](https://github.com/ripple/ripple-lib/commit/000a2ea00c57157044aeca0fb3f24b37669b163c)
|
||||
|
||||
+ [Fix complete ledgers check on subscription that is not initial](https://github.com/ripple/ripple-lib/commit/89de91301e682a46dc60aaacc7ae152e8fe1b7c7)
|
||||
|
||||
|
||||
##0.10.0
|
||||
|
||||
+ [Transaction changes](https://github.com/ripple/ripple-lib/pull/221)
|
||||
|
||||
+ **Important** `tef*` and `tel*` and errors will no longer be presented as
|
||||
final. Rather than considering these errors final, ripple-lib will wait until
|
||||
the `LastLedgerSequence` specified in the transaction is exceeded. This makes
|
||||
failures more definitive, and ensures that no transaction will resubmit
|
||||
indefinitely.
|
||||
|
||||
+ A new, final tej-class error is introduced to account for transactions that
|
||||
are locally determined to have expired: `tejMaxLedger`.
|
||||
|
||||
+ [Allow per transaction fees to be set, `transaction.setFixedFee()`](https://github.com/ripple/ripple-lib/commit/9b22f279bcbe60ee6bcf4b7fa60a48e9c197a828)
|
||||
|
||||
+ [Improve memo support](https://github.com/ripple/ripple-lib/commit/1704ac4ae144c0ce54afad86f644c75a632080b1)
|
||||
- Add `MemoFormat` property for memo
|
||||
- Enforce `MemoFormat` and `MemoType` to be valid ASCII
|
||||
- Support `text` and `json` MemoFormat
|
||||
|
||||
+ [Update jscl library](https://github.com/ripple/ripple-lib/commit/3204998fcb6f31d6c90532a737a4adb8a1e420f6)
|
||||
- Improved entropy by taking advantage of platform crypto
|
||||
- Use jscl's k256 curve instead of altering the c256 curve with k256 configuration
|
||||
- **deprecated:** the c256 curve is linked to the k256 curve to provide backwards compatibility, this link will be removed in the future
|
||||
|
||||
+ [Fix empty queue check on reconnect](https://github.com/ripple/ripple-lib/commit/3c21994adcf72d1fbd87d453ceb917f9ad6df4ec)
|
||||
|
||||
##0.9.4
|
||||
|
||||
+ [Normalize offers from book_offers and transaction stream](https://github.com/ripple/ripple-lib/commit/86ed24b94cf7c8929c87db3a63e9bbea7f767e9c)
|
||||
|
||||
+ [Fix: Amount.to_human() precision rounding](https://github.com/ripple/ripple-lib/commit/e371cc2c3ceccb3c1cfdf18b98d80093147dd8b2)
|
||||
|
||||
+ [Fix: fractional drops in funded taker_pays setter](https://github.com/ripple/ripple-lib/commit/0d7fc0a573a144caac15dd13798b23eeb1f95fb4)
|
||||
|
||||
##0.9.3
|
||||
|
||||
+ [Change `presubmit` to emit immediately before transaction submit](https://github.com/ripple/ripple-lib/commit/7a1feaa89701bf861ab31ebd8ffdc8d8d1474e29)
|
||||
|
||||
+ [Add a "core" browser build of ripple-lib which has a subset of features and smaller file size](https://github.com/ripple/ripple-lib/pull/205)
|
||||
|
||||
+ [Update binformat with missing fields from rippled](https://github.com/ripple/ripple-lib/commit/cae980788efb00191bfd0988ed836d60cdf7a9a2)
|
||||
|
||||
+ [Wait for transaction validation before returning `tec` error](https://github.com/ripple/ripple-lib/commit/6bdd4b2670906588852fc4dda457607b4aac08e4)
|
||||
|
||||
+ [Change default `max_fee` on `Remote` to `1 XRP`](https://github.com/ripple/ripple-lib/commit/d6b1728c23ff85c3cc791bed6982a750641fd95f)
|
||||
|
||||
+ [Fix: Request ledger_accept should return the Remote](https://github.com/ripple/ripple-lib/pull/209)
|
||||
|
||||
##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 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)
|
||||
|
||||
@@ -18,7 +214,7 @@ var options = {
|
||||
ledger: < valid ledger_index or ledger_hash >
|
||||
}
|
||||
|
||||
// The `marker` comes back in an account request if there are more results than are returned
|
||||
// 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 >;
|
||||
|
||||
22
README.md
22
README.md
@@ -9,15 +9,15 @@ A JavaScript API for interacting with Ripple in Node.js and the browser
|
||||
###Features
|
||||
|
||||
+ Connect to a rippled server in JavaScript (Node.js or browser)
|
||||
+ Issue [rippled API](https://ripple.com/wiki/JSON_Messages) requests
|
||||
+ Issue [rippled API](https://ripple.com/build/rippled-apis/) requests
|
||||
+ Listen to events on the Ripple network (transaction, ledger, etc.)
|
||||
+ Sign and submit transactions to the Ripple network
|
||||
|
||||
###In this file
|
||||
|
||||
1. [Installation](README.md#installation)
|
||||
2. [Quickstart](README.md#quickstart)
|
||||
3. [Running tests](https://github.com/ripple/ripple-lib#running-tests)
|
||||
1. [Installation](#installation)
|
||||
2. [Quick start](#quick-start)
|
||||
3. [Running tests](#running-tests)
|
||||
|
||||
###Additional documentation
|
||||
|
||||
@@ -44,10 +44,12 @@ A JavaScript API for interacting with Ripple in Node.js and the browser
|
||||
$ bower install ripple
|
||||
```
|
||||
|
||||
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions
|
||||
See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for additional bower instructions.
|
||||
|
||||
|
||||
**Building ripple-lib from github**
|
||||
**Building ripple-lib for browser environments**
|
||||
|
||||
ripple-lib uses Gulp to generate browser builds. These steps will generate minified and non-minified builds of ripple-lib in the `build/` directory.
|
||||
|
||||
```
|
||||
$ git clone https://github.com/ripple/ripple-lib
|
||||
@@ -55,9 +57,13 @@ See the [bower-ripple repo](https://github.com/ripple/bower-ripple) for addition
|
||||
$ npm run build
|
||||
```
|
||||
|
||||
Then use the minified `build/ripple-*-min.js`
|
||||
**Restricted browser builds**
|
||||
|
||||
##Quickstart
|
||||
You may generate browser builds that contain a subset of features. To do this, run `./node_modules/.bin/gulp build-<name>`
|
||||
|
||||
+ `build-core` Contains the functionality to make requests and listen for events such as `ledgerClose`. Only `ripple.Remote` is currently exposed. Advanced features like transaction submission and orderbook tracking are excluded from this build.
|
||||
|
||||
##Quick start
|
||||
|
||||
`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
|
||||
|
||||
|
||||
52
bin/ci.sh
Executable file
52
bin/ci.sh
Executable file
@@ -0,0 +1,52 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
NODE_INDEX="$1"
|
||||
TOTAL_NODES="$2"
|
||||
|
||||
typecheck() {
|
||||
npm install -g flow-bin
|
||||
npm run typecheck
|
||||
}
|
||||
|
||||
lint() {
|
||||
REPO_URL="https://raw.githubusercontent.com/ripple/javascript-style-guide"
|
||||
curl "$REPO_URL/es6/eslintrc" > ./eslintrc
|
||||
echo "plugins: [flowtype]" >> ./eslintrc
|
||||
node_modules/.bin/eslint --reset -c ./eslintrc $(git --no-pager diff --name-only -M100% --diff-filter=AM --relative $(git merge-base FETCH_HEAD origin/HEAD) FETCH_HEAD | grep "\.js$")
|
||||
}
|
||||
|
||||
unittest() {
|
||||
npm test --coverage
|
||||
npm run coveralls
|
||||
}
|
||||
|
||||
oneNode() {
|
||||
lint
|
||||
typecheck
|
||||
unittest
|
||||
}
|
||||
|
||||
twoNodes() {
|
||||
case "$NODE_INDEX" in
|
||||
0) lint && unittest;;
|
||||
1) typecheck;;
|
||||
*) echo "ERROR: invalid usage"; exit 2;;
|
||||
esac
|
||||
}
|
||||
|
||||
threeNodes() {
|
||||
case "$NODE_INDEX" in
|
||||
0) lint;;
|
||||
1) typecheck;;
|
||||
2) unittest;;
|
||||
*) echo "ERROR: invalid usage"; exit 2;;
|
||||
esac
|
||||
}
|
||||
|
||||
case "$TOTAL_NODES" in
|
||||
"") oneNode;;
|
||||
1) oneNode;;
|
||||
2) twoNodes;;
|
||||
3) threeNodes;;
|
||||
*) echo "ERROR: invalid usage"; exit 2;;
|
||||
esac
|
||||
@@ -1,47 +1,52 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
var SerializedObject = require('..').SerializedObject;
|
||||
|
||||
var SerializedObject = require('../src/js/ripple/serializedobject').SerializedObject;
|
||||
function main() {
|
||||
var argv = process.argv.slice(2);
|
||||
var blob = argv.shift();
|
||||
|
||||
var argv = process.argv.slice(2);
|
||||
|
||||
var blob;
|
||||
|
||||
blob = argv.shift();
|
||||
|
||||
if (blob === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready();
|
||||
if (blob === '-') {
|
||||
read_input(ready);
|
||||
} else {
|
||||
ready(blob);
|
||||
}
|
||||
}
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
var tx_json = '';
|
||||
process.stdin.on('data', function(data) {
|
||||
tx_json += data;
|
||||
});
|
||||
process.stdin.on('end', callback);
|
||||
process.stdin.resume();
|
||||
}
|
||||
|
||||
function ready() {
|
||||
function ready(blob) {
|
||||
var valid_arguments = blob;
|
||||
|
||||
if (!valid_arguments) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
} else {
|
||||
decode();
|
||||
}
|
||||
decode(blob);
|
||||
}
|
||||
}
|
||||
|
||||
function print_usage() {
|
||||
/* eslint-disable max-len */
|
||||
console.log(
|
||||
'Usage: decode_binary.js <hex_blob>\n\n',
|
||||
'Example: decode_binary.js 120000240000000161D6871AFD498D00000000000000000000000000005553440000000000550FC62003E785DC231A1058A05E56E3F09CF4E668400000000000000A732102AE75B908F0A95F740A7BFA96057637E5C2170BC8DAD13B2F7B52AE75FAEBEFCF811450F97A072F1C4357F1AD84566A609479D927C9428314550FC62003E785DC231A1058A05E56E3F09CF4E6'
|
||||
);
|
||||
};
|
||||
/* eslint-enable max-len */
|
||||
}
|
||||
|
||||
function decode() {
|
||||
buffer = new SerializedObject(blob);
|
||||
function decode(blob) {
|
||||
var buffer = new SerializedObject(blob);
|
||||
console.log(buffer.to_json());
|
||||
};
|
||||
}
|
||||
|
||||
main();
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
123
bin/rsign.js
123
bin/rsign.js
@@ -1,62 +1,23 @@
|
||||
#!/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();
|
||||
}
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
var Transaction = require('..').Transaction;
|
||||
|
||||
function read_input(callback) {
|
||||
tx_json = '';
|
||||
process.stdin.on('data', function(data) { tx_json += data; });
|
||||
process.stdin.on('end', callback);
|
||||
var stdin = '';
|
||||
process.stdin.on('data', function(data) {
|
||||
stdin += data;
|
||||
});
|
||||
process.stdin.on('end', function() {
|
||||
callback(stdin);
|
||||
});
|
||||
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','\''+
|
||||
'Example: rsign.js ssq55ueDob4yV3kPVnNQLHB6icwpC', '\'' +
|
||||
JSON.stringify({
|
||||
TransactionType: 'Payment',
|
||||
Account: 'r3P9vH81KBayazSTrQj6S25jW6kDb779Gi',
|
||||
@@ -64,14 +25,14 @@ function print_usage() {
|
||||
Amount: '200000000',
|
||||
Fee: '10',
|
||||
Sequence: 1
|
||||
})+'\''
|
||||
);
|
||||
};
|
||||
}) + '\''
|
||||
);
|
||||
}
|
||||
|
||||
function sign_transaction() {
|
||||
function sign_transaction(tx_json_object, secret, verbose) {
|
||||
var tx = new Transaction();
|
||||
|
||||
tx.tx_json = tx_json;
|
||||
tx.tx_json = tx_json_object;
|
||||
tx._secret = secret;
|
||||
tx.complete();
|
||||
|
||||
@@ -81,16 +42,56 @@ function sign_transaction() {
|
||||
|
||||
if (verbose) {
|
||||
var sim = { };
|
||||
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_blob = tx.serialize().to_hex();
|
||||
sim.tx_json = tx.tx_json;
|
||||
sim.tx_signing_hash = unsigned_hash;
|
||||
sim.tx_unsigned = unsigned_blob;
|
||||
|
||||
sim.tx_unsigned = unsigned_blob;
|
||||
console.log(JSON.stringify(sim, null, 2));
|
||||
} else {
|
||||
console.log(tx.serialize().to_hex());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function ready(tx_json, secret, verbose) {
|
||||
if (!(tx_json && secret)) {
|
||||
console.error('Invalid arguments\n');
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
|
||||
var tx_json_object;
|
||||
try {
|
||||
tx_json_object = JSON.parse(tx_json);
|
||||
} catch(exception) {
|
||||
console.error('Invalid JSON\n');
|
||||
print_usage();
|
||||
return;
|
||||
}
|
||||
sign_transaction(tx_json_object, secret, verbose);
|
||||
}
|
||||
|
||||
function main() {
|
||||
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(function(stdin) {
|
||||
ready(stdin, secret, verbose);
|
||||
});
|
||||
} else {
|
||||
ready(tx_json, secret, verbose);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
var UInt160 = require('..').UInt160;
|
||||
|
||||
var UInt160 = require('../').UInt160;
|
||||
var address = process.argv[2];
|
||||
function main() {
|
||||
var address = process.argv[2];
|
||||
|
||||
if (address === '-') {
|
||||
readInput(validateAddress);
|
||||
} else {
|
||||
validateAddress(address);
|
||||
if (address === '-') {
|
||||
readInput(validateAddress);
|
||||
} else {
|
||||
validateAddress(address);
|
||||
}
|
||||
}
|
||||
|
||||
function readInput(callback) {
|
||||
@@ -19,8 +23,10 @@ function readInput(callback) {
|
||||
process.stdin.on('end', function() {
|
||||
callback(result);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function validateAddress(address) {
|
||||
process.stdout.write((UInt160.is_valid(address.trim()) ? '0' : '1') + '\r\n');
|
||||
};
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
7
circle.yml
Normal file
7
circle.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
machine:
|
||||
node:
|
||||
version: 0.12.0
|
||||
test:
|
||||
override:
|
||||
- bin/ci.sh "$CIRCLE_NODE_INDEX" "$CIRCLE_NODE_TOTAL":
|
||||
parallel: true
|
||||
@@ -1,3 +0,0 @@
|
||||
start newcoin
|
||||
sleep 4
|
||||
start index.html
|
||||
20
docs/BUILD.md
Normal file
20
docs/BUILD.md
Normal file
@@ -0,0 +1,20 @@
|
||||
Using Flow typechecking
|
||||
=======================
|
||||
|
||||
Stage 1
|
||||
-------
|
||||
1. Add /* @flow */ to the top of a file you want to typecheck
|
||||
2. Run `gulp typecheck` to generate a list of warnings
|
||||
|
||||
Stage 2
|
||||
-------
|
||||
When all source files have the /* @flow */ header and all warnings have been
|
||||
addressed, remove the `weak: true` option from Gulpfile.js, run
|
||||
`gulp typecheck` and remove all the additional warnings.
|
||||
|
||||
Stage 3
|
||||
-------
|
||||
Add type annotations to the source code and run `gulp strip` to strip
|
||||
the type annotations and write the output to the `out` directory. After
|
||||
type annotations are added, the program must be run from the `out` directory
|
||||
because Node does not understand the annotations
|
||||
@@ -16,20 +16,9 @@ This file provides step-by-step walkthroughs for some of the most common usages
|
||||
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)
|
||||
1. [Get ripple-lib](../README.md#installation)
|
||||
2. Load the ripple-lib module into a Node.js file or webpage:
|
||||
```js
|
||||
/* Loading ripple-lib with Node.js */
|
||||
@@ -48,9 +37,9 @@ This file provides step-by-step walkthroughs for some of the most common usages
|
||||
servers: [
|
||||
{ host: 's-west.ripple.com', port: 443, secure: true }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var remote = new Remote({options});
|
||||
var remote = new Remote(options);
|
||||
|
||||
remote.connect(function(err, res) {
|
||||
/* remote connected, use some remote functions here */
|
||||
@@ -60,10 +49,37 @@ This file provides step-by-step walkthroughs for some of the most common usages
|
||||
|
||||
4. You're connected! Read on to see what to do now.
|
||||
|
||||
##Generating a new Ripple Wallet
|
||||
|
||||
```js
|
||||
var ripple = require('ripple-lib');
|
||||
|
||||
// subscribing to a server allows for more entropy
|
||||
var remote = new ripple.Remote({
|
||||
servers: [
|
||||
{ host: 's1.ripple.com', port: 443, secure: true }
|
||||
]
|
||||
});
|
||||
|
||||
remote.connect(function(err, res) {
|
||||
/* remote connected */
|
||||
});
|
||||
|
||||
// Wait for randomness to have been added.
|
||||
// The entropy of the random generator is increased
|
||||
// by random data received from a rippled
|
||||
remote.once('random', function(err, info) {
|
||||
var wallet = ripple.Wallet.generate();
|
||||
console.log(wallet);
|
||||
// { address: 'rEf4sbVobiiDGExrNj2PkNHGMA8eS6jWh3',
|
||||
// secret: 'shFh4a38EZpEdZxrLifEnVPAoBRce' }
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
##Sending rippled API requests
|
||||
|
||||
`Remote` contains functions for constructing a `Request` object.
|
||||
`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.
|
||||
|
||||
@@ -124,29 +140,29 @@ See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on s
|
||||
'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();
|
||||
});
|
||||
@@ -157,10 +173,10 @@ See the [wiki](https://ripple.com/wiki/JSON_Messages#subscribe) for details on s
|
||||
|
||||
##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.
|
||||
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 '1 XRP' or '10.50 USD' to the type of Amount object used by the Ripple network.
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
@@ -171,7 +187,7 @@ var Amount = require('ripple-lib').Amount;
|
||||
var MY_ADDRESS = 'rrrMyAddress';
|
||||
var MY_SECRET = 'secret';
|
||||
var RECIPIENT = 'rrrRecipient';
|
||||
var AMOUNT = Amount.from_human('1XRP');
|
||||
var AMOUNT = Amount.from_human('1 USD').set_issuer('rrrIssuer');
|
||||
|
||||
var remote = new Remote({ /* Remote options */ });
|
||||
|
||||
@@ -179,8 +195,8 @@ remote.connect(function() {
|
||||
remote.setSecret(MY_ADDRESS, MY_SECRET);
|
||||
|
||||
var transaction = remote.createTransaction('Payment', {
|
||||
account: MY_ADDRESS,
|
||||
destination: RECIPIENT,
|
||||
account: MY_ADDRESS,
|
||||
destination: RECIPIENT,
|
||||
amount: AMOUNT
|
||||
});
|
||||
|
||||
@@ -201,12 +217,12 @@ Since the fee required for a transaction may change between the time when the or
|
||||
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
|
||||
|
||||
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:
|
||||
Submitting a trade offer to the network is similar to submitting a payment transaction. Here is an example offering to sell 1 USD in exchange for 100 XRP:
|
||||
|
||||
```js
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
/* Loading ripple-lib Remote and Amount modules in Node.js */
|
||||
var Remote = require('ripple-lib').Remote;
|
||||
var Amount = require('ripple-lib').Amount;
|
||||
|
||||
@@ -225,7 +241,7 @@ remote.connect(function() {
|
||||
|
||||
var transaction = remote.createTransaction('OfferCreate', {
|
||||
account: MY_ADDRESS,
|
||||
taker_pays: '1',
|
||||
taker_pays: '100',
|
||||
taker_gets: '1/USD/' + GATEWAY
|
||||
});
|
||||
|
||||
|
||||
240
npm-shrinkwrap.json
generated
240
npm-shrinkwrap.json
generated
@@ -1,140 +1,164 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.0-rc5",
|
||||
"version": "0.13.0-rc1",
|
||||
"npm-shrinkwrap-version": "5.4.0",
|
||||
"node-version": "v0.12.6",
|
||||
"dependencies": {
|
||||
"async": {
|
||||
"version": "0.8.0",
|
||||
"from": "async@>=0.8.0 <0.9.0"
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz"
|
||||
},
|
||||
"babel-runtime": {
|
||||
"version": "5.8.3",
|
||||
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.3.tgz",
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "0.9.18",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-0.9.18.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"bignumber.js": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.7.tgz"
|
||||
},
|
||||
"extend": {
|
||||
"version": "1.2.1",
|
||||
"from": "extend@>=1.2.1 <1.3.0"
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz"
|
||||
},
|
||||
"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",
|
||||
"https-proxy-agent": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz",
|
||||
"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"
|
||||
"agent-base": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.0.0.tgz",
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "1.0.4",
|
||||
"from": "debug@>=1.0.1 <1.1.0",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz",
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "0.6.2",
|
||||
"from": "ms@0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz"
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.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"
|
||||
"extend": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.0.tgz",
|
||||
"dependencies": {
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.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",
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.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"
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"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"
|
||||
}
|
||||
}
|
||||
"jsonpointer": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-1.1.0.tgz"
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "3.10.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.0.tgz"
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "2.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.5.2.tgz"
|
||||
},
|
||||
"ripple-lib-transactionparser": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ripple-lib-transactionparser/-/ripple-lib-transactionparser-0.3.2.tgz",
|
||||
"dependencies": {
|
||||
"bignumber.js": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-1.4.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ripple-wallet-generator": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ripple-wallet-generator/-/ripple-wallet-generator-1.0.3.tgz"
|
||||
},
|
||||
"simple-asyncify": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/simple-asyncify/-/simple-asyncify-0.1.0.tgz"
|
||||
},
|
||||
"sjcl-extended": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "git://github.com/ripple/sjcl-extended.git#d8cf8b22e7d97193c54e1f65113e3edcf200ca17",
|
||||
"dependencies": {
|
||||
"sjcl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.3.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "0.4.32",
|
||||
"from": "ws@>=0.4.31 <0.5.0",
|
||||
"version": "0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-0.7.2.tgz",
|
||||
"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"
|
||||
"bufferutil": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-1.1.0.tgz",
|
||||
"dependencies": {
|
||||
"bindings": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"version": "0.0.6",
|
||||
"from": "options@>=0.0.5"
|
||||
"resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz"
|
||||
},
|
||||
"ultron": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz"
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-1.1.0.tgz",
|
||||
"dependencies": {
|
||||
"bindings": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz"
|
||||
},
|
||||
"nan": {
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-1.8.4.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
66
package.json
66
package.json
@@ -1,47 +1,65 @@
|
||||
{
|
||||
"name": "ripple-lib",
|
||||
"version": "0.9.2-rc6",
|
||||
"version": "0.13.0-rc1",
|
||||
"license": "ISC",
|
||||
"description": "A JavaScript API for interacting with Ripple in Node.js and the browser",
|
||||
"files": [
|
||||
"src/js/*",
|
||||
"dist/npm/*",
|
||||
"bin/*",
|
||||
"build/*",
|
||||
"test/*",
|
||||
"Makefile",
|
||||
"Gulpfile.js"
|
||||
],
|
||||
"main": "src/js/ripple",
|
||||
"main": "dist/npm/",
|
||||
"directories": {
|
||||
"test": "test"
|
||||
},
|
||||
"dependencies": {
|
||||
"async": "~0.8.0",
|
||||
"ws": "~0.4.31",
|
||||
"async": "~0.9.0",
|
||||
"babel-runtime": "^5.5.4",
|
||||
"bignumber.js": "^2.0.3",
|
||||
"extend": "~1.2.1",
|
||||
"https-proxy-agent": "^1.0.0",
|
||||
"is-my-json-valid": "^2.12.0",
|
||||
"lodash": "^3.1.0",
|
||||
"lru-cache": "~2.5.0",
|
||||
"superagent": "^0.18.0",
|
||||
"ripple-wallet-generator": "1.0.1"
|
||||
"ripple-lib-transactionparser": "^0.3.2",
|
||||
"ripple-wallet-generator": "^1.0.3",
|
||||
"simple-asyncify": "^0.1.0",
|
||||
"sjcl-extended": "ripple/sjcl-extended#1.0.3",
|
||||
"ws": "~0.7.1"
|
||||
},
|
||||
"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",
|
||||
"assert-diff": "^1.0.1",
|
||||
"babel": "^5.5.4",
|
||||
"babel-core": "^5.5.4",
|
||||
"babel-eslint": "^3.1.23",
|
||||
"babel-loader": "^5.0.0",
|
||||
"coveralls": "~2.10.0",
|
||||
"nock": "^0.34.1",
|
||||
"eslint": "^0.24.0",
|
||||
"eslint-plugin-flowtype": "^1.0.0",
|
||||
"eventemitter2": "^0.4.14",
|
||||
"flow-bin": "^0.13.1",
|
||||
"gulp": "~3.8.10",
|
||||
"gulp-bump": "~0.1.13",
|
||||
"gulp-rename": "~1.2.0",
|
||||
"gulp-uglify": "~1.1.0",
|
||||
"istanbul": "~0.3.5",
|
||||
"mocha": "~2.1.0",
|
||||
"webpack": "~1.5.3",
|
||||
"yargs": "~1.3.1"
|
||||
},
|
||||
"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"
|
||||
"build": "gulp",
|
||||
"clean": "rm -rf dist/npm && rm -rf build/flow",
|
||||
"typecheck": "babel --optional runtime --blacklist flow -d build/flow/ src/ && flow check",
|
||||
"compile": "babel --optional runtime -d dist/npm/ src/ && cp -r src/api/common/schemas/ dist/npm/api/common/schemas/",
|
||||
"compile-with-source-maps": "babel --optional runtime -s -t -d dist/npm/ src/",
|
||||
"prepublish": "npm run clean && npm run compile",
|
||||
"test": "istanbul test _mocha",
|
||||
"coveralls": "cat ./coverage/lcov.info | coveralls",
|
||||
"lint": "if ! [ -f eslintrc ]; then curl -o eslintrc 'https://raw.githubusercontent.com/ripple/javascript-style-guide/es6/eslintrc'; echo 'plugins:\n - flowtype' >> eslintrc; fi; eslint --reset -c eslintrc src/",
|
||||
"perf": "./scripts/perf_test.sh"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -49,6 +67,6 @@
|
||||
},
|
||||
"readmeFilename": "README.md",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
}
|
||||
|
||||
11
scripts/perf_test.sh
Executable file
11
scripts/perf_test.sh
Executable file
@@ -0,0 +1,11 @@
|
||||
#!/bin/sh
|
||||
URL="https://www.dropbox.com/s/a0gy7vbb86eeqlq/ledger-full-1000000.json?dl=1"
|
||||
DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
|
||||
DEST="$DIR/cache/ledger-full-1000000.json"
|
||||
if [ ! -e "$DEST" ]
|
||||
then
|
||||
echo "Downloading test data..."
|
||||
mkdir -p "$DIR/cache"
|
||||
curl -L "$URL" > "$DEST"
|
||||
fi
|
||||
npm run compile && time node "$DIR/verify_ledger_json.js" "$DEST"
|
||||
@@ -17,15 +17,15 @@ echo "publish to npm"
|
||||
npm publish
|
||||
exit_on_error
|
||||
|
||||
rm -rf dist
|
||||
rm -rf dist/bower
|
||||
echo ""
|
||||
echo "publish to bower"
|
||||
|
||||
git clone git@github.com:ripple/bower-ripple.git dist
|
||||
git clone git@github.com:ripple/bower-ripple.git dist/bower
|
||||
gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
@@ -40,4 +40,4 @@ exit_on_error
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
@@ -17,15 +17,15 @@ echo "publish rc to npm"
|
||||
npm publish --tag beta
|
||||
exit_on_error
|
||||
|
||||
rm -rf dist
|
||||
rm -rf dist/bower
|
||||
echo ""
|
||||
echo "publish to bower"
|
||||
|
||||
git clone git@github.com:ripple/bower-ripple.git dist
|
||||
git clone git@github.com:ripple/bower-ripple.git dist/bower
|
||||
gulp bower
|
||||
exit_on_error
|
||||
|
||||
cd dist
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
@@ -40,4 +40,4 @@ exit_on_error
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
rm -rf dist
|
||||
git clone git@github.com:ripple/bower-ripple.git dist
|
||||
rm -rf dist/bower
|
||||
git clone git@github.com:ripple/bower-ripple.git dist/bower
|
||||
gulp bower
|
||||
cd dist
|
||||
cd dist/bower
|
||||
version=$(cat bower.json | grep -Eo '([0-9]\.?)+(-rc[0-9])?')
|
||||
echo "version: $version"
|
||||
git add ripple.js ripple-debug.js ripple-min.js bower.json
|
||||
@@ -9,4 +9,4 @@ git commit -m "[TASK] add v$version"
|
||||
git tag "v$version"
|
||||
git push origin master
|
||||
git push --tags origin master
|
||||
cd ..
|
||||
cd ..
|
||||
|
||||
@@ -1,31 +1,35 @@
|
||||
/* eslint-disable no-var */
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var Ledger = require('../src/js/ripple/ledger').Ledger;
|
||||
var Amount = require('../dist/npm').Amount;
|
||||
var Ledger = require('../dist/npm').Ledger;
|
||||
|
||||
function parse_options(from, flags) {
|
||||
var argv = from.slice(),
|
||||
opts = {argv:argv};
|
||||
opts_ = {argv: argv};
|
||||
|
||||
flags.forEach(function(f) {
|
||||
// Do we have the flag?
|
||||
var flag_index = argv.indexOf('--' + f);
|
||||
// normalize the name of the flag
|
||||
f = f.replace('-', '_');
|
||||
// opts has Boolean value for normalized flag key
|
||||
opts[f] = !!~flag_index;
|
||||
if (opts[f]) {
|
||||
var flag = f.replace('-', '_');
|
||||
// opts_ has Boolean value for normalized flag key
|
||||
opts_[flag] = flag_index !== -1;
|
||||
if (opts_[flag]) {
|
||||
// remove the flag from the argv
|
||||
argv.splice(flag_index, 1);
|
||||
}
|
||||
});
|
||||
return opts;
|
||||
return opts_;
|
||||
}
|
||||
|
||||
var opts = parse_options(process.argv.slice(2), // remove `node` and `this.js`
|
||||
['sanity-test']);
|
||||
|
||||
if (opts.argv.length < 1) {
|
||||
console.error("Usage: scripts/verify_ledger_json path/to/ledger.json");
|
||||
console.error(" optional: --sanity-test (json>binary>json>binary)");
|
||||
console.error('Usage: scripts/verify_ledger_json path/to/ledger.json');
|
||||
console.error(' optional: --sanity-test (json>binary>json>binary)');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -36,14 +40,27 @@ var ledger = Ledger.from_json(JSON.parse(json));
|
||||
// before finally serializing for hashing. This is mostly to expose any issues
|
||||
// with ripple-libs binary <--> json codecs.
|
||||
if (opts.sanity_test) {
|
||||
console.log("All accountState nodes will be processed from " +
|
||||
"json->binary->json->binary. This may take some time " +
|
||||
"with large ledgers.");
|
||||
console.log('All accountState nodes will be processed from ' +
|
||||
'json->binary->json->binary. This may take some time ' +
|
||||
'with large ledgers.');
|
||||
}
|
||||
|
||||
console.log("Transaction hash in header: " + ledger.ledger_json.transaction_hash);
|
||||
console.log("Calculated transaction hash: " + ledger.calc_tx_hash().to_hex());
|
||||
console.log("Account state hash in header: " + ledger.ledger_json.account_hash);
|
||||
console.log("Calculated account state hash: " + ledger.calc_account_hash(
|
||||
{sanity_test:opts.sanity_test})
|
||||
.to_hex());
|
||||
// To recompute the hashes of some ledgers, we must allow values that slipped in
|
||||
// before strong policies were in place.
|
||||
Amount.strict_mode = false;
|
||||
|
||||
console.log('Transaction hash in header: ' +
|
||||
ledger.ledger_json.transaction_hash);
|
||||
console.log('Calculated transaction hash: ' +
|
||||
ledger.calc_tx_hash().to_hex());
|
||||
|
||||
console.log('Account state hash in header: ' +
|
||||
ledger.ledger_json.account_hash);
|
||||
|
||||
if (ledger.ledger_json.accountState) {
|
||||
console.log('Calculated account state hash: ' +
|
||||
ledger.calc_account_hash({sanity_test: opts.sanity_test})
|
||||
.to_hex());
|
||||
} else {
|
||||
console.log('Ledger has no accountState');
|
||||
}
|
||||
|
||||
44
src/api/common/constants.js
Normal file
44
src/api/common/constants.js
Normal file
@@ -0,0 +1,44 @@
|
||||
'use strict';
|
||||
const core = require('./utils').core;
|
||||
const flagIndices = core.Transaction.set_clear_flags.AccountSet;
|
||||
const flags = core.Remote.flags.account_root;
|
||||
|
||||
const AccountFlags = {
|
||||
passwordSpent: flags.PasswordSpent,
|
||||
requireDestinationTag: flags.RequireDestTag,
|
||||
requireAuthorization: flags.RequireAuth,
|
||||
disallowIncomingXRP: flags.DisallowXRP,
|
||||
disableMasterKey: flags.DisableMaster,
|
||||
noFreeze: flags.NoFreeze,
|
||||
globalFreeze: flags.GlobalFreeze,
|
||||
defaultRipple: flags.DefaultRipple
|
||||
};
|
||||
|
||||
const AccountFlagIndices = {
|
||||
requireDestinationTag: flagIndices.asfRequireDest,
|
||||
requireAuthorization: flagIndices.asfRequireAuth,
|
||||
disallowIncomingXRP: flagIndices.asfDisallowXRP,
|
||||
disableMasterKey: flagIndices.asfDisableMaster,
|
||||
enableTransactionIDTracking: flagIndices.asfAccountTxnID,
|
||||
noFreeze: flagIndices.asfNoFreeze,
|
||||
globalFreeze: flagIndices.asfGlobalFreeze,
|
||||
defaultRipple: flagIndices.asfDefaultRipple
|
||||
};
|
||||
|
||||
const AccountFields = {
|
||||
EmailHash: {name: 'emailHash', encoding: 'hex',
|
||||
length: 32, defaults: '0'},
|
||||
WalletLocator: {name: 'walletLocator', encoding: 'hex',
|
||||
length: 64, defaults: '0'},
|
||||
WalletSize: {name: 'walletSize', defaults: 0},
|
||||
MessageKey: {name: 'messageKey'},
|
||||
Domain: {name: 'domain', encoding: 'hex'},
|
||||
TransferRate: {name: 'transferRate', defaults: 0},
|
||||
Signers: {name: 'signers'}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
AccountFields,
|
||||
AccountFlagIndices,
|
||||
AccountFlags
|
||||
};
|
||||
89
src/api/common/errors.js
Normal file
89
src/api/common/errors.js
Normal file
@@ -0,0 +1,89 @@
|
||||
/* eslint-disable valid-jsdoc */
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Base class for all errors
|
||||
*/
|
||||
function RippleError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
RippleError.prototype = new Error();
|
||||
RippleError.prototype.name = 'RippleError';
|
||||
|
||||
function ValidationError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
ValidationError.prototype = new RippleError();
|
||||
ValidationError.prototype.name = 'ValidationError';
|
||||
|
||||
/**
|
||||
* Timeout, disconnects and too busy
|
||||
*/
|
||||
function NetworkError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
NetworkError.prototype = new RippleError();
|
||||
NetworkError.prototype.name = 'NetworkError';
|
||||
|
||||
/**
|
||||
* Failed transactions, no paths found, not enough balance, etc.
|
||||
*/
|
||||
function RippledNetworkError(message) {
|
||||
this.message = message !== undefined ? message : 'Cannot connect to rippled';
|
||||
}
|
||||
RippledNetworkError.prototype = new NetworkError();
|
||||
|
||||
/**
|
||||
* Failed transactions, no paths found, not enough balance, etc.
|
||||
*/
|
||||
function TransactionError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
TransactionError.prototype = new RippleError();
|
||||
TransactionError.prototype.name = 'TransactionError';
|
||||
|
||||
/**
|
||||
* Asset could not be found
|
||||
*/
|
||||
function NotFoundError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
NotFoundError.prototype = new RippleError();
|
||||
NotFoundError.prototype.name = 'NotFoundError';
|
||||
|
||||
function MissingLedgerHistoryError(message) {
|
||||
this.message = message ||
|
||||
'Server is missing ledger history in the specified range';
|
||||
}
|
||||
MissingLedgerHistoryError.prototype = new RippleError();
|
||||
MissingLedgerHistoryError.prototype.name = 'MissingLedgerHistoryError';
|
||||
|
||||
/**
|
||||
* Request timed out
|
||||
*/
|
||||
function TimeOutError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
TimeOutError.prototype = new RippleError();
|
||||
TimeOutError.prototype.name = 'TimeOutError';
|
||||
|
||||
/**
|
||||
* API logic failed to do what it intended
|
||||
*/
|
||||
function ApiError(message) {
|
||||
this.message = message;
|
||||
}
|
||||
ApiError.prototype = new RippleError();
|
||||
ApiError.prototype.name = 'ApiError';
|
||||
|
||||
module.exports = {
|
||||
ValidationError: ValidationError,
|
||||
NetworkError: NetworkError,
|
||||
TransactionError: TransactionError,
|
||||
RippledNetworkError: RippledNetworkError,
|
||||
NotFoundError: NotFoundError,
|
||||
MissingLedgerHistoryError: MissingLedgerHistoryError,
|
||||
TimeOutError: TimeOutError,
|
||||
ApiError: ApiError,
|
||||
RippleError: RippleError
|
||||
};
|
||||
15
src/api/common/index.js
Normal file
15
src/api/common/index.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
|
||||
module.exports = {
|
||||
core: utils.core,
|
||||
constants: require('./constants'),
|
||||
errors: require('./errors'),
|
||||
validate: require('./validate'),
|
||||
dropsToXrp: utils.dropsToXrp,
|
||||
xrpToDrops: utils.xrpToDrops,
|
||||
toRippledAmount: utils.toRippledAmount,
|
||||
wrapCatch: utils.wrapCatch,
|
||||
composeAsync: utils.composeAsync,
|
||||
convertExceptions: utils.convertExceptions
|
||||
};
|
||||
63
src/api/common/schema-validator.js
Normal file
63
src/api/common/schema-validator.js
Normal file
@@ -0,0 +1,63 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const validator = require('is-my-json-valid');
|
||||
const core = require('./utils').core;
|
||||
const ValidationError = require('./errors').ValidationError;
|
||||
|
||||
let SCHEMAS = {};
|
||||
|
||||
function isValidAddress(address) {
|
||||
return core.UInt160.is_valid(address);
|
||||
}
|
||||
|
||||
function isValidLedgerHash(ledgerHash) {
|
||||
return core.UInt256.is_valid(ledgerHash);
|
||||
}
|
||||
|
||||
function loadSchema(filepath) {
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
||||
} catch (e) {
|
||||
throw new Error('Failed to parse schema: ' + filepath);
|
||||
}
|
||||
}
|
||||
|
||||
function endsWith(str, suffix) {
|
||||
return str.indexOf(suffix, str.length - suffix.length) !== -1;
|
||||
}
|
||||
|
||||
function loadSchemas(dir) {
|
||||
const filenames = fs.readdirSync(dir).filter(name => endsWith(name, '.json'));
|
||||
const schemas = filenames.map(name => loadSchema(path.join(dir, name)));
|
||||
return _.indexBy(schemas, 'title');
|
||||
}
|
||||
|
||||
function formatSchemaError(error) {
|
||||
return error.field + ' ' + error.message
|
||||
+ (error.value ? ' (' + JSON.stringify(error.value) + ')' : '');
|
||||
}
|
||||
|
||||
function formatSchemaErrors(errors) {
|
||||
return errors.map(formatSchemaError).join(', ');
|
||||
}
|
||||
|
||||
function schemaValidate(schemaName, object) {
|
||||
const formats = {address: isValidAddress,
|
||||
ledgerHash: isValidLedgerHash};
|
||||
const options = {schemas: SCHEMAS, formats: formats,
|
||||
verbose: true, greedy: true};
|
||||
const schema = SCHEMAS[schemaName];
|
||||
if (schema === undefined) {
|
||||
throw new Error('schema not found for: ' + schemaName);
|
||||
}
|
||||
const validate = validator(schema, options);
|
||||
const isValid = validate(object);
|
||||
if (!isValid) {
|
||||
throw new ValidationError(formatSchemaErrors(validate.errors));
|
||||
}
|
||||
}
|
||||
|
||||
SCHEMAS = loadSchemas(path.join(__dirname, './schemas'));
|
||||
module.exports = schemaValidate;
|
||||
8
src/api/common/schemas/address.json
Normal file
8
src/api/common/schemas/address.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "address",
|
||||
"description": "A Ripple account address",
|
||||
"type": "string",
|
||||
"format": "address",
|
||||
"pattern": "^r[1-9A-HJ-NP-Za-km-z]{25,34}$"
|
||||
}
|
||||
15
src/api/common/schemas/adjustment.json
Normal file
15
src/api/common/schemas/adjustment.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "adjustment",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {"$ref": "address"},
|
||||
"amount": {"$ref": "amount"},
|
||||
"tag": {
|
||||
"description": "A string representing an unsigned 32-bit integer most commonly used to refer to a sender's hosted account at a Ripple gateway",
|
||||
"$ref": "uint32"
|
||||
}
|
||||
},
|
||||
"required": ["address", "amount"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
9
src/api/common/schemas/amount.json
Normal file
9
src/api/common/schemas/amount.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "amount",
|
||||
"description": "An Amount on the Ripple Protocol, used also for XRP in the ripple-rest API",
|
||||
"allOf": [
|
||||
{"$ref": "amountbase"},
|
||||
{"required": ["value"]}
|
||||
]
|
||||
}
|
||||
44
src/api/common/schemas/amountbase.json
Normal file
44
src/api/common/schemas/amountbase.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "amountbase",
|
||||
"description": "Base class for amount and issue",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"value": {
|
||||
"description": "The quantity of the currency, denoted as a string to retain floating point precision",
|
||||
"$ref": "value"
|
||||
},
|
||||
"currency": {
|
||||
"description": "The three-character code or hex string used to denote currencies",
|
||||
"$ref": "currency"
|
||||
},
|
||||
"counterparty": {
|
||||
"description": "The Ripple account address of the currency's issuer or gateway",
|
||||
"$ref": "address"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["currency"],
|
||||
"oneOf": [
|
||||
{
|
||||
"properties": {
|
||||
"currency": {
|
||||
"not": {
|
||||
"enum": ["XRP"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["counterparty"]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"currency": {
|
||||
"enum": ["XRP"]
|
||||
}
|
||||
},
|
||||
"not": {
|
||||
"required": ["counterparty"]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
8
src/api/common/schemas/blob.json
Normal file
8
src/api/common/schemas/blob.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "blob",
|
||||
"description": "An uppercase hex string representation of a transaction",
|
||||
"type": "string",
|
||||
"minLength": "1",
|
||||
"pattern": "^[0-9A-F]*$"
|
||||
}
|
||||
10
src/api/common/schemas/cancellation.json
Normal file
10
src/api/common/schemas/cancellation.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "cancellation",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"orderSequence": {"$ref": "sequence"}
|
||||
},
|
||||
"required": ["orderSequence"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
7
src/api/common/schemas/currency.json
Normal file
7
src/api/common/schemas/currency.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "currency",
|
||||
"description": "The three-character code or hex string used to denote currencies",
|
||||
"type": "string",
|
||||
"pattern": "^([a-zA-Z0-9<>(){}[\\]|?!@#$%^&*]{3}|[A-F0-9]{40})$"
|
||||
}
|
||||
7
src/api/common/schemas/hash128.json
Normal file
7
src/api/common/schemas/hash128.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "hash128",
|
||||
"description": "The hex representation of a 128-bit hash",
|
||||
"type": "string",
|
||||
"pattern": "^[A-F0-9]{32}$"
|
||||
}
|
||||
7
src/api/common/schemas/hash256.json
Normal file
7
src/api/common/schemas/hash256.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "hash256",
|
||||
"description": "The hex representation of a 256-bit hash",
|
||||
"type": "string",
|
||||
"pattern": "^[A-F0-9]{64}$"
|
||||
}
|
||||
42
src/api/common/schemas/instructions.json
Normal file
42
src/api/common/schemas/instructions.json
Normal file
@@ -0,0 +1,42 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "instructions",
|
||||
"description": "Instructions for executing a transaction",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sequence": {
|
||||
"description": "The sequence number, relative to the initiating account, of this transaction.",
|
||||
"$ref": "sequence"
|
||||
},
|
||||
"fee": {
|
||||
"description": "Fixed Fee",
|
||||
"$ref": "value"
|
||||
},
|
||||
"maxFee": {
|
||||
"description": "Max Fee",
|
||||
"$ref": "value"
|
||||
},
|
||||
"maxLedgerVersion": {
|
||||
"description": "Highest ledger version number that a transaction can appear in.",
|
||||
"$ref": "ledgerVersion"
|
||||
},
|
||||
"maxLedgerVersionOffset": {
|
||||
"description": "Offset from current legder version to highest ledger version that a transaction can appear in.",
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "fee and maxFee are mutually exclusive",
|
||||
"required": ["fee", "maxFee"]
|
||||
},
|
||||
{
|
||||
"description": "maxLedgerVersion and maxLedgerVersionOffset are mutually exclusive",
|
||||
"required": ["maxLedgerVersion", "maxLedgerVersionOffset"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
9
src/api/common/schemas/issue.json
Normal file
9
src/api/common/schemas/issue.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "issue",
|
||||
"description": "A currency-counterparty pair, or just currency if it's XRP",
|
||||
"allOf": [
|
||||
{"$ref": "amountbase"},
|
||||
{"not": {"required": ["value"]}}
|
||||
]
|
||||
}
|
||||
7
src/api/common/schemas/ledgerversion.json
Normal file
7
src/api/common/schemas/ledgerversion.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "ledgerVersion",
|
||||
"description": "A ledger version number",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
22
src/api/common/schemas/memo.json
Normal file
22
src/api/common/schemas/memo.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "memo",
|
||||
"description": "Memo objects represent arbitrary data that can be included in a transaction",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"pattern": "^[A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=%]*$"
|
||||
},
|
||||
"format": {
|
||||
"pattern": "^[A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=%]*$"
|
||||
},
|
||||
"data": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"anyOf": [
|
||||
{"required": ["data"]},
|
||||
{"required": ["type"]}
|
||||
]
|
||||
}
|
||||
25
src/api/common/schemas/order.json
Normal file
25
src/api/common/schemas/order.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "order",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"direction": {
|
||||
"type": "string",
|
||||
"enum": ["buy", "sell"]
|
||||
},
|
||||
"quantity": {"$ref": "amount"},
|
||||
"totalPrice": {"$ref": "amount"},
|
||||
"immediateOrCancel": {"type": "boolean"},
|
||||
"fillOrKill": {"type": "boolean"},
|
||||
"passive": {
|
||||
"description": "If enabled, the offer will not consume offers that exactly match it, and instead becomes an Offer node in the ledger. It will still consume offers that cross it.",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": ["direction", "quantity", "totalPrice"],
|
||||
"additionalProperties": false,
|
||||
"not": {
|
||||
"description": "immediateOrCancel and fillOrKill are mutually exclusive",
|
||||
"required": ["immediateOrCancel", "fillOrKill"]
|
||||
}
|
||||
}
|
||||
11
src/api/common/schemas/orderbook.json
Normal file
11
src/api/common/schemas/orderbook.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "orderbook",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"base": {"$ref": "issue"},
|
||||
"counter": {"$ref": "issue"}
|
||||
},
|
||||
"required": ["base", "counter"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
14
src/api/common/schemas/orders-options.json
Normal file
14
src/api/common/schemas/orders-options.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "orders-options",
|
||||
"description": "Options for getOrders and getOrderbook",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"ledgerVersion": {"$ref": "ledgerVersion"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
31
src/api/common/schemas/pathfind.json
Normal file
31
src/api/common/schemas/pathfind.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "pathfind",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"address": {"$ref": "address"},
|
||||
"currencies": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currency": {"$ref": "currency"},
|
||||
"counterparty": {"$ref": "address"}
|
||||
},
|
||||
"required": ["currency"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": ["address"]
|
||||
},
|
||||
"destination": {"$ref": "adjustment"}
|
||||
},
|
||||
"required": ["source", "destination"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
35
src/api/common/schemas/payment.json
Normal file
35
src/api/common/schemas/payment.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "payment",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"source": {"$ref": "adjustment"},
|
||||
"destination": {"$ref": "adjustment"},
|
||||
"paths": {"type": "string"},
|
||||
"slippage": {
|
||||
"description": "An optional cushion for the source_amount to increase the likelihood that the payment will succeed. The source_account will never be charged more than source_amount.value + source_slippage",
|
||||
"$ref": "value"
|
||||
},
|
||||
"memos": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "memo"
|
||||
}
|
||||
},
|
||||
"invoiceID": {
|
||||
"description": "A 256-bit hash that can be used to identify a particular payment",
|
||||
"$ref": "hash256"
|
||||
},
|
||||
"allowPartialPayment": {
|
||||
"description": "A boolean that, if set to true, indicates that this payment should go through even if the whole amount cannot be delivered because of a lack of liquidity or funds in the source_account account",
|
||||
"type": "boolean"
|
||||
},
|
||||
"noDirectRipple": {
|
||||
"description": "A boolean that can be set to true if paths are specified and the sender would like the Ripple Network to disregard any direct paths from the source_account to the destination_account. This may be used to take advantage of an arbitrage opportunity or by gateways wishing to issue balances from a hot wallet to a user who has mistakenly set a trustline directly to the hot wallet",
|
||||
"type": "boolean"
|
||||
},
|
||||
"limitQuality": {"type": "boolean"}
|
||||
},
|
||||
"required": ["source", "destination"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
8
src/api/common/schemas/quality.json
Normal file
8
src/api/common/schemas/quality.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "quality",
|
||||
"description": "Ratio for incoming/outgoing transit fees represented in billionths. (For example, a value of 500 million represents a 0.5:1 ratio.) As a special case, 0 is treated as a 1:1 ratio.",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1000000000
|
||||
}
|
||||
7
src/api/common/schemas/sequence.json
Normal file
7
src/api/common/schemas/sequence.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "sequence",
|
||||
"description": "An account transaction sequence number",
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
}
|
||||
10
src/api/common/schemas/settings-options.json
Normal file
10
src/api/common/schemas/settings-options.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "settings-options",
|
||||
"description": "Options for getSettings and getAccountInfo",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ledgerVersion": {"$ref": "ledgerVersion"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
27
src/api/common/schemas/settings.json
Normal file
27
src/api/common/schemas/settings.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "settings",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"passwordSpent": {"type": "boolean"},
|
||||
"requireDestinationTag": {"type": "boolean"},
|
||||
"requireAuthorization": {"type": "boolean"},
|
||||
"disallowIncomingXRP": {"type": "boolean"},
|
||||
"disableMasterKey": {"type": "boolean"},
|
||||
"enableTransactionIDTracking": {"type": "boolean"},
|
||||
"noFreeze": {"type": "boolean"},
|
||||
"globalFreeze": {"type": "boolean"},
|
||||
"defaultRipple": {"type": "boolean"},
|
||||
"emailHash": {"$ref": "hash128"},
|
||||
"walletLocator": {"$ref": "hash256"},
|
||||
"walletSize": {"type": "integer"},
|
||||
"messageKey": {"type": "string"},
|
||||
"domain": {"type": "string"},
|
||||
"transferRate": {"type": "integer"},
|
||||
"signers": {"type": "string"},
|
||||
"regularKey": {"$ref": "address"}
|
||||
},
|
||||
"minProperties": 1,
|
||||
"maxProperties": 1,
|
||||
"additionalProperties": false
|
||||
}
|
||||
7
src/api/common/schemas/timestamp.json
Normal file
7
src/api/common/schemas/timestamp.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "timestamp",
|
||||
"description": "An ISO 8601 combined date and time timestamp",
|
||||
"type": "string",
|
||||
"pattern": "^$|^[0-9]{4}-[0-1][0-9]-[0-3][0-9]T(2[0-3]|[01][0-9]):[0-5][0-9]:[0-5][0-9](Z|[+](2[0-3]|[01][0-9]):[0-5][0-9])$"
|
||||
}
|
||||
11
src/api/common/schemas/transaction-options.json
Normal file
11
src/api/common/schemas/transaction-options.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "transaction-options",
|
||||
"description": "Options for getTransaction",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"minLedgerVersion": {"$ref": "ledgerVersion"},
|
||||
"maxLedgerVersion": {"$ref": "ledgerVersion"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
40
src/api/common/schemas/transactions-options.json
Normal file
40
src/api/common/schemas/transactions-options.json
Normal file
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "transactions-options",
|
||||
"description": "Options for getTransactions",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"start": {"$ref": "hash256"},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"minLedgerVersion": {"$ref": "ledgerVersion"},
|
||||
"maxLedgerVersion": {"$ref": "ledgerVersion"},
|
||||
"earliestFirst": {"type": "boolean"},
|
||||
"excludeFailures": {"type": "boolean"},
|
||||
"initiated": {"type": "boolean"},
|
||||
"counterparty": {"$ref": "address"},
|
||||
"types": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"enum": [
|
||||
"payment",
|
||||
"trustline",
|
||||
"order",
|
||||
"orderCancellation",
|
||||
"settings"
|
||||
]
|
||||
}
|
||||
},
|
||||
"binary": {"type": "boolean"}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"not": {
|
||||
"anyOf": [
|
||||
{"required": ["incoming", "outgoing"]},
|
||||
{"required": ["start", "minLedgerVersion"]},
|
||||
{"required": ["start", "maxLedgerVersion"]}
|
||||
]
|
||||
}
|
||||
}
|
||||
17
src/api/common/schemas/trustline.json
Normal file
17
src/api/common/schemas/trustline.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "trustline",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"currency": {"$ref": "currency"},
|
||||
"counterparty": {"$ref": "address"},
|
||||
"limit": {"$ref": "value"},
|
||||
"qualityIn": {"$ref": "quality"},
|
||||
"qualityOut": {"$ref": "quality"},
|
||||
"allowRippling": {"type": "boolean"},
|
||||
"authorized": {"type": "boolean"},
|
||||
"frozen": {"type": "boolean"}
|
||||
},
|
||||
"required": ["currency", "counterparty", "limit"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
16
src/api/common/schemas/trustlines-options.json
Normal file
16
src/api/common/schemas/trustlines-options.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "trustlines-options",
|
||||
"description": "Options for getTrustlines and getBalances",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"counterparty": {"$ref": "address"},
|
||||
"currency": {"$ref": "currency"},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"ledgerVersion": {"$ref": "ledgerVersion"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
10
src/api/common/schemas/tx.json
Normal file
10
src/api/common/schemas/tx.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "tx",
|
||||
"description": "An object in rippled txJSON format",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"Account": {"$ref": "address"}
|
||||
},
|
||||
"required": ["Account"]
|
||||
}
|
||||
8
src/api/common/schemas/uint32.json
Normal file
8
src/api/common/schemas/uint32.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "uint32",
|
||||
"description": "A 32-bit unsigned integer",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 4294967295
|
||||
}
|
||||
7
src/api/common/schemas/value.json
Normal file
7
src/api/common/schemas/value.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "value",
|
||||
"description": "A string representation of a non-negative floating point number",
|
||||
"type": "string",
|
||||
"pattern": "^[0-9]*[.]?[0-9]+([eE][-+]?[0-9]+)?$"
|
||||
}
|
||||
71
src/api/common/utils.js
Normal file
71
src/api/common/utils.js
Normal file
@@ -0,0 +1,71 @@
|
||||
'use strict';
|
||||
const BigNumber = require('bignumber.js');
|
||||
const core = require('../../core');
|
||||
const errors = require('./errors');
|
||||
|
||||
function dropsToXrp(drops) {
|
||||
return (new BigNumber(drops)).dividedBy(1000000.0).toString();
|
||||
}
|
||||
|
||||
function xrpToDrops(xrp) {
|
||||
return (new BigNumber(xrp)).times(1000000.0).floor().toString();
|
||||
}
|
||||
|
||||
function toRippledAmount(amount) {
|
||||
if (amount.currency === 'XRP') {
|
||||
return xrpToDrops(amount.value);
|
||||
}
|
||||
return {
|
||||
currency: amount.currency,
|
||||
issuer: amount.counterparty ? amount.counterparty : amount.issuer,
|
||||
value: amount.value
|
||||
};
|
||||
}
|
||||
|
||||
function wrapCatch(asyncFunction: () => void): () => void {
|
||||
return function() {
|
||||
try {
|
||||
asyncFunction.apply(this, arguments);
|
||||
} catch (error) {
|
||||
const callback = arguments[arguments.length - 1];
|
||||
callback(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function composeAsync(wrapper, callback) {
|
||||
return function(error, data) {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
let result;
|
||||
try {
|
||||
result = wrapper(data);
|
||||
} catch (exception) {
|
||||
callback(exception);
|
||||
return;
|
||||
}
|
||||
callback(null, result);
|
||||
};
|
||||
}
|
||||
|
||||
function convertExceptions(f) {
|
||||
return function() {
|
||||
try {
|
||||
return f.apply(this, arguments);
|
||||
} catch (error) {
|
||||
throw new errors.ApiError(error.message);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
core,
|
||||
dropsToXrp,
|
||||
xrpToDrops,
|
||||
toRippledAmount,
|
||||
wrapCatch,
|
||||
composeAsync,
|
||||
convertExceptions
|
||||
};
|
||||
65
src/api/common/validate.js
Normal file
65
src/api/common/validate.js
Normal file
@@ -0,0 +1,65 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const core = require('./utils').core;
|
||||
const ValidationError = require('./errors').ValidationError;
|
||||
const schemaValidate = require('./schema-validator');
|
||||
|
||||
function error(text) {
|
||||
return new ValidationError(text);
|
||||
}
|
||||
|
||||
function validateAddressAndSecret(obj) {
|
||||
const address = obj.address;
|
||||
const secret = obj.secret;
|
||||
schemaValidate('address', address);
|
||||
if (!secret) {
|
||||
throw error('Parameter missing: secret');
|
||||
}
|
||||
try {
|
||||
if (!core.Seed.from_json(secret).get_key(address)) {
|
||||
throw error('secret does not match address');
|
||||
}
|
||||
} catch (exception) {
|
||||
throw error('secret does not match address');
|
||||
}
|
||||
}
|
||||
|
||||
function validateLedgerRange(options) {
|
||||
if (!_.isUndefined(options.minLedgerVersion)
|
||||
&& !_.isUndefined(options.maxLedgerVersion)) {
|
||||
if (Number(options.minLedgerVersion) > Number(options.maxLedgerVersion)) {
|
||||
throw error('minLedgerVersion must not be greater than maxLedgerVersion');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateOptions(schema, options) {
|
||||
schemaValidate(schema, options);
|
||||
validateLedgerRange(options);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
address: _.partial(schemaValidate, 'address'),
|
||||
addressAndSecret: validateAddressAndSecret,
|
||||
currency: _.partial(schemaValidate, 'currency'),
|
||||
identifier: _.partial(schemaValidate, 'hash256'),
|
||||
sequence: _.partial(schemaValidate, 'sequence'),
|
||||
order: _.partial(schemaValidate, 'order'),
|
||||
orderbook: _.partial(schemaValidate, 'orderbook'),
|
||||
payment: _.partial(schemaValidate, 'payment'),
|
||||
pathfind: _.partial(schemaValidate, 'pathfind'),
|
||||
settings: _.partial(schemaValidate, 'settings'),
|
||||
trustline: _.partial(schemaValidate, 'trustline'),
|
||||
txJSON: _.partial(schemaValidate, 'tx'),
|
||||
blob: _.partial(schemaValidate, 'blob'),
|
||||
getTransactionsOptions: _.partial(validateOptions, 'transactions-options'),
|
||||
getSettingsOptions: _.partial(validateOptions, 'settings-options'),
|
||||
getAccountInfoOptions: _.partial(validateOptions, 'settings-options'),
|
||||
getTrustlinesOptions: _.partial(validateOptions, 'trustlines-options'),
|
||||
getBalancesOptions: _.partial(validateOptions, 'trustlines-options'),
|
||||
getOrdersOptions: _.partial(validateOptions, 'orders-options'),
|
||||
getOrderbookOptions: _.partial(validateOptions, 'orders-options'),
|
||||
getTransactionOptions: _.partial(validateOptions, 'transaction-options'),
|
||||
options: _.partial(validateOptions, 'options'),
|
||||
instructions: _.partial(schemaValidate, 'instructions')
|
||||
};
|
||||
68
src/api/index.js
Normal file
68
src/api/index.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const core = require('./common').core;
|
||||
const server = require('./server/server');
|
||||
const connect = server.connect;
|
||||
const disconnect = server.disconnect;
|
||||
const getServerInfo = server.getServerInfo;
|
||||
const getFee = server.getFee;
|
||||
const isConnected = server.isConnected;
|
||||
const getLedgerVersion = server.getLedgerVersion;
|
||||
const getTransaction = require('./ledger/transaction');
|
||||
const getTransactions = require('./ledger/transactions');
|
||||
const getTrustlines = require('./ledger/trustlines');
|
||||
const getBalances = require('./ledger/balances');
|
||||
const getPaths = require('./ledger/pathfind');
|
||||
const getOrders = require('./ledger/orders');
|
||||
const getOrderbook = require('./ledger/orderbook');
|
||||
const getSettings = require('./ledger/settings');
|
||||
const getAccountInfo = require('./ledger/accountinfo');
|
||||
const preparePayment = require('./transaction/payment');
|
||||
const prepareTrustline = require('./transaction/trustline');
|
||||
const prepareOrder = require('./transaction/order');
|
||||
const prepareOrderCancellation = require('./transaction/ordercancellation');
|
||||
const prepareSettings = require('./transaction/settings');
|
||||
const sign = require('./transaction/sign');
|
||||
const submit = require('./transaction/submit');
|
||||
const errors = require('./common').errors;
|
||||
const convertExceptions = require('./common').convertExceptions;
|
||||
const generateWallet = convertExceptions(core.Wallet.generate);
|
||||
|
||||
function RippleAPI(options: {}) {
|
||||
const _options = _.assign({}, options, {automatic_resubmission: false});
|
||||
this.remote = new core.Remote(_options);
|
||||
}
|
||||
|
||||
RippleAPI.prototype = {
|
||||
connect,
|
||||
disconnect,
|
||||
isConnected,
|
||||
getServerInfo,
|
||||
getFee,
|
||||
getLedgerVersion,
|
||||
|
||||
getTransaction,
|
||||
getTransactions,
|
||||
getTrustlines,
|
||||
getBalances,
|
||||
getPaths,
|
||||
getOrders,
|
||||
getOrderbook,
|
||||
getSettings,
|
||||
getAccountInfo,
|
||||
|
||||
preparePayment,
|
||||
prepareTrustline,
|
||||
prepareOrder,
|
||||
prepareOrderCancellation,
|
||||
prepareSettings,
|
||||
sign,
|
||||
submit,
|
||||
|
||||
generateWallet,
|
||||
errors
|
||||
};
|
||||
|
||||
module.exports = RippleAPI;
|
||||
34
src/api/ledger/accountinfo.js
Normal file
34
src/api/ledger/accountinfo.js
Normal file
@@ -0,0 +1,34 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const removeUndefined = require('./parse/utils').removeUndefined;
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
|
||||
function formatAccountInfo(response) {
|
||||
const data = response.account_data;
|
||||
return removeUndefined({
|
||||
sequence: data.Sequence,
|
||||
xrpBalance: utils.common.dropsToXrp(data.Balance),
|
||||
ownerCount: data.OwnerCount,
|
||||
previousInitiatedTransactionID: data.AccountTxnID,
|
||||
previousAffectingTransactionID: data.PreviousTxnID,
|
||||
previousAffectingTransactionLedgerVersion: data.PreviousTxnLgrSeq
|
||||
});
|
||||
}
|
||||
|
||||
function getAccountInfo(account, options, callback) {
|
||||
validate.address(account);
|
||||
validate.getAccountInfoOptions(options);
|
||||
|
||||
const request = {
|
||||
account: account,
|
||||
ledger: options.ledgerVersion
|
||||
};
|
||||
|
||||
this.remote.requestAccountInfo(request,
|
||||
composeAsync(formatAccountInfo, callback));
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getAccountInfo);
|
||||
39
src/api/ledger/balances.js
Normal file
39
src/api/ledger/balances.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const utils = require('./utils');
|
||||
const getTrustlines = require('./trustlines');
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
|
||||
function getTrustlineBalanceAmount(trustline) {
|
||||
return {
|
||||
currency: trustline.specification.currency,
|
||||
counterparty: trustline.specification.counterparty,
|
||||
value: trustline.state.balance
|
||||
};
|
||||
}
|
||||
|
||||
function formatBalances(balances) {
|
||||
const xrpBalance = {
|
||||
currency: 'XRP',
|
||||
value: balances.xrp
|
||||
};
|
||||
return [xrpBalance].concat(
|
||||
balances.trustlines.map(getTrustlineBalanceAmount));
|
||||
}
|
||||
|
||||
function getBalances(account, options, callback) {
|
||||
validate.address(account);
|
||||
validate.getBalancesOptions(options);
|
||||
|
||||
const ledgerVersion = options.ledgerVersion
|
||||
|| this.remote.getLedgerSequence();
|
||||
async.parallel({
|
||||
xrp: _.partial(utils.getXRPBalance, this.remote, account, ledgerVersion),
|
||||
trustlines: _.partial(getTrustlines.bind(this), account, options)
|
||||
}, composeAsync(formatBalances, callback));
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getBalances);
|
||||
79
src/api/ledger/orderbook.js
Normal file
79
src/api/ledger/orderbook.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const utils = require('./utils');
|
||||
const parseOrderbookOrder = require('./parse/orderbook-order');
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
|
||||
// account is to specify a "perspective", which affects which unfunded offers
|
||||
// are returned
|
||||
function getBookOffers(remote, account, ledgerVersion, limit,
|
||||
takerGets, takerPays, callback) {
|
||||
remote.requestBookOffers(utils.renameCounterpartyToIssuerInOrder({
|
||||
taker_gets: takerGets,
|
||||
taker_pays: takerPays,
|
||||
ledger: ledgerVersion || 'validated',
|
||||
limit: limit,
|
||||
taker: account
|
||||
}), composeAsync(data => data.offers, callback));
|
||||
}
|
||||
|
||||
function isSameIssue(a, b) {
|
||||
return a.currency === b.currency && a.counterparty === b.counterparty;
|
||||
}
|
||||
|
||||
function directionFilter(direction, order) {
|
||||
return order.specification.direction === direction;
|
||||
}
|
||||
|
||||
function flipOrder(order) {
|
||||
const specification = order.specification;
|
||||
const flippedSpecification = {
|
||||
quantity: specification.totalPrice,
|
||||
totalPrice: specification.quantity,
|
||||
direction: specification.direction === 'buy' ? 'sell' : 'buy'
|
||||
};
|
||||
const newSpecification = _.merge({}, specification, flippedSpecification);
|
||||
return _.merge({}, order, {specification: newSpecification});
|
||||
}
|
||||
|
||||
function alignOrder(base, order) {
|
||||
const quantity = order.specification.quantity;
|
||||
return isSameIssue(quantity, base) ? order : flipOrder(order);
|
||||
}
|
||||
|
||||
function formatBidsAndAsks(orderbook, offers) {
|
||||
// the "base" currency is the currency that you are buying or selling
|
||||
// the "counter" is the currency that the "base" is priced in
|
||||
// a "bid"/"ask" is an order to buy/sell the base, respectively
|
||||
// for bids: takerGets = totalPrice = counter, takerPays = quantity = base
|
||||
// for asks: takerGets = quantity = base, takerPays = totalPrice = counter
|
||||
// quality = takerPays / takerGets; price = totalPrice / quantity
|
||||
// for bids: lowest quality => lowest quantity/totalPrice => highest price
|
||||
// for asks: lowest quality => lowest totalPrice/quantity => lowest price
|
||||
// for both bids and asks, lowest quality is closest to mid-market
|
||||
// we sort the orders so that earlier orders are closer to mid-market
|
||||
const orders = _.sortBy(offers, 'quality').map(parseOrderbookOrder);
|
||||
const alignedOrders = orders.map(_.partial(alignOrder, orderbook.base));
|
||||
const bids = alignedOrders.filter(_.partial(directionFilter, 'buy'));
|
||||
const asks = alignedOrders.filter(_.partial(directionFilter, 'sell'));
|
||||
return {bids, asks};
|
||||
}
|
||||
|
||||
function getOrderbook(account, orderbook, options, callback) {
|
||||
validate.address(account);
|
||||
validate.orderbook(orderbook);
|
||||
validate.getOrderbookOptions(options);
|
||||
|
||||
const getter = _.partial(getBookOffers, this.remote, account,
|
||||
options.ledgerVersion, options.limit);
|
||||
const getOffers = _.partial(getter, orderbook.base, orderbook.counter);
|
||||
const getReverseOffers = _.partial(getter, orderbook.counter, orderbook.base);
|
||||
async.parallel([getOffers, getReverseOffers],
|
||||
composeAsync((data) => formatBidsAndAsks(orderbook, _.flatten(data)),
|
||||
callback));
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getOrderbook);
|
||||
36
src/api/ledger/orders.js
Normal file
36
src/api/ledger/orders.js
Normal file
@@ -0,0 +1,36 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const parseAccountOrder = require('./parse/account-order');
|
||||
|
||||
function requestAccountOffers(remote, address, ledgerVersion, options,
|
||||
marker, limit, callback) {
|
||||
remote.requestAccountOffers({
|
||||
account: address,
|
||||
marker: marker,
|
||||
limit: utils.clamp(limit, 10, 400),
|
||||
ledger: ledgerVersion
|
||||
},
|
||||
composeAsync((data) => ({
|
||||
marker: data.marker,
|
||||
results: data.offers.map(_.partial(parseAccountOrder, address))
|
||||
}), callback));
|
||||
}
|
||||
|
||||
function getOrders(account, options, callback) {
|
||||
validate.address(account);
|
||||
validate.getOrdersOptions(options);
|
||||
|
||||
const ledgerVersion = options.ledgerVersion
|
||||
|| this.remote.getLedgerSequence();
|
||||
const getter = _.partial(requestAccountOffers, this.remote, account,
|
||||
ledgerVersion, options);
|
||||
utils.getRecursive(getter, options.limit,
|
||||
composeAsync((orders) => _.sortBy(orders,
|
||||
(order) => order.properties.sequence), callback));
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getOrders);
|
||||
41
src/api/ledger/parse/account-order.js
Normal file
41
src/api/ledger/parse/account-order.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const flags = utils.core.Remote.flags.offer;
|
||||
const parseAmount = require('./amount');
|
||||
const BigNumber = require('bignumber.js');
|
||||
|
||||
// TODO: remove this function once rippled provides quality directly
|
||||
function computeQuality(takerGets, takerPays) {
|
||||
const quotient = new BigNumber(takerPays.value).dividedBy(takerGets.value);
|
||||
return quotient.toDigits(16, BigNumber.ROUND_HALF_UP).toString();
|
||||
}
|
||||
|
||||
// rippled 'account_offers' returns a different format for orders than 'tx'
|
||||
// the flags are also different
|
||||
function parseAccountOrder(address: string, order: Object): Object {
|
||||
const direction = (order.flags & flags.Sell) === 0 ? 'buy' : 'sell';
|
||||
const takerGetsAmount = parseAmount(order.taker_gets);
|
||||
const takerPaysAmount = parseAmount(order.taker_pays);
|
||||
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
|
||||
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
|
||||
|
||||
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
|
||||
// so we can omit those flags here
|
||||
const specification = utils.removeUndefined({
|
||||
direction: direction,
|
||||
quantity: quantity,
|
||||
totalPrice: totalPrice,
|
||||
passive: ((order.flags & flags.Passive) !== 0) || undefined
|
||||
});
|
||||
|
||||
const properties = {
|
||||
maker: address,
|
||||
sequence: order.seq,
|
||||
makerExchangeRate: computeQuality(takerGetsAmount, takerPaysAmount)
|
||||
};
|
||||
|
||||
return {specification, properties};
|
||||
}
|
||||
|
||||
module.exports = parseAccountOrder;
|
||||
46
src/api/ledger/parse/account-trustline.js
Normal file
46
src/api/ledger/parse/account-trustline.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
|
||||
type Trustline = {
|
||||
account: string, limit: number, currency: string, quality_in: ?number,
|
||||
quality_out: ?number, no_ripple: boolean, freeze: boolean, authorized: boolean,
|
||||
limit_peer: string, no_ripple_peer: boolean, freeze_peer: boolean,
|
||||
peer_authorized: boolean, balance: any
|
||||
}
|
||||
|
||||
type TrustlineSpecification = {}
|
||||
type TrustlineCounterParty = {}
|
||||
type TrustlineState = {balance: number}
|
||||
type AccountTrustline = {
|
||||
specification: TrustlineSpecification, counterparty: TrustlineCounterParty,
|
||||
state: TrustlineState
|
||||
}
|
||||
|
||||
// rippled 'account_lines' returns a different format for
|
||||
// trustlines than 'tx'
|
||||
function parseAccountTrustline(trustline: Trustline): AccountTrustline {
|
||||
const specification = utils.removeUndefined({
|
||||
limit: trustline.limit,
|
||||
currency: trustline.currency,
|
||||
counterparty: trustline.account,
|
||||
qualityIn: trustline.quality_in || undefined,
|
||||
qualityOut: trustline.quality_out || undefined,
|
||||
ripplingDisabled: trustline.no_ripple || undefined,
|
||||
frozen: trustline.freeze || undefined,
|
||||
authorized: trustline.authorized || undefined
|
||||
});
|
||||
// rippled doesn't provide the counterparty's qualities
|
||||
const counterparty = utils.removeUndefined({
|
||||
limit: trustline.limit_peer,
|
||||
ripplingDisabled: trustline.no_ripple_peer || undefined,
|
||||
frozen: trustline.freeze_peer || undefined,
|
||||
authorized: trustline.peer_authorized || undefined
|
||||
});
|
||||
const state = {
|
||||
balance: trustline.balance
|
||||
};
|
||||
return {specification, counterparty, state};
|
||||
}
|
||||
|
||||
module.exports = parseAccountTrustline;
|
||||
24
src/api/ledger/parse/amount.js
Normal file
24
src/api/ledger/parse/amount.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
|
||||
type Amount = string | {currency: string, issuer: string, value: string}
|
||||
type XRPAmount = {currency: string, value: string}
|
||||
type IOUAmount = {currency: string, value: string, counterparty: string}
|
||||
type Output = XRPAmount | IOUAmount
|
||||
|
||||
function parseAmount(amount: Amount): Output {
|
||||
if (typeof amount === 'string') {
|
||||
return {
|
||||
currency: 'XRP',
|
||||
value: utils.dropsToXrp(amount)
|
||||
};
|
||||
}
|
||||
return {
|
||||
currency: amount.currency,
|
||||
value: amount.value,
|
||||
counterparty: amount.issuer
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = parseAmount;
|
||||
12
src/api/ledger/parse/cancellation.js
Normal file
12
src/api/ledger/parse/cancellation.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
|
||||
function parseOrderCancellation(tx: Object): Object {
|
||||
assert(tx.TransactionType === 'OfferCancel');
|
||||
return {
|
||||
orderSequence: tx.OfferSequence
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = parseOrderCancellation;
|
||||
24
src/api/ledger/parse/fields.js
Normal file
24
src/api/ledger/parse/fields.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const AccountFields = require('./utils').constants.AccountFields;
|
||||
|
||||
function parseField(info, value) {
|
||||
if (info.encoding === 'hex' && !info.length) {
|
||||
return new Buffer(value, 'hex').toString('ascii');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function parseFields(data: Object): Object {
|
||||
const settings = {};
|
||||
for (const fieldName in AccountFields) {
|
||||
const fieldValue = data[fieldName];
|
||||
if (fieldValue !== undefined) {
|
||||
const info = AccountFields[fieldName];
|
||||
settings[info.name] = parseField(info, fieldValue);
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
module.exports = parseFields;
|
||||
28
src/api/ledger/parse/order.js
Normal file
28
src/api/ledger/parse/order.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const utils = require('./utils');
|
||||
const parseAmount = require('./amount');
|
||||
const flags = utils.core.Transaction.flags.OfferCreate;
|
||||
|
||||
function parseOrder(tx: Object): Object {
|
||||
assert(tx.TransactionType === 'OfferCreate');
|
||||
|
||||
const direction = (tx.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
|
||||
const takerGetsAmount = parseAmount(tx.TakerGets);
|
||||
const takerPaysAmount = parseAmount(tx.TakerPays);
|
||||
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
|
||||
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
|
||||
|
||||
return utils.removeUndefined({
|
||||
direction: direction,
|
||||
quantity: quantity,
|
||||
totalPrice: totalPrice,
|
||||
passive: ((tx.Flags & flags.Passive) !== 0) || undefined,
|
||||
immediateOrCancel: ((tx.Flags & flags.ImmediateOrCancel) !== 0)
|
||||
|| undefined,
|
||||
fillOrKill: ((tx.Flags & flags.FillOrKill) !== 0) || undefined
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseOrder;
|
||||
43
src/api/ledger/parse/orderbook-order.js
Normal file
43
src/api/ledger/parse/orderbook-order.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const flags = utils.core.Remote.flags.offer;
|
||||
const parseAmount = require('./amount');
|
||||
|
||||
function parseOrderbookOrder(order: Object): Object {
|
||||
const direction = (order.Flags & flags.Sell) === 0 ? 'buy' : 'sell';
|
||||
const takerGetsAmount = parseAmount(order.TakerGets);
|
||||
const takerPaysAmount = parseAmount(order.TakerPays);
|
||||
const quantity = (direction === 'buy') ? takerPaysAmount : takerGetsAmount;
|
||||
const totalPrice = (direction === 'buy') ? takerGetsAmount : takerPaysAmount;
|
||||
|
||||
// note: immediateOrCancel and fillOrKill orders cannot enter the order book
|
||||
// so we can omit those flags here
|
||||
const specification = utils.removeUndefined({
|
||||
direction: direction,
|
||||
quantity: quantity,
|
||||
totalPrice: totalPrice,
|
||||
passive: ((order.Flags & flags.Passive) !== 0) || undefined
|
||||
});
|
||||
|
||||
const properties = {
|
||||
maker: order.Account,
|
||||
sequence: order.Sequence,
|
||||
makerExchangeRate: utils.adjustQualityForXRP(order.quality,
|
||||
takerGetsAmount.currency, takerPaysAmount.currency)
|
||||
};
|
||||
|
||||
const takerGetsFunded = order.taker_gets_funded ?
|
||||
parseAmount(order.taker_gets_funded) : undefined;
|
||||
const takerPaysFunded = order.taker_pays_funded ?
|
||||
parseAmount(order.taker_pays_funded) : undefined;
|
||||
const available = utils.removeUndefined({
|
||||
fundedAmount: takerGetsFunded,
|
||||
priceOfFundedAmount: takerPaysFunded
|
||||
});
|
||||
const state = _.isEmpty(available) ? undefined : available;
|
||||
return utils.removeUndefined({specification, properties, state});
|
||||
}
|
||||
|
||||
module.exports = parseOrderbookOrder;
|
||||
28
src/api/ledger/parse/pathfind.js
Normal file
28
src/api/ledger/parse/pathfind.js
Normal file
@@ -0,0 +1,28 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const parseAmount = require('./amount');
|
||||
|
||||
function parsePaths(paths) {
|
||||
return paths.map(steps => steps.map(step =>
|
||||
_.omit(step, ['type', 'type_hex'])));
|
||||
}
|
||||
|
||||
function parsePathfind(sourceAddress: string,
|
||||
destinationAmount: Object, pathfindResult: Object): Object {
|
||||
return pathfindResult.alternatives.map(function(alternative) {
|
||||
return {
|
||||
source: {
|
||||
address: sourceAddress,
|
||||
amount: parseAmount(alternative.source_amount)
|
||||
},
|
||||
destination: {
|
||||
address: pathfindResult.destination_account,
|
||||
amount: destinationAmount
|
||||
},
|
||||
paths: JSON.stringify(parsePaths(alternative.paths_computed))
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parsePathfind;
|
||||
60
src/api/ledger/parse/payment.js
Normal file
60
src/api/ledger/parse/payment.js
Normal file
@@ -0,0 +1,60 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const utils = require('./utils');
|
||||
const parseAmount = require('./amount');
|
||||
const Transaction = utils.core.Transaction;
|
||||
|
||||
function isPartialPayment(tx) {
|
||||
return (tx.Flags & Transaction.flags.Payment.PartialPayment) !== 0;
|
||||
}
|
||||
|
||||
function isNoDirectRipple(tx) {
|
||||
return (tx.Flags & Transaction.flags.Payment.NoRippleDirect) !== 0;
|
||||
}
|
||||
|
||||
function isQualityLimited(tx) {
|
||||
return (tx.Flags & Transaction.flags.Payment.LimitQuality) !== 0;
|
||||
}
|
||||
|
||||
function parsePaymentMemos(tx) {
|
||||
if (!Array.isArray(tx.Memos) || tx.Memos.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return tx.Memos.map((m) => {
|
||||
return utils.removeUndefined({
|
||||
type: m.Memo.parsed_memo_type,
|
||||
format: m.Memo.parsed_memo_format,
|
||||
data: m.Memo.parsed_memo_data
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parsePayment(tx: Object): Object {
|
||||
assert(tx.TransactionType === 'Payment');
|
||||
|
||||
const source = {
|
||||
address: tx.Account,
|
||||
amount: parseAmount(tx.SendMax || tx.Amount),
|
||||
tag: tx.SourceTag
|
||||
};
|
||||
|
||||
const destination = {
|
||||
address: tx.Destination,
|
||||
amount: parseAmount(tx.Amount),
|
||||
tag: tx.DestinationTag
|
||||
};
|
||||
|
||||
return utils.removeUndefined({
|
||||
source: utils.removeUndefined(source),
|
||||
destination: utils.removeUndefined(destination),
|
||||
memos: parsePaymentMemos(tx),
|
||||
invoiceID: tx.InvoiceID,
|
||||
paths: tx.Paths ? JSON.stringify(tx.Paths) : undefined,
|
||||
allowPartialPayment: isPartialPayment(tx) || undefined,
|
||||
noDirectRipple: isNoDirectRipple(tx) || undefined,
|
||||
limitQuality: isQualityLimited(tx) || undefined
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parsePayment;
|
||||
61
src/api/ledger/parse/settings.js
Normal file
61
src/api/ledger/parse/settings.js
Normal file
@@ -0,0 +1,61 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const AccountFlags = require('./utils').constants.AccountFlags;
|
||||
const parseFields = require('./fields');
|
||||
|
||||
function getAccountRootModifiedNode(tx: Object) {
|
||||
const modifiedNodes = tx.meta.AffectedNodes.filter(node =>
|
||||
node.ModifiedNode.LedgerEntryType === 'AccountRoot');
|
||||
assert(modifiedNodes.length === 1);
|
||||
return modifiedNodes[0].ModifiedNode;
|
||||
}
|
||||
|
||||
function parseFlags(tx: Object) {
|
||||
const settings = {};
|
||||
if (tx.TransactionType !== 'AccountSet') {
|
||||
return settings;
|
||||
}
|
||||
|
||||
const node = getAccountRootModifiedNode(tx);
|
||||
const oldFlags = _.get(node.PreviousFields, 'Flags');
|
||||
const newFlags = _.get(node.FinalFields, 'Flags');
|
||||
|
||||
if (oldFlags !== undefined && newFlags !== undefined) {
|
||||
const changedFlags = oldFlags ^ newFlags;
|
||||
const setFlags = newFlags & changedFlags;
|
||||
const clearedFlags = oldFlags & changedFlags;
|
||||
_.forEach(AccountFlags, (flagValue, flagName) => {
|
||||
if (setFlags & flagValue) {
|
||||
settings[flagName] = true;
|
||||
} else if (clearedFlags & flagValue) {
|
||||
settings[flagName] = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// enableTransactionIDTracking requires a special case because it
|
||||
// does not affect the Flags field; instead it adds/removes a field called
|
||||
// "AccountTxnID" to/from the account root.
|
||||
|
||||
const oldField = _.get(node.PreviousFields, 'AccountTxnID');
|
||||
const newField = _.get(node.FinalFields, 'AccountTxnID');
|
||||
if (newField && !oldField) {
|
||||
settings.enableTransactionIDTracking = true;
|
||||
} else if (oldField && !newField) {
|
||||
settings.enableTransactionIDTracking = false;
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
function parseSettings(tx: Object) {
|
||||
const txType = tx.TransactionType;
|
||||
assert(txType === 'AccountSet' || txType === 'SetRegularKey');
|
||||
|
||||
const regularKey = tx.RegularKey ? {regularKey: tx.RegularKey} : {};
|
||||
return _.assign(regularKey, parseFlags(tx), parseFields(tx));
|
||||
}
|
||||
|
||||
module.exports = parseSettings;
|
||||
46
src/api/ledger/parse/transaction.js
Normal file
46
src/api/ledger/parse/transaction.js
Normal file
@@ -0,0 +1,46 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const utils = require('./utils');
|
||||
const parsePayment = require('./payment');
|
||||
const parseTrustline = require('./trustline');
|
||||
const parseOrder = require('./order');
|
||||
const parseOrderCancellation = require('./cancellation');
|
||||
const parseSettings = require('./settings');
|
||||
|
||||
function parseTransactionType(type) {
|
||||
const mapping = {
|
||||
Payment: 'payment',
|
||||
TrustSet: 'trustline',
|
||||
OfferCreate: 'order',
|
||||
OfferCancel: 'orderCancellation',
|
||||
AccountSet: 'settings',
|
||||
SetRegularKey: 'settings'
|
||||
};
|
||||
return mapping[type] || null;
|
||||
}
|
||||
|
||||
function parseTransaction(tx: Object): Object {
|
||||
const type = parseTransactionType(tx.TransactionType);
|
||||
const mapping = {
|
||||
'payment': parsePayment,
|
||||
'trustline': parseTrustline,
|
||||
'order': parseOrder,
|
||||
'orderCancellation': parseOrderCancellation,
|
||||
'settings': parseSettings
|
||||
};
|
||||
const parser = mapping[type];
|
||||
assert(parser !== undefined, 'Unrecognized transaction type');
|
||||
const specification = parser(tx);
|
||||
const outcome = utils.parseOutcome(tx);
|
||||
return utils.removeUndefined({
|
||||
type: type,
|
||||
address: tx.Account,
|
||||
sequence: tx.Sequence,
|
||||
id: tx.hash,
|
||||
specification: utils.removeUndefined(specification),
|
||||
outcome: outcome ? utils.removeUndefined(outcome) : undefined
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseTransaction;
|
||||
33
src/api/ledger/parse/trustline.js
Normal file
33
src/api/ledger/parse/trustline.js
Normal file
@@ -0,0 +1,33 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const assert = require('assert');
|
||||
const utils = require('./utils');
|
||||
const flags = utils.core.Transaction.flags.TrustSet;
|
||||
|
||||
function parseFlag(flagsValue, trueValue, falseValue) {
|
||||
if (flagsValue & trueValue) {
|
||||
return true;
|
||||
}
|
||||
if (flagsValue & falseValue) {
|
||||
return false;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseTrustline(tx: Object): Object {
|
||||
assert(tx.TransactionType === 'TrustSet');
|
||||
|
||||
return utils.removeUndefined({
|
||||
limit: tx.LimitAmount.value,
|
||||
currency: tx.LimitAmount.currency,
|
||||
counterparty: tx.LimitAmount.issuer,
|
||||
qualityIn: tx.QualityIn,
|
||||
qualityOut: tx.QualityOut,
|
||||
ripplingDisabled: parseFlag(
|
||||
tx.Flags, flags.SetNoRipple, flags.ClearNoRipple),
|
||||
frozen: parseFlag(tx.Flags, flags.SetFreeze, flags.ClearFreeze),
|
||||
authorized: parseFlag(tx.Flags, flags.SetAuth, 0)
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = parseTrustline;
|
||||
76
src/api/ledger/parse/utils.js
Normal file
76
src/api/ledger/parse/utils.js
Normal file
@@ -0,0 +1,76 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const transactionParser = require('ripple-lib-transactionparser');
|
||||
const toTimestamp = require('../../../core/utils').toTimestamp;
|
||||
const utils = require('../utils');
|
||||
const BigNumber = require('bignumber.js');
|
||||
|
||||
function adjustQualityForXRP(quality: string, takerGetsCurrency: string,
|
||||
takerPaysCurrency: string) {
|
||||
// quality = takerPays.value/takerGets.value
|
||||
// using drops (1e-6 XRP) for XRP values
|
||||
const numeratorShift = (takerPaysCurrency === 'XRP' ? -6 : 0);
|
||||
const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0);
|
||||
const shift = numeratorShift - denominatorShift;
|
||||
return shift === 0 ? quality :
|
||||
(new BigNumber(quality)).shift(shift).toString();
|
||||
}
|
||||
|
||||
function parseTimestamp(tx: {date: string}): string | void {
|
||||
return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined;
|
||||
}
|
||||
|
||||
function removeUndefined(obj: Object): Object {
|
||||
return _.omit(obj, _.isUndefined);
|
||||
}
|
||||
|
||||
function removeEmptyCounterparty(amount) {
|
||||
if (amount.counterparty === '') {
|
||||
delete amount.counterparty;
|
||||
}
|
||||
}
|
||||
|
||||
function removeEmptyCounterpartyInBalanceChanges(balanceChanges) {
|
||||
_.forEach(balanceChanges, (changes) => {
|
||||
_.forEach(changes, removeEmptyCounterparty);
|
||||
});
|
||||
}
|
||||
|
||||
function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
|
||||
_.forEach(orderbookChanges, (changes) => {
|
||||
_.forEach(changes, (change) => {
|
||||
_.forEach(change, removeEmptyCounterparty);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function parseOutcome(tx: Object): ?Object {
|
||||
if (!tx.validated) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const balanceChanges = transactionParser.parseBalanceChanges(tx.meta);
|
||||
const orderbookChanges = transactionParser.parseOrderBookChanges(tx.meta);
|
||||
removeEmptyCounterpartyInBalanceChanges(balanceChanges);
|
||||
removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);
|
||||
|
||||
return {
|
||||
result: tx.meta.TransactionResult,
|
||||
timestamp: parseTimestamp(tx),
|
||||
fee: utils.common.dropsToXrp(tx.Fee),
|
||||
balanceChanges: balanceChanges,
|
||||
orderbookChanges: orderbookChanges,
|
||||
ledgerVersion: tx.ledger_index,
|
||||
indexInLedger: tx.meta.TransactionIndex
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
parseOutcome,
|
||||
removeUndefined,
|
||||
adjustQualityForXRP,
|
||||
dropsToXrp: utils.common.dropsToXrp,
|
||||
constants: utils.common.constants,
|
||||
core: utils.common.core
|
||||
};
|
||||
116
src/api/ledger/pathfind.js
Normal file
116
src/api/ledger/pathfind.js
Normal file
@@ -0,0 +1,116 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const BigNumber = require('bignumber.js');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const parsePathfind = require('./parse/pathfind');
|
||||
const NotFoundError = utils.common.errors.NotFoundError;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
|
||||
type PathFindParams = {
|
||||
src_currencies?: Array<string>, src_account: string, dst_amount: string,
|
||||
dst_account?: string
|
||||
}
|
||||
|
||||
function addParams(params: PathFindParams, result: {}) {
|
||||
return _.assign({}, result, {
|
||||
source_account: params.src_account,
|
||||
source_currencies: params.src_currencies,
|
||||
destination_amount: params.dst_amount
|
||||
});
|
||||
}
|
||||
|
||||
type PathFind = {
|
||||
source: {address: string, currencies: Array<string>},
|
||||
destination: {address: string, amount: string}
|
||||
}
|
||||
|
||||
function requestPathFind(remote, pathfind: PathFind, callback) {
|
||||
const params: PathFindParams = {
|
||||
src_account: pathfind.source.address,
|
||||
dst_account: pathfind.destination.address,
|
||||
dst_amount: utils.common.toRippledAmount(pathfind.destination.amount)
|
||||
};
|
||||
if (typeof params.dst_amount === 'object' && !params.dst_amount.issuer) {
|
||||
// Convert blank issuer to sender's address
|
||||
// (Ripple convention for 'any issuer')
|
||||
// https://ripple.com/build/transactions/
|
||||
// #special-issuer-values-for-sendmax-and-amount
|
||||
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
|
||||
params.dst_amount.issuer = params.dst_account;
|
||||
}
|
||||
if (pathfind.source.currencies && pathfind.source.currencies.length > 0) {
|
||||
params.src_currencies = pathfind.source.currencies.map(amount =>
|
||||
_.omit(utils.common.toRippledAmount(amount), 'value'));
|
||||
}
|
||||
|
||||
remote.requestRipplePathFind(params,
|
||||
composeAsync(_.partial(addParams, params), callback));
|
||||
}
|
||||
|
||||
function addDirectXrpPath(paths, xrpBalance) {
|
||||
// Add XRP "path" only if the source acct has enough XRP to make the payment
|
||||
const destinationAmount = paths.destination_amount;
|
||||
if ((new BigNumber(xrpBalance)).greaterThanOrEqualTo(destinationAmount)) {
|
||||
paths.alternatives.unshift({
|
||||
paths_computed: [],
|
||||
source_amount: paths.destination_amount
|
||||
});
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
function isRippledIOUAmount(amount) {
|
||||
// rippled XRP amounts are specified as decimal strings
|
||||
return (typeof amount === 'object') &&
|
||||
amount.currency && (amount.currency !== 'XRP');
|
||||
}
|
||||
|
||||
function conditionallyAddDirectXRPPath(remote, address, paths, callback) {
|
||||
if (isRippledIOUAmount(paths.destination_amount)
|
||||
|| !_.includes(paths.destination_currencies, 'XRP')) {
|
||||
callback(null, paths);
|
||||
} else {
|
||||
utils.getXRPBalance(remote, address, undefined,
|
||||
composeAsync(_.partial(addDirectXrpPath, paths), callback));
|
||||
}
|
||||
}
|
||||
|
||||
function formatResponse(pathfind, paths) {
|
||||
if (paths.alternatives && paths.alternatives.length > 0) {
|
||||
const address = pathfind.source.address;
|
||||
return parsePathfind(address, pathfind.destination.amount, paths);
|
||||
}
|
||||
if (!_.includes(paths.destination_currencies,
|
||||
pathfind.destination.amount.currency)) {
|
||||
throw new NotFoundError('No paths found. ' +
|
||||
'The destination_account does not accept ' +
|
||||
pathfind.destination.amount.currency + ', they only accept: ' +
|
||||
paths.destination_currencies.join(', '));
|
||||
} else if (paths.source_currencies && paths.source_currencies.length > 0) {
|
||||
throw new NotFoundError('No paths found. Please ensure' +
|
||||
' that the source_account has sufficient funds to execute' +
|
||||
' the payment in one of the specified source_currencies. If it does' +
|
||||
' there may be insufficient liquidity in the network to execute' +
|
||||
' this payment right now');
|
||||
} else {
|
||||
throw new NotFoundError('No paths found.' +
|
||||
' Please ensure that the source_account has sufficient funds to' +
|
||||
' execute the payment. If it does there may be insufficient liquidity' +
|
||||
' in the network to execute this payment right now');
|
||||
}
|
||||
}
|
||||
|
||||
function getPaths(pathfind, callback) {
|
||||
validate.pathfind(pathfind);
|
||||
|
||||
const address = pathfind.source.address;
|
||||
async.waterfall([
|
||||
_.partial(requestPathFind, this.remote, pathfind),
|
||||
_.partial(conditionallyAddDirectXRPPath, this.remote, address)
|
||||
], composeAsync(_.partial(formatResponse, pathfind), callback));
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getPaths);
|
||||
40
src/api/ledger/settings.js
Normal file
40
src/api/ledger/settings.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const parseFields = require('./parse/fields');
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
const AccountFlags = utils.common.constants.AccountFlags;
|
||||
|
||||
function parseFlags(value) {
|
||||
const settings = {};
|
||||
for (const flagName in AccountFlags) {
|
||||
if (value & AccountFlags[flagName]) {
|
||||
settings[flagName] = true;
|
||||
}
|
||||
}
|
||||
return settings;
|
||||
}
|
||||
|
||||
function formatSettings(response) {
|
||||
const data = response.account_data;
|
||||
const parsedFlags = parseFlags(data.Flags);
|
||||
const parsedFields = parseFields(data);
|
||||
return _.assign({}, parsedFlags, parsedFields);
|
||||
}
|
||||
|
||||
function getSettings(account, options, callback) {
|
||||
validate.address(account);
|
||||
validate.getSettingsOptions(options);
|
||||
|
||||
const request = {
|
||||
account: account,
|
||||
ledger: options.ledgerVersion
|
||||
};
|
||||
|
||||
this.remote.requestAccountInfo(request,
|
||||
composeAsync(formatSettings, callback));
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getSettings);
|
||||
67
src/api/ledger/transaction.js
Normal file
67
src/api/ledger/transaction.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const utils = require('./utils');
|
||||
const parseTransaction = require('./parse/transaction');
|
||||
const validate = utils.common.validate;
|
||||
const errors = utils.common.errors;
|
||||
|
||||
function attachTransactionDate(remote, tx, callback) {
|
||||
if (tx.date) {
|
||||
callback(null, tx);
|
||||
return;
|
||||
}
|
||||
if (!tx.ledger_index) {
|
||||
callback(new errors.NotFoundError('ledger_index not found in tx'));
|
||||
return;
|
||||
}
|
||||
|
||||
remote.requestLedger(tx.ledger_index, (error, data) => {
|
||||
if (error) {
|
||||
callback(new errors.NotFoundError('Transaction ledger not found'));
|
||||
} else if (typeof data.ledger.close_time === 'number') {
|
||||
callback(null, _.assign({date: data.ledger.close_time}, tx));
|
||||
} else {
|
||||
callback(new errors.ApiError('Ledger missing close_time'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function isTransactionInRange(tx, options) {
|
||||
return (!options.minLedgerVersion
|
||||
|| tx.ledger_index >= options.minLedgerVersion)
|
||||
&& (!options.maxLedgerVersion
|
||||
|| tx.ledger_index <= options.maxLedgerVersion);
|
||||
}
|
||||
|
||||
function getTransaction(identifier, options, callback) {
|
||||
validate.identifier(identifier);
|
||||
validate.getTransactionOptions(options);
|
||||
|
||||
const remote = this.remote;
|
||||
const maxLedgerVersion = Math.min(options.maxLedgerVersion,
|
||||
remote.getLedgerSequence());
|
||||
|
||||
function callbackWrapper(error, tx) {
|
||||
if (error instanceof errors.NotFoundError
|
||||
&& !utils.hasCompleteLedgerRange(remote,
|
||||
options.minLedgerVersion, maxLedgerVersion)) {
|
||||
callback(new errors.MissingLedgerHistoryError('Transaction not found,'
|
||||
+ ' but the server\'s ledger history is incomplete'));
|
||||
} else if (!error && !isTransactionInRange(tx, options)) {
|
||||
callback(new errors.NotFoundError('Transaction not found'));
|
||||
} else if (error) {
|
||||
callback(error);
|
||||
} else {
|
||||
callback(error, parseTransaction(tx));
|
||||
}
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
_.partial(remote.requestTx.bind(remote), {hash: identifier, binary: false}),
|
||||
_.partial(attachTransactionDate, remote)
|
||||
], callbackWrapper);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getTransaction);
|
||||
123
src/api/ledger/transactions.js
Normal file
123
src/api/ledger/transactions.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const parseTransaction = require('./parse/transaction');
|
||||
const getTransaction = require('./transaction');
|
||||
const validate = utils.common.validate;
|
||||
const composeAsync = utils.common.composeAsync;
|
||||
|
||||
function parseAccountTxTransaction(tx) {
|
||||
// rippled uses a different response format for 'account_tx' than 'tx'
|
||||
tx.tx.meta = tx.meta;
|
||||
tx.tx.validated = tx.validated;
|
||||
return parseTransaction(tx.tx);
|
||||
}
|
||||
|
||||
function transactionFilter(address, filters, tx) {
|
||||
if (filters.excludeFailures && tx.outcome.result !== 'tesSUCCESS') {
|
||||
return false;
|
||||
}
|
||||
if (filters.types && !_.includes(filters.types, tx.type)) {
|
||||
return false;
|
||||
}
|
||||
if (filters.initiated === true && tx.address !== address) {
|
||||
return false;
|
||||
}
|
||||
if (filters.initiated === false && tx.address === address) {
|
||||
return false;
|
||||
}
|
||||
if (filters.counterparty && tx.address !== filters.counterparty
|
||||
&& tx.Destination !== filters.counterparty) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function orderFilter(options, tx) {
|
||||
return !options.startTx || (options.earliestFirst ?
|
||||
utils.compareTransactions(tx, options.startTx) > 0 :
|
||||
utils.compareTransactions(tx, options.startTx) < 0);
|
||||
}
|
||||
|
||||
function getAccountTx(remote, address, options, marker, limit, callback) {
|
||||
const params = {
|
||||
account: address,
|
||||
ledger_index_min: options.minLedgerVersion || -1,
|
||||
ledger_index_max: options.maxLedgerVersion || -1,
|
||||
forward: options.earliestFirst,
|
||||
binary: options.binary,
|
||||
limit: utils.clamp(limit, 10, 400),
|
||||
marker: marker
|
||||
};
|
||||
|
||||
remote.requestAccountTx(params, (error, data) => {
|
||||
return error ? callback(error) : callback(null, {
|
||||
marker: data.marker,
|
||||
results: data.transactions
|
||||
.filter((tx) => tx.validated)
|
||||
.map(parseAccountTxTransaction)
|
||||
.filter(_.partial(transactionFilter, address, options))
|
||||
.filter(_.partial(orderFilter, options))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkForLedgerGaps(remote, options, transactions) {
|
||||
let {minLedgerVersion, maxLedgerVersion} = options;
|
||||
|
||||
// if we reached the limit on number of transactions, then we can shrink
|
||||
// the required ledger range to only guarantee that there are no gaps in
|
||||
// the range of ledgers spanned by those transactions
|
||||
if (options.limit && transactions.length === options.limit) {
|
||||
if (options.earliestFirst) {
|
||||
maxLedgerVersion = _.last(transactions).outcome.ledgerVersion;
|
||||
} else {
|
||||
minLedgerVersion = _.last(transactions).outcome.ledgerVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (!utils.hasCompleteLedgerRange(remote, minLedgerVersion,
|
||||
maxLedgerVersion)) {
|
||||
throw new utils.common.errors.MissingLedgerHistoryError();
|
||||
}
|
||||
}
|
||||
|
||||
function formatResponse(remote, options, transactions) {
|
||||
const compare = options.earliestFirst ? utils.compareTransactions :
|
||||
_.rearg(utils.compareTransactions, 1, 0);
|
||||
const sortedTransactions = transactions.sort(compare);
|
||||
checkForLedgerGaps(remote, options, sortedTransactions);
|
||||
return sortedTransactions;
|
||||
}
|
||||
|
||||
function getTransactionsInternal(remote, address, options, callback) {
|
||||
const getter = _.partial(getAccountTx, remote, address, options);
|
||||
const format = _.partial(formatResponse, remote, options);
|
||||
utils.getRecursive(getter, options.limit, composeAsync(format, callback));
|
||||
}
|
||||
|
||||
function getTransactions(address, options, callback) {
|
||||
validate.address(address);
|
||||
validate.getTransactionsOptions(options);
|
||||
|
||||
const defaults = {maxLedgerVersion: this.remote.getLedgerSequence()};
|
||||
if (options.start) {
|
||||
getTransaction.bind(this)(options.start, {}, (error, tx) => {
|
||||
if (error) {
|
||||
callback(error);
|
||||
return;
|
||||
}
|
||||
const ledgerVersion = tx.outcome.ledgerVersion;
|
||||
const bound = options.earliestFirst ?
|
||||
{minLedgerVersion: ledgerVersion} : {maxLedgerVersion: ledgerVersion};
|
||||
const newOptions = _.assign(defaults, options, {startTx: tx}, bound);
|
||||
getTransactionsInternal(this.remote, address, newOptions, callback);
|
||||
});
|
||||
} else {
|
||||
const newOptions = _.assign(defaults, options);
|
||||
getTransactionsInternal(this.remote, address, newOptions, callback);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getTransactions);
|
||||
45
src/api/ledger/trustlines.js
Normal file
45
src/api/ledger/trustlines.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const parseAccountTrustline = require('./parse/account-trustline');
|
||||
|
||||
function currencyFilter(currency, trustline) {
|
||||
return currency === null || trustline.specification.currency === currency;
|
||||
}
|
||||
|
||||
function getAccountLines(remote, address, ledgerVersion, options, marker, limit,
|
||||
callback) {
|
||||
const requestOptions = {
|
||||
account: address,
|
||||
ledger: ledgerVersion,
|
||||
marker: marker,
|
||||
limit: utils.clamp(limit, 10, 400),
|
||||
peer: options.counterparty
|
||||
};
|
||||
|
||||
remote.requestAccountLines(requestOptions, (error, data) => {
|
||||
return error ? callback(error) :
|
||||
callback(null, {
|
||||
marker: data.marker,
|
||||
results: data.lines.map(parseAccountTrustline)
|
||||
.filter(_.partial(currencyFilter, options.currency || null))
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getTrustlines(account: string, options: {currency: string,
|
||||
counterparty: string, limit: number, ledgerVersion: number},
|
||||
callback: () => void): void {
|
||||
validate.address(account);
|
||||
validate.getTrustlinesOptions(options);
|
||||
|
||||
const ledgerVersion = options.ledgerVersion
|
||||
|| this.remote.getLedgerSequence();
|
||||
const getter = _.partial(getAccountLines, this.remote, account,
|
||||
ledgerVersion, options);
|
||||
utils.getRecursive(getter, options.limit, callback);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(getTrustlines);
|
||||
98
src/api/ledger/utils.js
Normal file
98
src/api/ledger/utils.js
Normal file
@@ -0,0 +1,98 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const common = require('../common');
|
||||
const dropsToXrp = common.dropsToXrp;
|
||||
const composeAsync = common.composeAsync;
|
||||
|
||||
function clamp(value, min, max) {
|
||||
assert(min <= max, 'Illegal clamp bounds');
|
||||
return Math.min(Math.max(value, min), max);
|
||||
}
|
||||
|
||||
function getXRPBalance(remote, address, ledgerVersion, callback) {
|
||||
remote.requestAccountInfo({account: address, ledger: ledgerVersion},
|
||||
composeAsync((data) => dropsToXrp(data.account_data.Balance), callback));
|
||||
}
|
||||
|
||||
// If the marker is omitted from a response, you have reached the end
|
||||
// getter(marker, limit, callback), callback(error, {marker, results})
|
||||
function getRecursiveRecur(getter, marker, limit, callback) {
|
||||
getter(marker, limit, (error, data) => {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
const remaining = limit - data.results.length;
|
||||
if (remaining > 0 && data.marker !== undefined) {
|
||||
getRecursiveRecur(getter, data.marker, remaining, (_error, results) => {
|
||||
return _error ? callback(_error) :
|
||||
callback(null, data.results.concat(results));
|
||||
});
|
||||
} else {
|
||||
return callback(null, data.results.slice(0, limit));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getRecursive(getter, limit, callback) {
|
||||
getRecursiveRecur(getter, undefined, limit || Infinity, callback);
|
||||
}
|
||||
|
||||
function renameCounterpartyToIssuer(amount) {
|
||||
if (amount === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const issuer = amount.counterparty === undefined ?
|
||||
amount.issuer : amount.counterparty;
|
||||
const withIssuer = _.assign({}, amount, {issuer: issuer});
|
||||
return _.omit(withIssuer, 'counterparty');
|
||||
}
|
||||
|
||||
function renameCounterpartyToIssuerInOrder(order) {
|
||||
const taker_gets = renameCounterpartyToIssuer(order.taker_gets);
|
||||
const taker_pays = renameCounterpartyToIssuer(order.taker_pays);
|
||||
const changes = {taker_gets: taker_gets, taker_pays: taker_pays};
|
||||
return _.assign({}, order, _.omit(changes, _.isUndefined));
|
||||
}
|
||||
|
||||
function signum(num) {
|
||||
return (num === 0) ? 0 : (num > 0 ? 1 : -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Order two rippled transactions based on their ledger_index.
|
||||
* If two transactions took place in the same ledger, sort
|
||||
* them based on TransactionIndex
|
||||
* See: https://ripple.com/build/transactions/
|
||||
*
|
||||
* @param {Object} first
|
||||
* @param {Object} second
|
||||
* @returns {Number} [-1, 0, 1]
|
||||
*/
|
||||
|
||||
function compareTransactions(first, second) {
|
||||
if (first.ledgerVersion === second.ledgerVersion) {
|
||||
return signum(Number(first.indexInLedger) - Number(second.indexInLedger));
|
||||
}
|
||||
return Number(first.ledgerVersion) < Number(second.ledgerVersion) ? -1 : 1;
|
||||
}
|
||||
|
||||
function hasCompleteLedgerRange(remote, minLedgerVersion, maxLedgerVersion) {
|
||||
const firstLedgerVersion = 32570; // earlier versions have been lost
|
||||
return remote.getServer().hasLedgerRange(
|
||||
minLedgerVersion || firstLedgerVersion,
|
||||
maxLedgerVersion || remote.getLedgerSequence());
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getXRPBalance,
|
||||
compareTransactions,
|
||||
renameCounterpartyToIssuer,
|
||||
renameCounterpartyToIssuerInOrder,
|
||||
getRecursive,
|
||||
hasCompleteLedgerRange,
|
||||
wrapCatch: common.wrapCatch,
|
||||
clamp: clamp,
|
||||
common: common
|
||||
};
|
||||
|
||||
53
src/api/server/server.js
Normal file
53
src/api/server/server.js
Normal file
@@ -0,0 +1,53 @@
|
||||
/* @flow */
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
// If a ledger is not received in this time, consider the connection offline
|
||||
const CONNECTION_TIMEOUT = 1000 * 30;
|
||||
|
||||
function connect(callback: (err: any, data: any) => void): void {
|
||||
this.remote.connect(callback);
|
||||
}
|
||||
|
||||
function disconnect(callback: (err: any, data: any) => void): void {
|
||||
this.remote.disconnect(callback);
|
||||
}
|
||||
|
||||
function isUpToDate(remote): boolean {
|
||||
const server = remote.getServer();
|
||||
return Boolean(server) && (remote._stand_alone
|
||||
|| (Date.now() - server._lastLedgerClose) <= CONNECTION_TIMEOUT);
|
||||
}
|
||||
|
||||
function isConnected(): boolean {
|
||||
return Boolean(this.remote._ledger_current_index) && isUpToDate(this.remote);
|
||||
}
|
||||
|
||||
function getServerInfo(callback: (err: any, data: any) => void): void {
|
||||
this.remote.requestServerInfo((error, response) => {
|
||||
if (error) {
|
||||
callback(new common.errors.RippledNetworkError(error.message));
|
||||
} else {
|
||||
callback(null, response.info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getFee(): number {
|
||||
return common.dropsToXrp(this.remote.createTransaction()._computeFee());
|
||||
}
|
||||
|
||||
function getLedgerVersion(): number {
|
||||
return this.remote.getLedgerSequence();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
connect,
|
||||
disconnect,
|
||||
isConnected,
|
||||
getServerInfo,
|
||||
getFee,
|
||||
getLedgerVersion
|
||||
};
|
||||
38
src/api/transaction/order.js
Normal file
38
src/api/transaction/order.js
Normal file
@@ -0,0 +1,38 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
|
||||
const OfferCreateFlags = {
|
||||
passive: {set: 'Passive'},
|
||||
immediateOrCancel: {set: 'ImmediateOrCancel'},
|
||||
fillOrKill: {set: 'FillOrKill'}
|
||||
};
|
||||
|
||||
function createOrderTransaction(account, order) {
|
||||
validate.address(account);
|
||||
validate.order(order);
|
||||
|
||||
const transaction = new Transaction();
|
||||
const takerPays = utils.common.toRippledAmount(order.direction === 'buy' ?
|
||||
order.quantity : order.totalPrice);
|
||||
const takerGets = utils.common.toRippledAmount(order.direction === 'buy' ?
|
||||
order.totalPrice : order.quantity);
|
||||
|
||||
transaction.offerCreate(account, takerPays, takerGets);
|
||||
|
||||
utils.setTransactionBitFlags(transaction, order, OfferCreateFlags);
|
||||
if (order.direction === 'sell') {
|
||||
transaction.setFlags('Sell');
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareOrder(account, order, instructions, callback) {
|
||||
const transaction = createOrderTransaction(account, order);
|
||||
utils.createTxJSON(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(prepareOrder);
|
||||
21
src/api/transaction/ordercancellation.js
Normal file
21
src/api/transaction/ordercancellation.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
|
||||
function createOrderCancellationTransaction(account, sequence) {
|
||||
validate.address(account);
|
||||
validate.sequence(sequence);
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.offerCancel(account, sequence);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareOrderCancellation(account, sequence, instructions, callback) {
|
||||
const transaction = createOrderCancellationTransaction(account, sequence);
|
||||
utils.createTxJSON(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(prepareOrderCancellation);
|
||||
93
src/api/transaction/payment.js
Normal file
93
src/api/transaction/payment.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const BigNumber = require('bignumber.js');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const toRippledAmount = utils.common.toRippledAmount;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
|
||||
function isSendMaxAllowed(payment) {
|
||||
const srcAmt = payment.source.amount;
|
||||
const dstAmt = payment.destination.amount;
|
||||
|
||||
// Don't set SendMax for XRP->XRP payment
|
||||
// temREDUNDANT_SEND_MAX removed in:
|
||||
// https://github.com/ripple/rippled/commit/
|
||||
// c522ffa6db2648f1d8a987843e7feabf1a0b7de8/
|
||||
return srcAmt && !(srcAmt.currency === 'XRP' && dstAmt.currency === 'XRP');
|
||||
}
|
||||
|
||||
function isIOUWithoutCounterparty(amount) {
|
||||
return amount && amount.currency !== 'XRP'
|
||||
&& amount.counterparty === undefined;
|
||||
}
|
||||
|
||||
function applyAnyCounterpartyEncoding(payment) {
|
||||
// Convert blank counterparty to sender or receiver's address
|
||||
// (Ripple convention for 'any counterparty')
|
||||
// https://ripple.com/build/transactions/
|
||||
// #special-issuer-values-for-sendmax-and-amount
|
||||
// https://ripple.com/build/ripple-rest/#counterparties-in-payments
|
||||
if (isIOUWithoutCounterparty(payment.source.amount)) {
|
||||
payment.source.amount.counterparty = payment.source.address;
|
||||
}
|
||||
if (isIOUWithoutCounterparty(payment.destination.amount)) {
|
||||
payment.destination.amount.counterparty = payment.destination.address;
|
||||
}
|
||||
}
|
||||
|
||||
function createPaymentTransaction(account, payment) {
|
||||
applyAnyCounterpartyEncoding(payment);
|
||||
validate.address(account);
|
||||
validate.payment(payment);
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.payment({
|
||||
from: payment.source.address,
|
||||
to: payment.destination.address,
|
||||
amount: toRippledAmount(payment.destination.amount)
|
||||
});
|
||||
|
||||
if (payment.invoiceID) {
|
||||
transaction.invoiceID(payment.invoiceID);
|
||||
}
|
||||
if (payment.source.tag) {
|
||||
transaction.sourceTag(payment.source.tag);
|
||||
}
|
||||
if (payment.destination.tag) {
|
||||
transaction.destinationTag(payment.destination.tag);
|
||||
}
|
||||
if (payment.paths) {
|
||||
transaction.paths(JSON.parse(payment.paths));
|
||||
}
|
||||
if (payment.memos) {
|
||||
_.forEach(payment.memos, memo =>
|
||||
transaction.addMemo(memo.type, memo.format, memo.data)
|
||||
);
|
||||
}
|
||||
if (payment.allowPartialPayment) {
|
||||
transaction.setFlags(['PartialPayment']);
|
||||
}
|
||||
if (payment.noDirectRipple) {
|
||||
transaction.setFlags(['NoRippleDirect']);
|
||||
}
|
||||
if (payment.limitQuality) {
|
||||
transaction.setFlags(['LimitQuality']);
|
||||
}
|
||||
if (isSendMaxAllowed(payment)) {
|
||||
const maxValue = new BigNumber(payment.source.amount.value)
|
||||
.plus(payment.source.slippage || 0).toString();
|
||||
const maxAmount = _.assign({}, payment.source.amount, {value: maxValue});
|
||||
transaction.sendMax(toRippledAmount(maxAmount));
|
||||
}
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function preparePayment(account, payment, instructions, callback) {
|
||||
const transaction = createPaymentTransaction(account, payment);
|
||||
utils.createTxJSON(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(preparePayment);
|
||||
98
src/api/transaction/settings.js
Normal file
98
src/api/transaction/settings.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const AccountFlagIndices = utils.common.constants.AccountFlagIndices;
|
||||
const AccountFields = utils.common.constants.AccountFields;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
|
||||
// Emptry string passed to setting will clear it
|
||||
const CLEAR_SETTING = '';
|
||||
|
||||
function setTransactionFlags(transaction, values) {
|
||||
const keys = Object.keys(values);
|
||||
assert(keys.length === 1, 'ERROR: can only set one setting per transaction');
|
||||
const flagName = keys[0];
|
||||
const value = values[flagName];
|
||||
const index = AccountFlagIndices[flagName];
|
||||
if (index !== undefined) {
|
||||
if (value) {
|
||||
transaction.tx_json.SetFlag = index;
|
||||
} else {
|
||||
transaction.tx_json.ClearFlag = index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTransactionFields(transaction, input) {
|
||||
const fieldSchema = AccountFields;
|
||||
for (const fieldName in fieldSchema) {
|
||||
const field = fieldSchema[fieldName];
|
||||
let value = input[field.name];
|
||||
|
||||
if (value === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The value required to clear an account root field varies
|
||||
if (value === CLEAR_SETTING && field.hasOwnProperty('defaults')) {
|
||||
value = field.defaults;
|
||||
}
|
||||
|
||||
if (field.encoding === 'hex' && !field.length) {
|
||||
// This is currently only used for Domain field
|
||||
value = new Buffer(value, 'ascii').toString('hex').toUpperCase();
|
||||
}
|
||||
|
||||
transaction.tx_json[fieldName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: A fee of 1% requires 101% of the destination to be sent for the
|
||||
* destination to receive 100%.
|
||||
* The transfer rate is specified as the input amount as fraction of 1.
|
||||
* To specify the default rate of 0%, a 100% input amount, specify 1.
|
||||
* To specify a rate of 1%, a 101% input amount, specify 1.01
|
||||
*
|
||||
* @param {Number|String} transferRate
|
||||
*
|
||||
* @returns {Number|String} numbers will be converted while strings
|
||||
* are returned
|
||||
*/
|
||||
|
||||
function convertTransferRate(transferRate) {
|
||||
return _.isNumber(transferRate) ? transferRate * 1e9 : transferRate;
|
||||
}
|
||||
|
||||
function createSettingsTransaction(account, settings) {
|
||||
validate.address(account);
|
||||
validate.settings(settings);
|
||||
|
||||
const transaction = new Transaction();
|
||||
if (settings.regularKey) {
|
||||
return transaction.setRegularKey({
|
||||
account: account,
|
||||
regular_key: settings.regularKey
|
||||
});
|
||||
}
|
||||
|
||||
transaction.accountSet(account);
|
||||
setTransactionFlags(transaction, settings);
|
||||
setTransactionFields(transaction, settings);
|
||||
|
||||
if (transaction.tx_json.TransferRate !== undefined) {
|
||||
transaction.tx_json.TransferRate = convertTransferRate(
|
||||
transaction.tx_json.TransferRate);
|
||||
}
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareSettings(account, settings, instructions, callback) {
|
||||
const transaction = createSettingsTransaction(account, settings);
|
||||
utils.createTxJSON(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(prepareSettings);
|
||||
68
src/api/transaction/sign.js
Normal file
68
src/api/transaction/sign.js
Normal file
@@ -0,0 +1,68 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const core = utils.common.core;
|
||||
const validate = utils.common.validate;
|
||||
|
||||
/**
|
||||
* These prefixes are inserted before the source material used to
|
||||
* generate various hashes. This is done to put each hash in its own
|
||||
* "space." This way, two different types of objects with the
|
||||
* same binary data will produce different hashes.
|
||||
*
|
||||
* Each prefix is a 4-byte value with the last byte set to zero
|
||||
* and the first three bytes formed from the ASCII equivalent of
|
||||
* some arbitrary string. For example "TXN".
|
||||
*/
|
||||
const HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
const HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
const HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
|
||||
function getKeyPair(address, secret) {
|
||||
return core.Seed.from_json(secret).get_key(address);
|
||||
}
|
||||
|
||||
function getPublicKeyHex(keypair) {
|
||||
return keypair.to_hex_pub();
|
||||
}
|
||||
|
||||
function serialize(txJSON) {
|
||||
return core.SerializedObject.from_json(txJSON);
|
||||
}
|
||||
|
||||
function hashSerialization(serialized, prefix) {
|
||||
return serialized.hash(prefix || HASH_TX_ID).to_hex();
|
||||
}
|
||||
|
||||
function hashJSON(txJSON, prefix) {
|
||||
return hashSerialization(serialize(txJSON), prefix);
|
||||
}
|
||||
|
||||
function signingHash(txJSON, isTestNet=false) {
|
||||
return hashJSON(txJSON, isTestNet ? HASH_TX_SIGN_TESTNET : HASH_TX_SIGN);
|
||||
}
|
||||
|
||||
function computeSignature(txJSON, keypair) {
|
||||
const signature = keypair.sign(signingHash(txJSON));
|
||||
return core.sjcl.codec.hex.fromBits(signature).toUpperCase();
|
||||
}
|
||||
|
||||
function sign(txJSON: {Account: string; SigningPubKey: string,
|
||||
TxnSignature: string}, secret: string):
|
||||
{signedTransaction: string; id: string} {
|
||||
validate.txJSON(txJSON);
|
||||
validate.addressAndSecret({address: txJSON.Account, secret: secret});
|
||||
|
||||
const keypair = getKeyPair(txJSON.Account, secret);
|
||||
if (txJSON.SigningPubKey === undefined) {
|
||||
txJSON.SigningPubKey = getPublicKeyHex(keypair);
|
||||
}
|
||||
txJSON.TxnSignature = computeSignature(txJSON, keypair);
|
||||
const serialized = serialize(txJSON);
|
||||
return {
|
||||
signedTransaction: serialized.to_hex(),
|
||||
id: hashSerialization(serialized, HASH_TX_ID)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = sign;
|
||||
15
src/api/transaction/submit.js
Normal file
15
src/api/transaction/submit.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Request = utils.common.core.Request;
|
||||
|
||||
function submit(tx_blob: string,
|
||||
callback: (err: any, data: any) => void): void {
|
||||
validate.blob(tx_blob);
|
||||
const request = new Request(this.remote, 'submit');
|
||||
request.message.tx_blob = tx_blob;
|
||||
request.request(null, callback);
|
||||
}
|
||||
|
||||
module.exports = submit;
|
||||
35
src/api/transaction/trustline.js
Normal file
35
src/api/transaction/trustline.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const utils = require('./utils');
|
||||
const validate = utils.common.validate;
|
||||
const Transaction = utils.common.core.Transaction;
|
||||
|
||||
const TrustSetFlags = {
|
||||
authorized: {set: 'SetAuth'},
|
||||
allowRippling: {set: 'ClearNoRipple', unset: 'NoRipple'},
|
||||
frozen: {set: 'SetFreeze', unset: 'ClearFreeze'}
|
||||
};
|
||||
|
||||
function createTrustlineTransaction(account, trustline) {
|
||||
validate.address(account);
|
||||
validate.trustline(trustline);
|
||||
|
||||
const limit = {
|
||||
currency: trustline.currency,
|
||||
issuer: trustline.counterparty,
|
||||
value: trustline.limit
|
||||
};
|
||||
|
||||
const transaction = new Transaction();
|
||||
transaction.trustSet(account, limit,
|
||||
trustline.qualityIn, trustline.qualityOut);
|
||||
utils.setTransactionBitFlags(transaction, trustline, TrustSetFlags);
|
||||
return transaction;
|
||||
}
|
||||
|
||||
function prepareTrustline(account, trustline, instructions, callback) {
|
||||
const transaction = createTrustlineTransaction(account, trustline);
|
||||
utils.createTxJSON(transaction, this.remote, instructions, callback);
|
||||
}
|
||||
|
||||
module.exports = utils.wrapCatch(prepareTrustline);
|
||||
70
src/api/transaction/utils.js
Normal file
70
src/api/transaction/utils.js
Normal file
@@ -0,0 +1,70 @@
|
||||
/* @flow */
|
||||
'use strict';
|
||||
const BigNumber = require('bignumber.js');
|
||||
const common = require('../common');
|
||||
|
||||
function setTransactionBitFlags(transaction: any, values: any, flags: any):
|
||||
void {
|
||||
for (const flagName in flags) {
|
||||
const flagValue = values[flagName];
|
||||
const flagConversions = flags[flagName];
|
||||
|
||||
if (flagValue === true && flagConversions.set !== undefined) {
|
||||
transaction.setFlags(flagConversions.set);
|
||||
}
|
||||
if (flagValue === false && flagConversions.unset !== undefined) {
|
||||
transaction.setFlags(flagConversions.unset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getFeeDrops(remote) {
|
||||
const feeUnits = 10; // all transactions currently have a fee of 10 fee units
|
||||
return remote.feeTx(feeUnits).to_text();
|
||||
}
|
||||
|
||||
function createTxJSON(transaction: any, remote: any, instructions: any,
|
||||
callback: (err: ?(typeof Error), data: {tx_json: any}) => void): void {
|
||||
common.validate.instructions(instructions);
|
||||
|
||||
transaction.complete();
|
||||
const account = transaction.getAccount();
|
||||
const txJSON = transaction.tx_json;
|
||||
|
||||
if (instructions.maxLedgerVersion !== undefined) {
|
||||
txJSON.LastLedgerSequence = parseInt(instructions.maxLedgerVersion, 10);
|
||||
} else {
|
||||
const offset = instructions.maxLedgerVersionOffset !== undefined ?
|
||||
parseInt(instructions.maxLedgerVersionOffset, 10) : 3;
|
||||
txJSON.LastLedgerSequence = remote.getLedgerSequence() + offset;
|
||||
}
|
||||
|
||||
if (instructions.fee !== undefined) {
|
||||
txJSON.Fee = common.xrpToDrops(instructions.fee);
|
||||
} else {
|
||||
const serverFeeDrops = getFeeDrops(remote);
|
||||
if (instructions.maxFee !== undefined) {
|
||||
const maxFeeDrops = common.xrpToDrops(instructions.maxFee);
|
||||
txJSON.Fee = BigNumber.min(serverFeeDrops, maxFeeDrops).toString();
|
||||
} else {
|
||||
txJSON.Fee = serverFeeDrops;
|
||||
}
|
||||
}
|
||||
|
||||
if (instructions.sequence !== undefined) {
|
||||
txJSON.Sequence = parseInt(instructions.sequence, 10);
|
||||
callback(null, txJSON);
|
||||
} else {
|
||||
remote.findAccount(account).getNextSequence(function(error, sequence) {
|
||||
txJSON.Sequence = sequence;
|
||||
callback(null, txJSON);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
setTransactionBitFlags: setTransactionBitFlags,
|
||||
createTxJSON: createTxJSON,
|
||||
wrapCatch: common.wrapCatch,
|
||||
common: common
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
'use strict';
|
||||
// Routines for working with an account.
|
||||
//
|
||||
// You should not instantiate this class yourself, instead use Remote#account.
|
||||
@@ -9,16 +10,15 @@
|
||||
// balance_proposed
|
||||
//
|
||||
|
||||
// var network = require('./network.js');
|
||||
var async = require('async');
|
||||
var util = require('util');
|
||||
var extend = require('extend');
|
||||
var EventEmitter = require('events').EventEmitter;
|
||||
var Amount = require('./amount').Amount;
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var TransactionManager = require('./transactionmanager').TransactionManager;
|
||||
var sjcl = require('./utils').sjcl;
|
||||
var Base = require('./base').Base;
|
||||
const _ = require('lodash');
|
||||
const async = require('async');
|
||||
const util = require('util');
|
||||
const extend = require('extend');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const TransactionManager = require('./transactionmanager').TransactionManager;
|
||||
const sjcl = require('./utils').sjcl;
|
||||
const Base = require('./base').Base;
|
||||
|
||||
/**
|
||||
* @constructor Account
|
||||
@@ -29,7 +29,7 @@ var Base = require('./base').Base;
|
||||
function Account(remote, account) {
|
||||
EventEmitter.call(this);
|
||||
|
||||
var self = this;
|
||||
const self = this;
|
||||
|
||||
this._remote = remote;
|
||||
this._account = UInt160.from_json(account);
|
||||
@@ -40,29 +40,29 @@ function Account(remote, account) {
|
||||
// Important: This must never be overwritten, only extend()-ed
|
||||
this._entry = { };
|
||||
|
||||
function listenerAdded(type, listener) {
|
||||
function listenerAdded(type) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_subscribe()
|
||||
.add_account(self._account_id)
|
||||
.broadcast();
|
||||
self._remote.requestSubscribe()
|
||||
.addAccount(self._account_id)
|
||||
.broadcast().request();
|
||||
}
|
||||
self._subs += 1;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.on('newListener', listenerAdded);
|
||||
|
||||
function listenerRemoved(type, listener) {
|
||||
function listenerRemoved(type) {
|
||||
if (~Account.subscribeEvents.indexOf(type)) {
|
||||
self._subs -= 1;
|
||||
if (!self._subs && self._remote._connected) {
|
||||
self._remote.request_unsubscribe()
|
||||
.add_account(self._account_id)
|
||||
.broadcast();
|
||||
self._remote.requestUnsubscribe()
|
||||
.addAccount(self._account_id)
|
||||
.broadcast().request();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.on('removeListener', listenerRemoved);
|
||||
|
||||
@@ -70,7 +70,7 @@ function Account(remote, account) {
|
||||
if (self._account.is_valid() && self._subs) {
|
||||
request.add_account(self._account_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this._remote.on('prepare_subscribe', attachAccount);
|
||||
|
||||
@@ -79,11 +79,11 @@ function Account(remote, account) {
|
||||
return;
|
||||
}
|
||||
|
||||
var changed = false;
|
||||
let changed = false;
|
||||
|
||||
transaction.mmeta.each(function(an) {
|
||||
var isAccount = an.fields.Account === self._account_id;
|
||||
var isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
|
||||
const isAccount = an.fields.Account === self._account_id;
|
||||
const isAccountRoot = isAccount && (an.entryType === 'AccountRoot');
|
||||
|
||||
if (isAccountRoot) {
|
||||
extend(self._entry, an.fieldsNew, an.fieldsFinal);
|
||||
@@ -94,14 +94,14 @@ function Account(remote, account) {
|
||||
if (changed) {
|
||||
self.emit('entry', self._entry);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.on('transaction', handleTransaction);
|
||||
|
||||
this._transactionManager = new TransactionManager(this);
|
||||
|
||||
return this;
|
||||
};
|
||||
}
|
||||
|
||||
util.inherits(Account, EventEmitter);
|
||||
|
||||
@@ -109,7 +109,7 @@ util.inherits(Account, EventEmitter);
|
||||
* List of events that require a remote subscription to the account.
|
||||
*/
|
||||
|
||||
Account.subscribeEvents = [ 'transaction', 'entry' ];
|
||||
Account.subscribeEvents = ['transaction', 'entry'];
|
||||
|
||||
Account.prototype.toJson = function() {
|
||||
return this._account.to_json();
|
||||
@@ -144,9 +144,9 @@ Account.prototype.getInfo = function(callback) {
|
||||
* @param {Function} callback
|
||||
*/
|
||||
|
||||
Account.prototype.entry = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
Account.prototype.entry = function(callback_) {
|
||||
const self = this;
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (err) {
|
||||
@@ -156,21 +156,21 @@ Account.prototype.entry = function(callback) {
|
||||
self.emit('entry', self._entry);
|
||||
callback(null, info);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Account.prototype.getNextSequence = function(callback) {
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
Account.prototype.getNextSequence = function(callback_) {
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
function isNotFound(err) {
|
||||
return err && typeof err === 'object'
|
||||
&& typeof err.remote === 'object'
|
||||
&& err.remote.error === 'actNotFound';
|
||||
};
|
||||
}
|
||||
|
||||
function accountInfo(err, info) {
|
||||
if (isNotFound(err)) {
|
||||
@@ -181,7 +181,7 @@ Account.prototype.getNextSequence = function(callback) {
|
||||
} else {
|
||||
callback(null, info.account_data.Sequence);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.getInfo(accountInfo);
|
||||
|
||||
@@ -197,9 +197,9 @@ Account.prototype.getNextSequence = function(callback) {
|
||||
* @param {function(err, lines)} callback Called with the result
|
||||
*/
|
||||
|
||||
Account.prototype.lines = function(callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
Account.prototype.lines = function(callback_) {
|
||||
const self = this;
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
function accountLines(err, res) {
|
||||
if (err) {
|
||||
@@ -225,23 +225,22 @@ Account.prototype.lines = function(callback) {
|
||||
* @returns {Account}
|
||||
*/
|
||||
|
||||
Account.prototype.line = function(currency, address, callback) {
|
||||
var self = this;
|
||||
var callback = typeof callback === 'function' ? callback : function(){};
|
||||
Account.prototype.line = function(currency, address, callback_) {
|
||||
const self = this;
|
||||
const callback = typeof callback_ === 'function' ? callback_ : _.noop;
|
||||
|
||||
self.lines(function(err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var line;
|
||||
let line;
|
||||
|
||||
top:
|
||||
for (var i=0; i<data.lines.length; i++) {
|
||||
var l = data.lines[i];
|
||||
for (let i = 0; i < data.lines.length; i++) {
|
||||
const l = data.lines[i];
|
||||
if (l.account === address && l.currency === currency) {
|
||||
line = l;
|
||||
break top;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,15 +270,16 @@ Account.prototype.notifyTx = function(transaction) {
|
||||
|
||||
this.emit('transaction', transaction);
|
||||
|
||||
var account = transaction.transaction.Account;
|
||||
const account = transaction.transaction.Account;
|
||||
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
var isThisAccount = (account === this._account_id);
|
||||
const isThisAccount = (account === this._account_id);
|
||||
|
||||
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound', transaction);
|
||||
this.emit(isThisAccount ? 'transaction-outbound' : 'transaction-inbound',
|
||||
transaction);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -297,16 +297,17 @@ Account.prototype.submit = function(transaction) {
|
||||
/**
|
||||
* Check whether the given public key is valid for this account
|
||||
*
|
||||
* @param {Hex-encoded String|RippleAddress} public_key
|
||||
* @param {Function} callback
|
||||
* @param {Hex-encoded_String|RippleAddress} public_key Public key
|
||||
* @param {Function} callback Is a callback
|
||||
* @returns {void}
|
||||
*
|
||||
* @callback
|
||||
* @param {Error} err
|
||||
* @param {Boolean} true if the public key is valid and active, false otherwise
|
||||
* param {Error} err
|
||||
* param {Boolean} true if the public key is valid and active, false otherwise
|
||||
*/
|
||||
Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
var self = this;
|
||||
var public_key_as_uint160;
|
||||
const self = this;
|
||||
let public_key_as_uint160;
|
||||
|
||||
try {
|
||||
public_key_as_uint160 = Account._publicKeyToAddress(public_key);
|
||||
@@ -315,7 +316,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
}
|
||||
|
||||
function getAccountInfo(async_callback) {
|
||||
self.getInfo(function(err, account_info_res){
|
||||
self.getInfo(function(err, account_info_res) {
|
||||
|
||||
// If the remote responds with an Account Not Found error then the account
|
||||
// is unfunded and thus we can assume that the master key is active
|
||||
@@ -325,7 +326,7 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
async_callback(err, account_info_res);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function publicKeyIsValid(account_info_res, async_callback) {
|
||||
// Catch the case of unfunded accounts
|
||||
@@ -340,10 +341,11 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
var account_info = account_info_res.account_data;
|
||||
const account_info = account_info_res.account_data;
|
||||
|
||||
// Respond with true if the RegularKey is set and matches the given public key or
|
||||
// if the public key matches the account address and the lsfDisableMaster is not set
|
||||
// Respond with true if the RegularKey is set and matches the given
|
||||
// public key or if the public key matches the account address and
|
||||
// the lsfDisableMaster is not set
|
||||
if (account_info.RegularKey &&
|
||||
account_info.RegularKey === public_key_as_uint160) {
|
||||
async_callback(null, true);
|
||||
@@ -353,9 +355,9 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
} else {
|
||||
async_callback(null, false);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var steps = [
|
||||
const steps = [
|
||||
getAccountInfo,
|
||||
publicKeyIsValid
|
||||
];
|
||||
@@ -368,25 +370,25 @@ Account.prototype.publicKeyIsActive = function(public_key, callback) {
|
||||
*
|
||||
* @static
|
||||
*
|
||||
* @param {Hex-encoded string|RippleAddress} public_key
|
||||
* @returns {RippleAddress}
|
||||
* @param {Hex-encoded_string|RippleAddress} public_key Public key
|
||||
* @returns {RippleAddress} Ripple Address
|
||||
*/
|
||||
Account._publicKeyToAddress = function(public_key) {
|
||||
// Based on functions in /src/js/ripple/keypair.js
|
||||
function hexToUInt160(public_key) {
|
||||
var bits = sjcl.codec.hex.toBits(public_key);
|
||||
var hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
var address = UInt160.from_bits(hash);
|
||||
function hexToUInt160(publicKey) {
|
||||
const bits = sjcl.codec.hex.toBits(publicKey);
|
||||
const hash = sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits));
|
||||
const address = UInt160.from_bits(hash);
|
||||
address.set_version(Base.VER_ACCOUNT_ID);
|
||||
|
||||
return address.to_json();
|
||||
};
|
||||
}
|
||||
|
||||
if (UInt160.is_valid(public_key)) {
|
||||
return public_key;
|
||||
} else if (/^[0-9a-fA-F]+$/.test(public_key)) {
|
||||
return hexToUInt160(public_key);
|
||||
} else {
|
||||
} else { // eslint-disable-line no-else-return
|
||||
throw new Error('Public key is invalid. Must be a UInt160 or a hex string');
|
||||
}
|
||||
};
|
||||
990
src/core/amount.js
Normal file
990
src/core/amount.js
Normal file
@@ -0,0 +1,990 @@
|
||||
'use strict';
|
||||
|
||||
// Represent Ripple amounts and currencies.
|
||||
// - Numbers in hex are big-endian.
|
||||
|
||||
const assert = require('assert');
|
||||
const extend = require('extend');
|
||||
const utils = require('./utils');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Seed = require('./seed').Seed;
|
||||
const Currency = require('./currency').Currency;
|
||||
const Value = require('./value').Value;
|
||||
const IOUValue = require('./iouvalue').IOUValue;
|
||||
const XRPValue = require('./xrpvalue').XRPValue;
|
||||
|
||||
function Amount(value = new XRPValue(NaN)) {
|
||||
// Json format:
|
||||
// integer : XRP
|
||||
// { 'value' : ..., 'currency' : ..., 'issuer' : ...}
|
||||
assert(value instanceof Value);
|
||||
|
||||
this._value = value;
|
||||
this._is_native = true; // Default to XRP. Only valid if value is not NaN.
|
||||
this._currency = new Currency();
|
||||
this._issuer = new UInt160();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set strict_mode = false to disable amount range checking
|
||||
*/
|
||||
|
||||
Amount.strict_mode = true;
|
||||
|
||||
const consts = {
|
||||
currency_xns: 0,
|
||||
currency_one: 1,
|
||||
xns_precision: 6,
|
||||
|
||||
// bi_ prefix refers to "big integer"
|
||||
// man refers to mantissa
|
||||
bi_man_max_value: '9999999999999999',
|
||||
bi_man_min_value: Number(1e15).toString(),
|
||||
bi_xns_max: Number(1e17).toString(),
|
||||
bi_xns_min: Number(-1e17).toString(),
|
||||
|
||||
cMinOffset: -96,
|
||||
cMaxOffset: 80,
|
||||
|
||||
// Maximum possible amount for non-XRP currencies using the maximum mantissa
|
||||
// with maximum exponent. Corresponds to hex 0xEC6386F26FC0FFFF.
|
||||
max_value: '9999999999999999e80',
|
||||
// Minimum possible amount for non-XRP currencies.
|
||||
min_value: '-1000000000000000e-96'
|
||||
};
|
||||
|
||||
const MAX_XRP_VALUE = new XRPValue(1e11);
|
||||
const MAX_IOU_VALUE = new IOUValue(consts.max_value);
|
||||
const MIN_IOU_VALUE = new IOUValue(consts.min_value).abs();
|
||||
|
||||
const bi_xns_unit = new IOUValue(1e6);
|
||||
|
||||
// Add constants to Amount class
|
||||
extend(Amount, consts);
|
||||
|
||||
// DEPRECATED: Use Amount instead, e.g. Amount.currency_xns
|
||||
exports.consts = consts;
|
||||
|
||||
// Given '100/USD/ISSUER' return the a string with ISSUER remapped.
|
||||
Amount.text_full_rewrite = function(j) {
|
||||
return Amount.from_json(j).to_text_full();
|
||||
};
|
||||
|
||||
// Given '100/USD/ISSUER' return the json.
|
||||
Amount.json_rewrite = function(j) {
|
||||
return Amount.from_json(j).to_json();
|
||||
};
|
||||
|
||||
Amount.from_number = function(n) {
|
||||
return (new Amount()).parse_number(n);
|
||||
};
|
||||
|
||||
Amount.from_json = function(j) {
|
||||
return (new Amount()).parse_json(j);
|
||||
};
|
||||
|
||||
Amount.from_quality = function(quality, currency, issuer, opts) {
|
||||
return (new Amount()).parse_quality(quality, currency, issuer, opts);
|
||||
};
|
||||
|
||||
Amount.from_human = function(j, opts) {
|
||||
return (new Amount()).parse_human(j, opts);
|
||||
};
|
||||
|
||||
Amount.is_valid = function(j) {
|
||||
return Amount.from_json(j).is_valid();
|
||||
};
|
||||
|
||||
Amount.is_valid_full = function(j) {
|
||||
return Amount.from_json(j).is_valid_full();
|
||||
};
|
||||
|
||||
Amount.NaN = function() {
|
||||
const result = new Amount();
|
||||
result._value = new IOUValue(NaN); // should have no effect
|
||||
return result; // but let's be careful
|
||||
};
|
||||
|
||||
// be sure that _is_native is set properly BEFORE calling _set_value
|
||||
Amount.prototype._set_value = function(value: Value) {
|
||||
|
||||
this._value = value.isZero() && value.isNegative() ?
|
||||
value.negate() : value;
|
||||
this._check_limits();
|
||||
|
||||
};
|
||||
|
||||
// Returns a new value which is the absolute value of this.
|
||||
Amount.prototype.abs = function() {
|
||||
|
||||
return this._copy(this._value.abs());
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.add = function(addend) {
|
||||
const addendAmount = Amount.from_json(addend);
|
||||
|
||||
if (!this.is_comparable(addendAmount)) {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
return this._copy(this._value.add(addendAmount._value));
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.subtract = function(subtrahend) {
|
||||
// Correctness over speed, less code has less bugs, reuse add code.
|
||||
return this.add(Amount.from_json(subtrahend).negate());
|
||||
};
|
||||
|
||||
// XXX Diverges from cpp.
|
||||
Amount.prototype.multiply = function(multiplicand) {
|
||||
|
||||
const multiplicandAmount = Amount.from_json(multiplicand);
|
||||
|
||||
return this._copy(this._value.multiply(multiplicandAmount._value));
|
||||
|
||||
};
|
||||
|
||||
Amount.prototype.scale = function(scaleFactor) {
|
||||
return this.multiply(scaleFactor);
|
||||
};
|
||||
|
||||
Amount.prototype.divide = function(divisor) {
|
||||
const divisorAmount = Amount.from_json(divisor);
|
||||
|
||||
return this._copy(this._value.divide(divisorAmount._value));
|
||||
};
|
||||
|
||||
/**
|
||||
* This function calculates a ratio - such as a price - between two Amount
|
||||
* objects.
|
||||
*
|
||||
* The return value will have the same type (currency) as the numerator. This is
|
||||
* a simplification, which should be sane in most cases. For example, a USD/XRP
|
||||
* price would be rendered as USD.
|
||||
*
|
||||
* @example
|
||||
* const price = buy_amount.ratio_human(sell_amount);
|
||||
*
|
||||
* @this {Amount} The numerator (top half) of the fraction.
|
||||
* @param {Amount} denominator The denominator (bottom half) of the fraction.
|
||||
* @param opts Options for the calculation.
|
||||
* @param opts.reference_date {Date|Number} Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {Amount} The resulting ratio. Unit will be the same as numerator.
|
||||
*/
|
||||
|
||||
Amount.prototype.ratio_human = function(denom, opts) {
|
||||
const options = extend({ }, opts);
|
||||
|
||||
const numerator = this.clone();
|
||||
|
||||
let denominator = Amount.from_json(denom);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!numerator.is_valid() || !denominator.is_valid()) {
|
||||
return new Amount(NaN);
|
||||
}
|
||||
|
||||
if (denominator.is_zero()) {
|
||||
return new Amount(NaN);
|
||||
}
|
||||
|
||||
// Apply interest/demurrage
|
||||
//
|
||||
// We only need to apply it to the second factor, because the currency unit of
|
||||
// the first factor will carry over into the result.
|
||||
if (options.reference_date) {
|
||||
denominator = denominator.applyInterest(options.reference_date);
|
||||
}
|
||||
|
||||
// Special case: The denominator is a native (XRP) amount.
|
||||
//
|
||||
// In that case, it's going to be expressed as base units (1 XRP =
|
||||
// 10^xns_precision base units).
|
||||
//
|
||||
// However, the unit of the denominator is lost, so when the resulting ratio
|
||||
// is printed, the ratio is going to be too small by a factor of
|
||||
// 10^xns_precision.
|
||||
//
|
||||
// To compensate, we multiply the numerator by 10^xns_precision.
|
||||
if (denominator._is_native) {
|
||||
numerator._set_value(numerator.multiply(bi_xns_unit));
|
||||
}
|
||||
|
||||
return numerator.divide(denominator);
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate a product of two amounts.
|
||||
*
|
||||
* This function allows you to calculate a product between two amounts which
|
||||
* retains XRPs human/external interpretation (i.e. 1 XRP = 1,000,000 base
|
||||
* units).
|
||||
*
|
||||
* Intended use is to calculate something like: 10 USD * 10 XRP/USD = 100 XRP
|
||||
*
|
||||
* @example
|
||||
* let sell_amount = buy_amount.product_human(price);
|
||||
*
|
||||
* @see Amount#ratio_human
|
||||
*
|
||||
* @param {Amount} factor The second factor of the product.
|
||||
* @param {Object} opts Options for the calculation.
|
||||
* @param {Date|Number} opts.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {Amount} The product. Unit will be the same as the first factor.
|
||||
*/
|
||||
Amount.prototype.product_human = function(factor, options = {}) {
|
||||
|
||||
let fac = Amount.from_json(factor);
|
||||
|
||||
// If either operand is NaN, the result is NaN.
|
||||
if (!this.is_valid() || !fac.is_valid()) {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
// Apply interest/demurrage
|
||||
//
|
||||
// We only need to apply it to the second factor, because the currency unit of
|
||||
// the first factor will carry over into the result.
|
||||
if (options.reference_date) {
|
||||
fac = fac.applyInterest(options.reference_date);
|
||||
}
|
||||
|
||||
const product = this.multiply(fac);
|
||||
|
||||
// Special case: The second factor is a native (XRP) amount expressed as base
|
||||
// units (1 XRP = 10^xns_precision base units).
|
||||
//
|
||||
// See also Amount#ratio_human.
|
||||
if (fac._is_native) {
|
||||
const quotient = product.divide(bi_xns_unit.toString());
|
||||
product._set_value(quotient._value);
|
||||
}
|
||||
|
||||
return product;
|
||||
};
|
||||
|
||||
/**
|
||||
* Turn this amount into its inverse.
|
||||
*
|
||||
* @return {Amount} self
|
||||
* @private
|
||||
*/
|
||||
Amount.prototype._invert = function() {
|
||||
this._set_value(this._value.invert());
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the inverse of this amount.
|
||||
*
|
||||
* @return {Amount} New Amount object with same currency and issuer, but the
|
||||
* inverse of the value.
|
||||
*/
|
||||
Amount.prototype.invert = function() {
|
||||
return this.clone()._invert();
|
||||
};
|
||||
|
||||
/**
|
||||
* Canonicalize amount value is now taken care of in the Value classes
|
||||
*
|
||||
* Mirrors rippled's internal Amount representation
|
||||
* From https://github.com/ripple/rippled/blob/develop/src/ripple/data
|
||||
* /protocol/STAmount.h#L31-L40
|
||||
*
|
||||
* Internal form:
|
||||
* 1: If amount is zero, then value is zero and offset is -100
|
||||
* 2: Otherwise:
|
||||
* legal offset range is -96 to +80 inclusive
|
||||
* value range is 10^15 to (10^16 - 1) inclusive
|
||||
* amount = value * [10 ^ offset]
|
||||
*
|
||||
* -------------------
|
||||
*
|
||||
* The amount can be epxresses as A x 10^B
|
||||
* Where:
|
||||
* - A must be an integer between 10^15 and (10^16)-1 inclusive
|
||||
* - B must be between -96 and 80 inclusive
|
||||
*
|
||||
* This results
|
||||
* - minumum: 10^15 x 10^-96 -> 10^-81 -> -1e-81
|
||||
* - maximum: (10^16)-1 x 10^80 -> 9999999999999999e80
|
||||
*
|
||||
* @returns {Amount}
|
||||
* @throws {Error} if offset exceeds legal ranges, meaning the amount value is
|
||||
* bigger than supported
|
||||
*/
|
||||
|
||||
Amount.prototype._check_limits = function() {
|
||||
if (!Amount.strict_mode) {
|
||||
return this;
|
||||
}
|
||||
if (this._value.isNaN() || this._value.isZero()) {
|
||||
return this;
|
||||
}
|
||||
const absval = this._value.abs();
|
||||
if (this._is_native) {
|
||||
if (absval.greaterThan(MAX_XRP_VALUE)) {
|
||||
throw new Error('Exceeding max value of ' + MAX_XRP_VALUE.toString());
|
||||
}
|
||||
} else {
|
||||
if (absval.lessThan(MIN_IOU_VALUE)) {
|
||||
throw new Error('Exceeding min value of ' + MIN_IOU_VALUE.toString());
|
||||
}
|
||||
if (absval.greaterThan(MAX_IOU_VALUE)) {
|
||||
throw new Error('Exceeding max value of ' + MAX_IOU_VALUE.toString());
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.clone = function(negate) {
|
||||
return this.copyTo(new Amount(), negate);
|
||||
};
|
||||
|
||||
Amount.prototype._copy = function(value) {
|
||||
const copy = this.clone();
|
||||
copy._set_value(value);
|
||||
return copy;
|
||||
};
|
||||
|
||||
Amount.prototype.compareTo = function(to) {
|
||||
const toAmount = Amount.from_json(to);
|
||||
if (!this.is_comparable(toAmount)) {
|
||||
return new Amount();
|
||||
}
|
||||
return this._value.comparedTo(toAmount._value);
|
||||
};
|
||||
|
||||
// Make d a copy of this. Returns d.
|
||||
// Modification of objects internally refered to is not allowed.
|
||||
Amount.prototype.copyTo = function(d, negate) {
|
||||
d._value = negate ? this._value.negate() : this._value;
|
||||
d._is_native = this._is_native;
|
||||
d._currency = this._currency;
|
||||
d._issuer = this._issuer;
|
||||
return d;
|
||||
};
|
||||
|
||||
Amount.prototype.currency = function() {
|
||||
return this._currency;
|
||||
};
|
||||
|
||||
Amount.prototype.equals = function(d, ignore_issuer) {
|
||||
if (!(d instanceof Amount)) {
|
||||
return this.equals(Amount.from_json(d));
|
||||
}
|
||||
|
||||
return this.is_valid() && d.is_valid()
|
||||
&& this._is_native === d._is_native
|
||||
&& this._value.equals(d._value)
|
||||
&& (this._is_native || (this._currency.equals(d._currency)
|
||||
&& (ignore_issuer || this._issuer.equals(d._issuer))));
|
||||
};
|
||||
|
||||
// True if Amounts are valid and both native or non-native.
|
||||
Amount.prototype.is_comparable = function(v) {
|
||||
return this.is_valid() && v.is_valid() && this._is_native === v._is_native;
|
||||
};
|
||||
|
||||
Amount.prototype.is_native = function() {
|
||||
return this._is_native;
|
||||
};
|
||||
|
||||
Amount.prototype.is_negative = function() {
|
||||
return this._value.isNegative();
|
||||
};
|
||||
|
||||
Amount.prototype.is_positive = function() {
|
||||
return !this.is_zero() && !this.is_negative();
|
||||
};
|
||||
|
||||
// Only checks the value. Not the currency and issuer.
|
||||
Amount.prototype.is_valid = function() {
|
||||
return !this._value.isNaN();
|
||||
};
|
||||
|
||||
Amount.prototype.is_valid_full = function() {
|
||||
return this.is_valid()
|
||||
&& this._currency.is_valid()
|
||||
&& this._issuer.is_valid();
|
||||
};
|
||||
|
||||
Amount.prototype.is_zero = function() {
|
||||
return this._value.isZero();
|
||||
};
|
||||
|
||||
Amount.prototype.issuer = function() {
|
||||
return this._issuer;
|
||||
};
|
||||
|
||||
// Return a new value.
|
||||
Amount.prototype.negate = function() {
|
||||
return this.clone('NEGATE');
|
||||
};
|
||||
|
||||
/**
|
||||
* Tries to correctly interpret an amount as entered by a user.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* XRP 250 => 250000000/XRP
|
||||
* 25.2 XRP => 25200000/XRP
|
||||
* USD 100.40 => 100.4/USD/?
|
||||
* 100 => 100000000/XRP
|
||||
*
|
||||
*
|
||||
* The regular expression below matches above cases, broken down for better
|
||||
* understanding:
|
||||
*
|
||||
* // either 3 letter alphabetic currency-code or 3 digit numeric currency-code.
|
||||
* // See ISO 4217
|
||||
* ([A-z]{3}|[0-9]{3})
|
||||
*
|
||||
* // end of string
|
||||
* $
|
||||
*/
|
||||
|
||||
Amount.prototype.parse_human = function(j, options) {
|
||||
const opts = options || {};
|
||||
|
||||
const hex_RE = /^[a-fA-F0-9]{40}$/;
|
||||
const currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/;
|
||||
|
||||
let value;
|
||||
let currency;
|
||||
|
||||
const words = j.split(' ').filter(function(word) {
|
||||
return word !== '';
|
||||
});
|
||||
|
||||
function isNumber(s) {
|
||||
return isFinite(s) && s !== '' && s !== null;
|
||||
}
|
||||
|
||||
if (words.length === 1) {
|
||||
if (isNumber(words[0])) {
|
||||
value = words[0];
|
||||
currency = 'XRP';
|
||||
} else {
|
||||
value = words[0].slice(0, -3);
|
||||
currency = words[0].slice(-3);
|
||||
if (!(isNumber(value) && currency.match(currency_RE))) {
|
||||
return new Amount();
|
||||
}
|
||||
}
|
||||
} else if (words.length === 2) {
|
||||
if (isNumber(words[0]) && words[1].match(hex_RE)) {
|
||||
value = words[0];
|
||||
currency = words[1];
|
||||
} else if (words[0].match(currency_RE) && isNumber(words[1])) {
|
||||
value = words[1];
|
||||
currency = words[0];
|
||||
} else if (isNumber(words[0]) && words[1].match(currency_RE)) {
|
||||
value = words[0];
|
||||
currency = words[1];
|
||||
} else {
|
||||
return new Amount();
|
||||
}
|
||||
} else {
|
||||
return new Amount();
|
||||
}
|
||||
|
||||
currency = currency.toUpperCase();
|
||||
this.set_currency(currency);
|
||||
this._is_native = (currency === 'XRP');
|
||||
const newValue =
|
||||
(this._is_native ? new XRPValue(value) :
|
||||
new IOUValue(value));
|
||||
this._set_value(newValue);
|
||||
|
||||
// Apply interest/demurrage
|
||||
if (opts.reference_date && this._currency.has_interest()) {
|
||||
const interest = this._currency.get_interest_at(opts.reference_date);
|
||||
this._set_value(
|
||||
this._value.divide(new IOUValue(interest.toString())));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.parse_issuer = function(issuer) {
|
||||
this._issuer = UInt160.from_json(issuer);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode a price from a BookDirectory index.
|
||||
*
|
||||
* BookDirectory ledger entries each encode the offer price in their index. This
|
||||
* method can decode that information and populate an Amount object with it.
|
||||
*
|
||||
* It is possible not to provide a currency or issuer, but be aware that Amount
|
||||
* objects behave differently based on the currency, so you may get incorrect
|
||||
* results.
|
||||
*
|
||||
* Prices involving demurraging currencies are tricky, since they depend on the
|
||||
* base and counter currencies.
|
||||
*
|
||||
* @param {String} quality 8 hex bytes quality or 32 hex bytes BookDirectory
|
||||
* index.
|
||||
* @param {Currency|String} counterCurrency currency of the resulting Amount
|
||||
* object.
|
||||
* @param {Issuer|String} counterIssuer Issuer of the resulting Amount object.
|
||||
* @param {Object} opts Additional options
|
||||
* @param {Boolean} opts.inverse If true, return the inverse of the price
|
||||
* encoded in the quality.
|
||||
* @param {Currency|String} opts.base_currency The other currency. This plays a
|
||||
* role with interest-bearing or demurrage currencies. In that case the
|
||||
* demurrage has to be applied when the quality is decoded, otherwise the
|
||||
* price will be false.
|
||||
* @param {Date|Number} opts.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @param {Boolean} opts.xrp_as_drops Whether XRP amount should be treated as
|
||||
* drops. When the base currency is XRP, the quality is calculated in drops.
|
||||
* For human use however, we want to think of 1000000 drops as 1 XRP and
|
||||
* prices as per-XRP instead of per-drop.
|
||||
* @return {Amount} self
|
||||
*/
|
||||
Amount.prototype.parse_quality =
|
||||
function(quality, counterCurrency, counterIssuer, opts) {
|
||||
const options = opts || {};
|
||||
|
||||
const baseCurrency = Currency.from_json(options.base_currency);
|
||||
|
||||
const mantissa_hex = quality.substring(quality.length - 14);
|
||||
const offset_hex = quality.substring(
|
||||
quality.length - 16, quality.length - 14);
|
||||
const mantissa = new IOUValue(mantissa_hex, null, 16);
|
||||
const offset = parseInt(offset_hex, 16) - 100;
|
||||
|
||||
this._currency = Currency.from_json(counterCurrency);
|
||||
this._issuer = UInt160.from_json(counterIssuer);
|
||||
this._is_native = this._currency.is_native();
|
||||
|
||||
if (this._is_native && baseCurrency.is_native()) {
|
||||
throw new Error('XRP/XRP quality is not allowed');
|
||||
}
|
||||
|
||||
/*
|
||||
The quality, as stored in the last 64 bits of a directory index, is stored as
|
||||
the quotient of TakerPays/TakerGets.
|
||||
|
||||
When `opts.inverse` is true we are looking at a quality used for determining a
|
||||
`bid` price and it must first be inverted, before our declared base/counter
|
||||
currencies are in line with the price.
|
||||
|
||||
For example:
|
||||
|
||||
quality as stored : 5 USD / 3000000 drops
|
||||
inverted : 3000000 drops / 5 USD
|
||||
*/
|
||||
const valueStr = mantissa.toString() + 'e' + offset.toString();
|
||||
let nativeAdjusted = new IOUValue(valueStr);
|
||||
nativeAdjusted = options.inverse ? nativeAdjusted.invert() : nativeAdjusted;
|
||||
|
||||
if (!options.xrp_as_drops) {
|
||||
// `In a currency exchange, the exchange rate is quoted as the units of the
|
||||
// counter currency in terms of a single unit of a base currency`. A
|
||||
// quality is how much taker must `pay` to get ONE `gets` unit thus:
|
||||
// pays ~= counterCurrency
|
||||
// gets ~= baseCurrency.
|
||||
if (this._is_native) {
|
||||
// pay:$price drops get:1 X
|
||||
// pay:($price / 1,000,000) XRP get:1 X
|
||||
nativeAdjusted = nativeAdjusted.divide(bi_xns_unit);
|
||||
} else if (baseCurrency.is_valid() && baseCurrency.is_native()) {
|
||||
// pay:$price X get:1 drop
|
||||
// pay:($price * 1,000,000) X get:1 XRP
|
||||
nativeAdjusted = nativeAdjusted.multiply(bi_xns_unit);
|
||||
}
|
||||
}
|
||||
if (this._is_native) {
|
||||
this._set_value(
|
||||
new XRPValue(nativeAdjusted.round(6, Value.getBNRoundDown()).toString()));
|
||||
} else {
|
||||
this._set_value(nativeAdjusted);
|
||||
}
|
||||
|
||||
if (options.reference_date && baseCurrency.is_valid()
|
||||
&& baseCurrency.has_interest()) {
|
||||
const interest = baseCurrency.get_interest_at(options.reference_date);
|
||||
this._set_value(
|
||||
this._value.divide(new IOUValue(interest.toString())));
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.parse_number = function(n) {
|
||||
this._is_native = false;
|
||||
this._currency = Currency.from_json(1);
|
||||
this._issuer = UInt160.from_json(1);
|
||||
this._set_value(new IOUValue(n));
|
||||
return this;
|
||||
};
|
||||
|
||||
// <-> j
|
||||
Amount.prototype.parse_json = function(j) {
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
// .../.../... notation is not a wire format. But allowed for easier
|
||||
// testing.
|
||||
const m = j.match(/^([^/]+)\/([^/]+)(?:\/(.+))?$/);
|
||||
|
||||
if (m) {
|
||||
this._currency = Currency.from_json(m[2]);
|
||||
if (m[3]) {
|
||||
this._issuer = UInt160.from_json(m[3]);
|
||||
} else {
|
||||
this._issuer = UInt160.from_json('1');
|
||||
}
|
||||
this.parse_value(m[1]);
|
||||
} else {
|
||||
this.parse_native(j);
|
||||
this._currency = Currency.from_json('0');
|
||||
this._issuer = UInt160.from_json('0');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
this.parse_json(String(j));
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (j === null) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (j instanceof Amount) {
|
||||
j.copyTo(this);
|
||||
} else if (j.hasOwnProperty('value')) {
|
||||
// Parse the passed value to sanitize and copy it.
|
||||
this._currency.parse_json(j.currency, true); // Never XRP.
|
||||
|
||||
if (typeof j.issuer === 'string') {
|
||||
this._issuer.parse_json(j.issuer);
|
||||
}
|
||||
|
||||
this.parse_value(j.value);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
this._set_value(new IOUValue(NaN));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Parse a XRP value from untrusted input.
|
||||
// - integer = raw units
|
||||
// - float = with precision 6
|
||||
// XXX Improvements: disallow leading zeros.
|
||||
Amount.prototype.parse_native = function(j) {
|
||||
if (j && typeof j === 'string' && !isNaN(j)) {
|
||||
if (j.indexOf('.') >= 0) {
|
||||
throw new Error('Native amounts must be specified in integer drops');
|
||||
}
|
||||
const value = new XRPValue(j);
|
||||
this._is_native = true;
|
||||
this._set_value(value.divide(bi_xns_unit));
|
||||
} else {
|
||||
this._set_value(new IOUValue(NaN));
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
// Parse a non-native value for the json wire format.
|
||||
// Requires _currency to be set!
|
||||
Amount.prototype.parse_value = function(j) {
|
||||
this._is_native = false;
|
||||
const newValue = new IOUValue(j, Value.getBNRoundDown());
|
||||
this._set_value(newValue);
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.set_currency = function(c) {
|
||||
this._currency = Currency.from_json(c);
|
||||
this._is_native = this._currency.is_native();
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.set_issuer = function(issuer) {
|
||||
if (issuer instanceof UInt160) {
|
||||
this._issuer = issuer;
|
||||
} else {
|
||||
this._issuer = UInt160.from_json(issuer);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
Amount.prototype.to_number = function() {
|
||||
return Number(this.to_text());
|
||||
};
|
||||
|
||||
// Convert only value to JSON wire format.
|
||||
Amount.prototype.to_text = function() {
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
}
|
||||
|
||||
if (this._is_native) {
|
||||
return this._value.multiply(bi_xns_unit).toString();
|
||||
}
|
||||
|
||||
// not native
|
||||
const offset = this._value.getExponent() - 15;
|
||||
const sign = this._value.isNegative() ? '-' : '';
|
||||
const mantissa = utils.getMantissa16FromString(
|
||||
this._value.abs().toString());
|
||||
if (offset !== 0 && (offset < -25 || offset > -4)) {
|
||||
// Use e notation.
|
||||
// XXX Clamp output.
|
||||
return sign + mantissa.toString() + 'e' + offset.toString();
|
||||
}
|
||||
|
||||
const val = '000000000000000000000000000'
|
||||
+ mantissa.toString()
|
||||
+ '00000000000000000000000';
|
||||
const pre = val.substring(0, offset + 43);
|
||||
const post = val.substring(offset + 43);
|
||||
const s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros.
|
||||
const s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros.
|
||||
|
||||
return sign + (s_pre ? s_pre[0] : '0')
|
||||
+ (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculate present value based on currency and a reference date.
|
||||
*
|
||||
* This only affects demurraging and interest-bearing currencies.
|
||||
*
|
||||
* User should not store amount objects after the interest is applied. This is
|
||||
* intended by display functions such as toHuman().
|
||||
*
|
||||
* @param {Date|Number} referenceDate Date based on which demurrage/interest
|
||||
* should be applied. Can be given as JavaScript Date or int for Ripple epoch.
|
||||
* @return {Amount} The amount with interest applied.
|
||||
*/
|
||||
Amount.prototype.applyInterest = function(referenceDate) {
|
||||
if (!this._currency.has_interest()) {
|
||||
return this;
|
||||
}
|
||||
const interest = this._currency.get_interest_at(referenceDate);
|
||||
return this._copy(
|
||||
this._value.multiply(new IOUValue(interest.toString())));
|
||||
};
|
||||
|
||||
/**
|
||||
* Format only value in a human-readable format.
|
||||
*
|
||||
* @example
|
||||
* let pretty = amount.to_human({precision: 2});
|
||||
*
|
||||
* @param {Object} options Options for formatter.
|
||||
* @param {Number} options.precision Max. number of digits after decimal point.
|
||||
* @param {Number} options.min_precision Min. number of digits after dec. point.
|
||||
* @param {Boolean} options.skip_empty_fraction Don't show fraction if it
|
||||
* is zero, even if min_precision is set.
|
||||
* @param {Number} options.max_sig_digits Maximum number of significant digits.
|
||||
* Will cut fractional part, but never integer part.
|
||||
* @param {Boolean|String} options.group_sep Whether to show a separator every n
|
||||
* digits, if a string, that value will be used as the separator. Default: ','
|
||||
* @param {Number} options.group_width How many numbers will be grouped
|
||||
* together, default: 3.
|
||||
* @param {Boolean|String} options.signed Whether negative numbers will have a
|
||||
* prefix. If String, that string will be used as the prefix. Default: '-'
|
||||
* @param {Date|Number} options.reference_date Date based on which
|
||||
* demurrage/interest should be applied. Can be given as JavaScript Date or int
|
||||
* for Ripple epoch.
|
||||
* @return {String} amount string
|
||||
*/
|
||||
Amount.prototype.to_human = function(options) {
|
||||
const opts = options || {};
|
||||
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
}
|
||||
|
||||
/* eslint-disable consistent-this */
|
||||
// Apply demurrage/interest
|
||||
let ref = this;
|
||||
/* eslint-enable consistent-this */
|
||||
|
||||
if (opts.reference_date) {
|
||||
ref = this.applyInterest(opts.reference_date);
|
||||
}
|
||||
|
||||
const isNegative = ref._value.isNegative();
|
||||
const valueString = ref._value.abs().toFixed();
|
||||
const parts = valueString.split('.');
|
||||
let int_part = parts[0];
|
||||
let fraction_part = parts.length === 2 ? parts[1] : '';
|
||||
|
||||
int_part = int_part.replace(/^0*/, '');
|
||||
fraction_part = fraction_part.replace(/0*$/, '');
|
||||
|
||||
if (fraction_part.length || !opts.skip_empty_fraction) {
|
||||
// Enforce the maximum number of decimal digits (precision)
|
||||
if (typeof opts.precision === 'number') {
|
||||
let precision = Math.max(0, opts.precision);
|
||||
precision = Math.min(precision, fraction_part.length);
|
||||
const rounded = Number('0.' + fraction_part).toFixed(precision);
|
||||
|
||||
if (rounded < 1) {
|
||||
fraction_part = rounded.substring(2);
|
||||
} else {
|
||||
int_part = (Number(int_part) + 1).toString();
|
||||
fraction_part = '';
|
||||
}
|
||||
|
||||
while (fraction_part.length < precision) {
|
||||
fraction_part = '0' + fraction_part;
|
||||
}
|
||||
}
|
||||
|
||||
// Limit the number of significant digits (max_sig_digits)
|
||||
if (typeof opts.max_sig_digits === 'number') {
|
||||
// First, we count the significant digits we have.
|
||||
// A zero in the integer part does not count.
|
||||
const int_is_zero = Number(int_part) === 0;
|
||||
let digits = int_is_zero ? 0 : int_part.length;
|
||||
|
||||
// Don't count leading zeros in the fractional part if the integer part is
|
||||
// zero.
|
||||
const sig_frac = int_is_zero
|
||||
? fraction_part.replace(/^0*/, '')
|
||||
: fraction_part;
|
||||
digits += sig_frac.length;
|
||||
|
||||
// Now we calculate where we are compared to the maximum
|
||||
let rounding = digits - opts.max_sig_digits;
|
||||
|
||||
// If we're under the maximum we want to cut no (=0) digits
|
||||
rounding = Math.max(rounding, 0);
|
||||
|
||||
// If we're over the maximum we still only want to cut digits from the
|
||||
// fractional part, not from the integer part.
|
||||
rounding = Math.min(rounding, fraction_part.length);
|
||||
|
||||
// Now we cut `rounding` digits off the right.
|
||||
if (rounding > 0) {
|
||||
fraction_part = fraction_part.slice(0, -rounding);
|
||||
}
|
||||
}
|
||||
|
||||
// Enforce the minimum number of decimal digits (min_precision)
|
||||
if (typeof opts.min_precision === 'number') {
|
||||
opts.min_precision = Math.max(0, opts.min_precision);
|
||||
while (fraction_part.length < opts.min_precision) {
|
||||
fraction_part += '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.group_sep !== false) {
|
||||
const sep = (typeof opts.group_sep === 'string') ? opts.group_sep : ',';
|
||||
const groups = utils.chunkString(int_part, opts.group_width || 3, true);
|
||||
int_part = groups.join(sep);
|
||||
}
|
||||
|
||||
let formatted = '';
|
||||
if (isNegative && opts.signed !== false) {
|
||||
formatted += '-';
|
||||
}
|
||||
|
||||
formatted += int_part.length ? int_part : '0';
|
||||
formatted += fraction_part.length ? '.' + fraction_part : '';
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
Amount.prototype.to_human_full = function(options) {
|
||||
const opts = options || {};
|
||||
const value = this.to_human(opts);
|
||||
const currency = this._currency.to_human();
|
||||
const issuer = this._issuer.to_json(opts);
|
||||
const base = value + '/' + currency;
|
||||
return this.is_native() ? base : (base + '/' + issuer);
|
||||
};
|
||||
|
||||
Amount.prototype.to_json = function() {
|
||||
if (this._is_native) {
|
||||
return this.to_text();
|
||||
}
|
||||
|
||||
const amount_json = {
|
||||
value: this.to_text(),
|
||||
currency: this._currency.has_interest() ?
|
||||
this._currency.to_hex() : this._currency.to_json()
|
||||
};
|
||||
|
||||
if (this._issuer.is_valid()) {
|
||||
amount_json.issuer = this._issuer.to_json();
|
||||
}
|
||||
|
||||
return amount_json;
|
||||
};
|
||||
|
||||
Amount.prototype.to_text_full = function(opts) {
|
||||
if (!this.is_valid()) {
|
||||
return 'NaN';
|
||||
}
|
||||
return this._is_native
|
||||
? this.to_human() + '/XRP'
|
||||
: this.to_text() + '/' + this._currency.to_json()
|
||||
+ '/' + this._issuer.to_json(opts);
|
||||
};
|
||||
|
||||
// For debugging.
|
||||
Amount.prototype.not_equals_why = function(d, ignore_issuer) {
|
||||
if (typeof d === 'string') {
|
||||
return this.not_equals_why(Amount.from_json(d));
|
||||
}
|
||||
if (!(d instanceof Amount)) {
|
||||
return 'Not an Amount';
|
||||
}
|
||||
if (!this.is_valid() || !d.is_valid()) {
|
||||
return 'Invalid amount.';
|
||||
}
|
||||
if (this._is_native !== d._is_native) {
|
||||
return 'Native mismatch.';
|
||||
}
|
||||
|
||||
const type = this._is_native ? 'XRP' : 'Non-XRP';
|
||||
if (!this._value.isZero() && this._value.negate().equals(d._value)) {
|
||||
return type + ' sign differs.';
|
||||
}
|
||||
if (!this._value.equals(d._value)) {
|
||||
return type + ' value differs.';
|
||||
}
|
||||
if (!this._is_native) {
|
||||
if (!this._currency.equals(d._currency)) {
|
||||
return 'Non-XRP currency differs.';
|
||||
}
|
||||
if (!ignore_issuer && !this._issuer.equals(d._issuer)) {
|
||||
return 'Non-XRP issuer differs: '
|
||||
+ d._issuer.to_json()
|
||||
+ '/'
|
||||
+ this._issuer.to_json();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.Amount = Amount;
|
||||
|
||||
// DEPRECATED: Include the corresponding files instead.
|
||||
exports.Currency = Currency;
|
||||
exports.Seed = Seed;
|
||||
exports.UInt160 = UInt160;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
437
src/core/autobridgecalculator.js
Normal file
437
src/core/autobridgecalculator.js
Normal file
@@ -0,0 +1,437 @@
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const assert = require('assert');
|
||||
const UInt160 = require('./uint160').UInt160;
|
||||
const Amount = require('./amount').Amount;
|
||||
const Utils = require('./orderbookutils');
|
||||
|
||||
function assertValidNumber(number, message) {
|
||||
assert(!_.isNull(number) && !isNaN(number), message);
|
||||
}
|
||||
|
||||
function assertValidLegOneOffer(legOneOffer, message) {
|
||||
assert(legOneOffer);
|
||||
assert.strictEqual(typeof legOneOffer, 'object', message);
|
||||
assert.strictEqual(typeof legOneOffer.TakerPays, 'object', message);
|
||||
assertValidNumber(legOneOffer.TakerGets, message);
|
||||
}
|
||||
|
||||
function AutobridgeCalculator(currencyGets, currencyPays,
|
||||
legOneOffers, legTwoOffers, issuerGets, issuerPays) {
|
||||
this._currencyGets = currencyGets;
|
||||
this._currencyPays = currencyPays;
|
||||
this._issuerGets = issuerGets;
|
||||
this._issuerPays = issuerPays;
|
||||
this.legOneOffers = _.cloneDeep(legOneOffers);
|
||||
this.legTwoOffers = _.cloneDeep(legTwoOffers);
|
||||
|
||||
this._ownerFundsLeftover = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates an ordered array of autobridged offers by quality
|
||||
*
|
||||
* @return {Array}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.calculate = function() {
|
||||
let legOnePointer = 0;
|
||||
let legTwoPointer = 0;
|
||||
|
||||
let offersAutobridged = [];
|
||||
|
||||
this.clearOwnerFundsLeftover();
|
||||
|
||||
while (this.legOneOffers[legOnePointer] && this.legTwoOffers[legTwoPointer]) {
|
||||
const legOneOffer = this.legOneOffers[legOnePointer];
|
||||
const legTwoOffer = this.legTwoOffers[legTwoPointer];
|
||||
const leftoverFunds = this.getLeftoverOwnerFunds(legOneOffer.Account);
|
||||
let autobridgedOffer;
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
this.unclampLegOneOwnerFunds(legOneOffer);
|
||||
} else if (!legOneOffer.is_fully_funded && !leftoverFunds.is_zero()) {
|
||||
this.adjustLegOneFundedAmount(legOneOffer);
|
||||
}
|
||||
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
|
||||
|
||||
if (legOneTakerGetsFunded.is_zero()) {
|
||||
legOnePointer++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (legTwoTakerPaysFunded.is_zero()) {
|
||||
legTwoPointer++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (legOneTakerGetsFunded.compareTo(legTwoTakerPaysFunded) > 0) {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithClampedLegOne(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legTwoPointer++;
|
||||
} else if (legTwoTakerPaysFunded.compareTo(legOneTakerGetsFunded) > 0) {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithClampedLegTwo(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legOnePointer++;
|
||||
} else {
|
||||
autobridgedOffer = this.getAutobridgedOfferWithoutClamps(
|
||||
legOneOffer,
|
||||
legTwoOffer
|
||||
);
|
||||
|
||||
legOnePointer++;
|
||||
legTwoPointer++;
|
||||
}
|
||||
|
||||
offersAutobridged.push(autobridgedOffer);
|
||||
}
|
||||
|
||||
return offersAutobridged;
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the output from leg one is greater than the input to leg two.
|
||||
* Therefore, we must effectively clamp leg one output to leg two input.
|
||||
*
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegOne =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets);
|
||||
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer);
|
||||
const autobridgedTakerPays = legTwoTakerPaysFunded.multiply(legOneQuality);
|
||||
|
||||
if (legOneOffer.Account === legTwoOffer.Account) {
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer);
|
||||
const updatedTakerGets = legOneTakerGets.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGets(legOneOffer, updatedTakerGets);
|
||||
|
||||
this.clampLegOneOwnerFunds(legOneOffer);
|
||||
} else {
|
||||
// Update funded amount since leg one offer was not completely consumed
|
||||
const updatedTakerGetsFunded = legOneTakerGetsFunded
|
||||
.subtract(legTwoTakerPaysFunded);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, updatedTakerGetsFunded);
|
||||
}
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the input from leg two is greater than the output to leg one.
|
||||
* Therefore, we must effectively clamp leg two input to leg one output.
|
||||
*
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithClampedLegTwo =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const legOneTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
const legTwoTakerPaysFunded = Utils.getOfferTakerPaysFunded(legTwoOffer);
|
||||
const legTwoQuality = Utils.getOfferQuality(legTwoOffer, this._currencyGets);
|
||||
|
||||
const autobridgedTakerGets = legOneTakerGetsFunded.divide(legTwoQuality);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer);
|
||||
|
||||
// Update funded amount since leg two offer was not completely consumed
|
||||
legTwoOffer.taker_gets_funded = Utils.getOfferTakerGetsFunded(legTwoOffer)
|
||||
.subtract(autobridgedTakerGets)
|
||||
.to_text();
|
||||
legTwoOffer.taker_pays_funded = legTwoTakerPaysFunded
|
||||
.subtract(legOneTakerGetsFunded)
|
||||
.to_text();
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* In this case, the output from leg one and the input to leg two are the same.
|
||||
* We do not need to clamp either.
|
||||
* @param {Object} legOneOffer
|
||||
* @param {Object} legTwoOffer
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getAutobridgedOfferWithoutClamps =
|
||||
function(legOneOffer, legTwoOffer) {
|
||||
const autobridgedTakerGets = Utils.getOfferTakerGetsFunded(legTwoOffer);
|
||||
const autobridgedTakerPays = Utils.getOfferTakerPaysFunded(legOneOffer);
|
||||
|
||||
return this.formatAutobridgedOffer(
|
||||
autobridgedTakerGets,
|
||||
autobridgedTakerPays
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear owner funds leftovers
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.clearOwnerFundsLeftover = function() {
|
||||
this._ownerFundsLeftover = {};
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset owner funds leftovers for an account to 0
|
||||
*
|
||||
* @param {String} account
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.resetOwnerFundsLeftover = function(account) {
|
||||
assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = Utils.normalizeAmount('0');
|
||||
|
||||
return this._ownerFundsLeftover[account];
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieve leftover funds found after clamping leg one by account
|
||||
*
|
||||
* @param {String} account
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.getLeftoverOwnerFunds = function(account) {
|
||||
assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
|
||||
let amount = this._ownerFundsLeftover[account];
|
||||
|
||||
if (!amount) {
|
||||
amount = Utils.normalizeAmount('0');
|
||||
}
|
||||
|
||||
return amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add funds to account's leftover funds
|
||||
*
|
||||
* @param {String} account
|
||||
* @param {Amount} amount
|
||||
*
|
||||
* @return {Amount}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.addLeftoverOwnerFunds =
|
||||
function(account, amount) {
|
||||
assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
assert(amount instanceof Amount, 'Amount is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = this.getLeftoverOwnerFunds(account)
|
||||
.add(amount);
|
||||
|
||||
return this._ownerFundsLeftover[account];
|
||||
};
|
||||
|
||||
/**
|
||||
* Set account's leftover funds
|
||||
*
|
||||
* @param {String} account
|
||||
* @param {Amount} amount
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLeftoverOwnerFunds =
|
||||
function(account, amount) {
|
||||
assert(UInt160.is_valid(account), 'Account is invalid');
|
||||
assert(amount instanceof Amount, 'Amount is invalid');
|
||||
|
||||
this._ownerFundsLeftover[account] = amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Format an autobridged offer and compute synthetic values (e.g. quality)
|
||||
*
|
||||
* @param {Amount} takerGets
|
||||
* @param {Amount} takerPays
|
||||
*
|
||||
* @return {Object}
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.formatAutobridgedOffer =
|
||||
function(takerGets, takerPays) {
|
||||
assert(takerGets instanceof Amount, 'Autobridged taker gets is invalid');
|
||||
assert(takerPays instanceof Amount, 'Autobridged taker pays is invalid');
|
||||
|
||||
const autobridgedOffer = {};
|
||||
const quality = takerPays.divide(takerGets);
|
||||
|
||||
autobridgedOffer.TakerGets = {
|
||||
value: takerGets.to_text(),
|
||||
currency: this._currencyGets.to_hex(),
|
||||
issuer: this._issuerGets
|
||||
};
|
||||
|
||||
autobridgedOffer.TakerPays = {
|
||||
value: takerPays.to_text(),
|
||||
currency: this._currencyPays.to_hex(),
|
||||
issuer: this._issuerPays
|
||||
};
|
||||
|
||||
autobridgedOffer.quality = quality.to_text();
|
||||
|
||||
autobridgedOffer.taker_gets_funded = autobridgedOffer.TakerGets.value;
|
||||
autobridgedOffer.taker_pays_funded = autobridgedOffer.TakerPays.value;
|
||||
|
||||
autobridgedOffer.autobridged = true;
|
||||
|
||||
autobridgedOffer.BookDirectory = Utils.convertOfferQualityToHex(quality);
|
||||
|
||||
return autobridgedOffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove funds clamp on leg one offer. This is necessary when the two offers
|
||||
* are owned by the same account. In this case, it doesn't matter if offer one
|
||||
* is not fully funded. Leg one out goes to leg two in and since its the same
|
||||
* account, an infinite amount can flow.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.unclampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
legOneOffer.initTakerGetsFunded = Utils.getOfferTakerGetsFunded(legOneOffer);
|
||||
|
||||
this.setLegOneTakerGetsFunded(
|
||||
legOneOffer,
|
||||
Utils.getOfferTakerGets(legOneOffer)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Apply clamp back on leg one offer after a round of autobridge calculation
|
||||
* completes. We must reapply clamps that have been removed because we cannot
|
||||
* guarantee that the next offer from leg two will also be from the same
|
||||
* account.
|
||||
*
|
||||
* When we reapply, it could happen that the amount of TakerGets left after
|
||||
* the autobridge calculation is less than the original funded amount. In this
|
||||
* case, we have extra funds we can use towards unfunded offers with worse
|
||||
* quality by the same owner.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.clampLegOneOwnerFunds = function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
|
||||
const takerGets = Utils.getOfferTakerGets(legOneOffer);
|
||||
|
||||
if (takerGets.compareTo(legOneOffer.initTakerGetsFunded) > 0) {
|
||||
// After clamping, TakerGets is still greater than initial funded amount
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneOffer.initTakerGetsFunded);
|
||||
} else {
|
||||
const updatedLeftover = legOneOffer.initTakerGetsFunded.subtract(takerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, takerGets);
|
||||
this.addLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Increase leg one offer funded amount with extra funds found after applying
|
||||
* clamp.
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.adjustLegOneFundedAmount =
|
||||
function(legOneOffer) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(!legOneOffer.is_fully_funded, 'Leg one offer cannot be fully funded');
|
||||
|
||||
const fundedSum = Utils.getOfferTakerGetsFunded(legOneOffer)
|
||||
.add(this.getLeftoverOwnerFunds(legOneOffer.Account));
|
||||
|
||||
if (fundedSum.compareTo(Utils.getOfferTakerGets(legOneOffer)) >= 0) {
|
||||
// There are enough extra funds to fully fund the offer
|
||||
const legOneTakerGets = Utils.getOfferTakerGets(legOneOffer);
|
||||
const updatedLeftover = fundedSum.subtract(legOneTakerGets);
|
||||
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, legOneTakerGets);
|
||||
this.setLeftoverOwnerFunds(legOneOffer.Account, updatedLeftover);
|
||||
} else {
|
||||
// There are not enough extra funds to fully fund the offer
|
||||
this.setLegOneTakerGetsFunded(legOneOffer, fundedSum);
|
||||
this.resetOwnerFundsLeftover(legOneOffer.Account);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set taker gets funded amount for a IOU:XRP offer. Also calculates taker
|
||||
* pays funded using offer quality and updates is_fully_funded flag
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
* @param {Amount} takerGetsFunded
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLegOneTakerGetsFunded =
|
||||
function setLegOneTakerGetsFunded(legOneOffer, takerGetsFunded) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGetsFunded instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
legOneOffer.taker_gets_funded = takerGetsFunded.to_text();
|
||||
legOneOffer.taker_pays_funded = takerGetsFunded
|
||||
.multiply(Utils.getOfferQuality(legOneOffer, this._currencyGets))
|
||||
.to_text();
|
||||
|
||||
if (legOneOffer.taker_gets_funded === legOneOffer.TakerGets.value) {
|
||||
legOneOffer.is_fully_funded = true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set taker gets amount for a IOU:XRP offer. Also calculates taker pays
|
||||
* using offer quality
|
||||
*
|
||||
* @param {Object} legOneOffer - IOU:XRP offer
|
||||
* @param {Amount} takerGets
|
||||
*/
|
||||
|
||||
AutobridgeCalculator.prototype.setLegOneTakerGets =
|
||||
function(legOneOffer, takerGets) {
|
||||
assertValidLegOneOffer(legOneOffer, 'Leg one offer is invalid');
|
||||
assert(takerGets instanceof Amount, 'Taker gets funded is invalid');
|
||||
|
||||
const legOneQuality = Utils.getOfferQuality(legOneOffer, this._currencyGets);
|
||||
|
||||
legOneOffer.TakerGets = takerGets.to_text();
|
||||
legOneOffer.TakerPays = takerGets.multiply(legOneQuality);
|
||||
};
|
||||
|
||||
module.exports = AutobridgeCalculator;
|
||||
154
src/core/base.js
Normal file
154
src/core/base.js
Normal file
@@ -0,0 +1,154 @@
|
||||
'use strict';
|
||||
const _ = require('lodash');
|
||||
const sjcl = require('./utils').sjcl;
|
||||
const utils = require('./utils');
|
||||
const extend = require('extend');
|
||||
const convertBase = require('./baseconverter');
|
||||
|
||||
const Base = {};
|
||||
|
||||
const alphabets = Base.alphabets = {
|
||||
ripple: 'rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz',
|
||||
tipple: 'RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz',
|
||||
bitcoin: '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
||||
};
|
||||
|
||||
extend(Base, {
|
||||
VER_NONE: 1,
|
||||
VER_NODE_PUBLIC: 28,
|
||||
VER_NODE_PRIVATE: 32,
|
||||
VER_ACCOUNT_ID: 0,
|
||||
VER_ACCOUNT_PUBLIC: 35,
|
||||
VER_ACCOUNT_PRIVATE: 34,
|
||||
VER_FAMILY_GENERATOR: 41,
|
||||
VER_FAMILY_SEED: 33
|
||||
});
|
||||
|
||||
function sha256(bytes) {
|
||||
return sjcl.codec.bytes.fromBits(
|
||||
sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes)));
|
||||
}
|
||||
|
||||
function encodeString(alphabet, input) {
|
||||
if (input.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const leadingZeros = _.takeWhile(input, function(d) {
|
||||
return d === 0;
|
||||
});
|
||||
const out = convertBase(input, 256, 58).map(function(digit) {
|
||||
if (digit < 0 || digit >= alphabet.length) {
|
||||
throw new Error('Value ' + digit + ' is out of bounds for encoding');
|
||||
}
|
||||
return alphabet[digit];
|
||||
});
|
||||
const prefix = leadingZeros.map(function() {
|
||||
return alphabet[0];
|
||||
});
|
||||
return prefix.concat(out).join('');
|
||||
}
|
||||
|
||||
function decodeString(indexes, input) {
|
||||
if (input.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const input58 = input.split('').map(function(c) {
|
||||
const charCode = c.charCodeAt(0);
|
||||
if (charCode >= indexes.length || indexes[charCode] === -1) {
|
||||
throw new Error('Character ' + c + ' is not valid for encoding');
|
||||
}
|
||||
return indexes[charCode];
|
||||
});
|
||||
const leadingZeros = _.takeWhile(input58, function(d) {
|
||||
return d === 0;
|
||||
});
|
||||
const out = convertBase(input58, 58, 256);
|
||||
return leadingZeros.concat(out);
|
||||
}
|
||||
|
||||
function Base58(alphabet) {
|
||||
const indexes = utils.arraySet(128, -1);
|
||||
for (let i = 0; i < alphabet.length; i++) {
|
||||
indexes[alphabet.charCodeAt(i)] = i;
|
||||
}
|
||||
return {
|
||||
decode: decodeString.bind(null, indexes),
|
||||
encode: encodeString.bind(null, alphabet)
|
||||
};
|
||||
}
|
||||
|
||||
Base.encoders = {};
|
||||
Object.keys(alphabets).forEach(function(alphabet) {
|
||||
Base.encoders[alphabet] = new Base58(alphabets[alphabet]);
|
||||
});
|
||||
|
||||
// --> input: big-endian array of bytes.
|
||||
// <-- string at least as long as input.
|
||||
Base.encode = function(input, alpha) {
|
||||
return this.encoders[alpha || 'ripple'].encode(input);
|
||||
};
|
||||
|
||||
// --> input: String
|
||||
// <-- array of bytes or undefined.
|
||||
Base.decode = function(input, alpha) {
|
||||
if (typeof input !== 'string') {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
return this.encoders[alpha || 'ripple'].decode(input);
|
||||
} catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
Base.verify_checksum = function(bytes) {
|
||||
const computed = sha256(sha256(bytes.slice(0, -4))).slice(0, 4);
|
||||
const checksum = bytes.slice(-4);
|
||||
return _.isEqual(computed, checksum);
|
||||
};
|
||||
|
||||
// --> input: Array
|
||||
// <-- String
|
||||
Base.encode_check = function(version, input, alphabet) {
|
||||
const buffer = [].concat(version, input);
|
||||
const check = sha256(sha256(buffer)).slice(0, 4);
|
||||
|
||||
return Base.encode([].concat(buffer, check), alphabet);
|
||||
};
|
||||
|
||||
// --> input : String
|
||||
// <-- NaN || sjcl.bn
|
||||
Base.decode_check = function(version, input, alphabet) {
|
||||
const buffer = Base.decode(input, alphabet);
|
||||
|
||||
if (!buffer || buffer.length < 5) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Single valid version
|
||||
if (typeof version === 'number' && buffer[0] !== version) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// Multiple allowed versions
|
||||
if (Array.isArray(version) && _.every(version, function(v) {
|
||||
return v !== buffer[0];
|
||||
})) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
if (!Base.verify_checksum(buffer)) {
|
||||
return NaN;
|
||||
}
|
||||
|
||||
// We'll use the version byte to add a leading zero, this ensures JSBN doesn't
|
||||
// intrepret the value as a negative number
|
||||
buffer[0] = 0;
|
||||
|
||||
return sjcl.bn.fromBits(
|
||||
sjcl.codec.bytes.toBits(buffer.slice(0, -4)));
|
||||
};
|
||||
|
||||
exports.Base = Base;
|
||||
32
src/core/baseconverter.js
Normal file
32
src/core/baseconverter.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict';
|
||||
|
||||
function normalize(digitArray) {
|
||||
while (digitArray[0] === 0) {
|
||||
digitArray.shift();
|
||||
}
|
||||
return digitArray;
|
||||
}
|
||||
|
||||
function divmod(digitArray, base, divisor) {
|
||||
var remainder = 0;
|
||||
var quotient = [];
|
||||
for (var j = 0; j < digitArray.length; j++) {
|
||||
var temp = remainder * base + parseInt(digitArray[j], 10);
|
||||
quotient.push(Math.floor(temp / divisor));
|
||||
remainder = temp % divisor;
|
||||
}
|
||||
return {quotient: normalize(quotient), remainder: remainder};
|
||||
}
|
||||
|
||||
function convertBase(digitArray, fromBase, toBase) {
|
||||
var result = [];
|
||||
var dividend = digitArray;
|
||||
while (dividend.length > 0) {
|
||||
var qr = divmod(dividend, fromBase, toBase);
|
||||
result.unshift(qr.remainder);
|
||||
dividend = qr.quotient;
|
||||
}
|
||||
return normalize(result);
|
||||
}
|
||||
|
||||
module.exports = convertBase;
|
||||
@@ -1,10 +1,19 @@
|
||||
'use strict';
|
||||
|
||||
/*eslint no-multi-spaces:0,space-in-brackets:0,key-spacing:0,comma-spacing:0*/
|
||||
|
||||
/**
|
||||
* Data type map.
|
||||
*
|
||||
* Mapping of type ids to data types. The type id is specified by the high
|
||||
*
|
||||
* For reference, see rippled's definition:
|
||||
* https://github.com/ripple/rippled/blob/develop/src/ripple/data/protocol
|
||||
* /SField.cpp
|
||||
*/
|
||||
var TYPES_MAP = exports.types = [
|
||||
void(0),
|
||||
|
||||
exports.types = [
|
||||
undefined,
|
||||
|
||||
// Common
|
||||
'Int16', // 1
|
||||
@@ -17,11 +26,11 @@ var TYPES_MAP = exports.types = [
|
||||
'Account', // 8
|
||||
|
||||
// 9-13 reserved
|
||||
void(0), // 9
|
||||
void(0), // 10
|
||||
void(0), // 11
|
||||
void(0), // 12
|
||||
void(0), // 13
|
||||
undefined, // 9
|
||||
undefined, // 10
|
||||
undefined, // 11
|
||||
undefined, // 12
|
||||
undefined, // 13
|
||||
|
||||
'Object', // 14
|
||||
'Array', // 15
|
||||
@@ -39,7 +48,7 @@ var TYPES_MAP = exports.types = [
|
||||
* Mapping of field type id to field type name.
|
||||
*/
|
||||
|
||||
var FIELDS_MAP = exports.fields = {
|
||||
const FIELDS_MAP = exports.fields = {
|
||||
// Common types
|
||||
1: { // Int16
|
||||
1: 'LedgerEntryType',
|
||||
@@ -106,7 +115,8 @@ var FIELDS_MAP = exports.fields = {
|
||||
16: 'BookDirectory',
|
||||
17: 'InvoiceID',
|
||||
18: 'Nickname',
|
||||
19: 'Feature'
|
||||
19: 'Amendment',
|
||||
20: 'TicketID'
|
||||
},
|
||||
6: { // Amount
|
||||
1: 'Amount',
|
||||
@@ -135,7 +145,8 @@ var FIELDS_MAP = exports.fields = {
|
||||
10: 'ExpireCode',
|
||||
11: 'CreateCode',
|
||||
12: 'MemoType',
|
||||
13: 'MemoData'
|
||||
13: 'MemoData',
|
||||
14: 'MemoFormat'
|
||||
},
|
||||
8: { // Account
|
||||
1: 'Account',
|
||||
@@ -146,7 +157,7 @@ var FIELDS_MAP = exports.fields = {
|
||||
8: 'RegularKey'
|
||||
},
|
||||
14: { // Object
|
||||
1: void(0), //end of Object
|
||||
1: undefined, // end of Object
|
||||
2: 'TransactionMetaData',
|
||||
3: 'CreatedNode',
|
||||
4: 'DeletedNode',
|
||||
@@ -158,7 +169,7 @@ var FIELDS_MAP = exports.fields = {
|
||||
10: 'Memo'
|
||||
},
|
||||
15: { // Array
|
||||
1: void(0), //end of Array
|
||||
1: undefined, // end of Array
|
||||
2: 'SigningAccounts',
|
||||
3: 'TxnSignatures',
|
||||
4: 'Signatures',
|
||||
@@ -187,11 +198,11 @@ var FIELDS_MAP = exports.fields = {
|
||||
19: { // Vector256
|
||||
1: 'Indexes',
|
||||
2: 'Hashes',
|
||||
3: 'Features'
|
||||
3: 'Amendments'
|
||||
}
|
||||
};
|
||||
|
||||
var INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
|
||||
let INVERSE_FIELDS_MAP = exports.fieldsInverseMap = { };
|
||||
|
||||
Object.keys(FIELDS_MAP).forEach(function(k1) {
|
||||
Object.keys(FIELDS_MAP[k1]).forEach(function(k2) {
|
||||
@@ -199,12 +210,11 @@ Object.keys(FIELDS_MAP).forEach(function(k1) {
|
||||
});
|
||||
});
|
||||
|
||||
const REQUIRED = exports.REQUIRED = 0,
|
||||
OPTIONAL = exports.OPTIONAL = 1,
|
||||
DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
var REQUIRED = exports.REQUIRED = 0,
|
||||
OPTIONAL = exports.OPTIONAL = 1,
|
||||
DEFAULT = exports.DEFAULT = 2;
|
||||
|
||||
var base = [
|
||||
const base = [
|
||||
[ 'TransactionType' , REQUIRED ],
|
||||
[ 'Flags' , OPTIONAL ],
|
||||
[ 'SourceTag' , OPTIONAL ],
|
||||
@@ -214,7 +224,9 @@ var base = [
|
||||
[ 'Fee' , REQUIRED ],
|
||||
[ 'OperationLimit' , OPTIONAL ],
|
||||
[ 'SigningPubKey' , REQUIRED ],
|
||||
[ 'TxnSignature' , OPTIONAL ]
|
||||
[ 'TxnSignature' , OPTIONAL ],
|
||||
[ 'AccountTxnID' , OPTIONAL ],
|
||||
[ 'Memos' , OPTIONAL ]
|
||||
];
|
||||
|
||||
exports.tx = {
|
||||
@@ -224,7 +236,9 @@ exports.tx = {
|
||||
[ 'WalletSize' , OPTIONAL ],
|
||||
[ 'MessageKey' , OPTIONAL ],
|
||||
[ 'Domain' , OPTIONAL ],
|
||||
[ 'TransferRate' , OPTIONAL ]
|
||||
[ 'TransferRate' , OPTIONAL ],
|
||||
[ 'SetFlag' , OPTIONAL ],
|
||||
[ 'ClearFlag' , OPTIONAL ]
|
||||
]),
|
||||
TrustSet: [20].concat(base, [
|
||||
[ 'LimitAmount' , OPTIONAL ],
|
||||
@@ -234,13 +248,14 @@ exports.tx = {
|
||||
OfferCreate: [7].concat(base, [
|
||||
[ 'TakerPays' , REQUIRED ],
|
||||
[ 'TakerGets' , REQUIRED ],
|
||||
[ 'Expiration' , OPTIONAL ]
|
||||
[ 'Expiration' , OPTIONAL ],
|
||||
[ 'OfferSequence' , OPTIONAL ]
|
||||
]),
|
||||
OfferCancel: [8].concat(base, [
|
||||
[ 'OfferSequence' , REQUIRED ]
|
||||
]),
|
||||
SetRegularKey: [5].concat(base, [
|
||||
[ 'RegularKey' , REQUIRED ]
|
||||
[ 'RegularKey' , OPTIONAL ]
|
||||
]),
|
||||
Payment: [0].concat(base, [
|
||||
[ 'Destination' , REQUIRED ],
|
||||
@@ -266,16 +281,25 @@ exports.tx = {
|
||||
EnableFeature: [100].concat(base, [
|
||||
[ 'Feature' , REQUIRED ]
|
||||
]),
|
||||
EnableAmendment: [100].concat(base, [
|
||||
[ 'Amendment' , REQUIRED ]
|
||||
]),
|
||||
SetFee: [101].concat(base, [
|
||||
[ 'Features' , REQUIRED ],
|
||||
[ 'BaseFee' , REQUIRED ],
|
||||
[ 'ReferenceFeeUnits' , REQUIRED ],
|
||||
[ 'ReserveBase' , REQUIRED ],
|
||||
[ 'ReserveIncrement' , REQUIRED ]
|
||||
]),
|
||||
TicketCreate: [10].concat(base, [
|
||||
[ 'Target' , OPTIONAL ],
|
||||
[ 'Expiration' , OPTIONAL ]
|
||||
]),
|
||||
TicketCancel: [11].concat(base, [
|
||||
[ 'TicketID' , REQUIRED ]
|
||||
])
|
||||
};
|
||||
|
||||
var sleBase = [
|
||||
const sleBase = [
|
||||
['LedgerIndex', OPTIONAL],
|
||||
['LedgerEntryType', REQUIRED],
|
||||
['Flags', REQUIRED]
|
||||
@@ -373,7 +397,7 @@ exports.ledger = {
|
||||
['Balance', REQUIRED],
|
||||
['LowLimit', REQUIRED],
|
||||
['HighLimit', REQUIRED]])
|
||||
}
|
||||
};
|
||||
|
||||
exports.metadata = [
|
||||
[ 'TransactionIndex' , REQUIRED ],
|
||||
@@ -409,5 +433,8 @@ exports.ter = {
|
||||
tecNO_TARGET : 138,
|
||||
tecNO_PERMISSION : 139,
|
||||
tecNO_ENTRY : 140,
|
||||
tecINSUFFICIENT_RESERVE : 141
|
||||
tecINSUFFICIENT_RESERVE : 141,
|
||||
tecNEED_MASTER_KEY : 142,
|
||||
tecDST_TAG_NEEDED : 143,
|
||||
tecINTERNAL : 144
|
||||
};
|
||||
@@ -1,4 +1,6 @@
|
||||
var extend = require('extend');
|
||||
'use strict';
|
||||
|
||||
var extend = require('extend');
|
||||
var UInt160 = require('./uint160').UInt160;
|
||||
var utils = require('./utils');
|
||||
var Float = require('./ieee754').Float;
|
||||
@@ -16,8 +18,7 @@ var Currency = extend(function() {
|
||||
// 3-letter code: ...
|
||||
// XXX Should support hex, C++ doesn't currently allow it.
|
||||
|
||||
this._value = NaN;
|
||||
|
||||
this._value = NaN;
|
||||
this._update();
|
||||
}, UInt160);
|
||||
|
||||
@@ -32,25 +33,37 @@ Currency.HEX_CURRENCY_BAD = '0000000000000000000000005852500000000000';
|
||||
* Examples:
|
||||
*
|
||||
* USD => currency
|
||||
* USD - Dollar => currency with optional full currency name
|
||||
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate per year
|
||||
* USD - Dollar => currency with optional full currency
|
||||
* name
|
||||
* XAU (-0.5%pa) => XAU with 0.5% effective demurrage rate
|
||||
* per year
|
||||
* XAU - Gold (-0.5%pa) => Optionally allowed full currency name
|
||||
* USD (1%pa) => US dollars with 1% effective interest per year
|
||||
* USD (1%pa) => US dollars with 1% effective interest
|
||||
* per year
|
||||
* INR - Indian Rupees => Optional full currency name with spaces
|
||||
* TYX - 30-Year Treasuries => Optional full currency with numbers and a dash
|
||||
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers, dash and interest rate
|
||||
* TYX - 30-Year Treasuries => Optional full currency with numbers
|
||||
* and a dash
|
||||
* TYX - 30-Year Treasuries (1.5%pa) => Optional full currency with numbers,
|
||||
* dash and interest rate
|
||||
*
|
||||
* The regular expression below matches above cases, broken down for better understanding:
|
||||
* The regular expression below matches above cases, broken down for better
|
||||
* understanding:
|
||||
*
|
||||
* ^\s* // start with any amount of whitespace
|
||||
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217
|
||||
* (\s*-\s*[- \w]+) // optional full currency name following the dash after currency code,
|
||||
* full currency code can contain letters, numbers and dashes
|
||||
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and . notation (-0.5%pa)
|
||||
* ([a-zA-Z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3
|
||||
* digit numeric currency-code. See ISO 4217
|
||||
* (\s*-\s*[- \w]+) // optional full currency name following the dash
|
||||
* after currency code, full currency code can
|
||||
* contain letters, numbers and dashes
|
||||
* (\s*\(-?\d+\.?\d*%pa\))? // optional demurrage rate, has optional - and
|
||||
* . notation (-0.5%pa)
|
||||
* \s*$ // end with any amount of whitespace
|
||||
*
|
||||
*/
|
||||
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
|
||||
|
||||
/*eslint-disable max-len*/
|
||||
Currency.prototype.human_RE = /^\s*([a-zA-Z0-9\<\>\(\)\{\}\[\]\|\?\!\@\#\$\%\^\&]{3})(\s*-\s*[- \w]+)?(\s*\(-?\d+\.?\d*%pa\))?\s*$/;
|
||||
/*eslint-enable max-len*/
|
||||
|
||||
Currency.from_json = function(j, shouldInterpretXrpAsIou) {
|
||||
return (new Currency()).parse_json(j, shouldInterpretXrpAsIou);
|
||||
@@ -58,39 +71,65 @@ Currency.from_json = function(j, shouldInterpretXrpAsIou) {
|
||||
|
||||
Currency.from_human = function(j, opts) {
|
||||
return (new Currency().parse_human(j, opts));
|
||||
}
|
||||
};
|
||||
|
||||
// this._value = NaN on error.
|
||||
Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
this._value = NaN;
|
||||
|
||||
switch (typeof j) {
|
||||
case 'string':
|
||||
if (j instanceof Currency) {
|
||||
this._value = j.copyTo({})._value;
|
||||
this._update();
|
||||
return this;
|
||||
}
|
||||
|
||||
// if an empty string is given, fall back to XRP
|
||||
switch (typeof j) {
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
case 'string':
|
||||
if (!j || j === '0') {
|
||||
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
|
||||
// Empty string or XRP
|
||||
this.parse_hex(shouldInterpretXrpAsIou
|
||||
? Currency.HEX_CURRENCY_BAD
|
||||
: Currency.HEX_ZERO);
|
||||
break;
|
||||
}
|
||||
|
||||
if (j === '1') {
|
||||
// 'no currency'
|
||||
this.parse_hex(Currency.HEX_ONE);
|
||||
break;
|
||||
}
|
||||
|
||||
if (/^[A-F0-9]{40}$/.test(j)) {
|
||||
// Hex format
|
||||
this.parse_hex(j);
|
||||
break;
|
||||
}
|
||||
|
||||
// match the given string to see if it's in an allowed format
|
||||
var matches = String(j).match(this.human_RE);
|
||||
var matches = j.match(this.human_RE);
|
||||
|
||||
if (matches) {
|
||||
|
||||
var currencyCode = matches[1];
|
||||
|
||||
// for the currency 'XRP' case
|
||||
// we drop everything else that could have been provided
|
||||
// e.g. 'XRP - Ripple'
|
||||
if (!currencyCode || /^(0|XRP)$/.test(currencyCode)) {
|
||||
this.parse_hex(shouldInterpretXrpAsIou ? Currency.HEX_CURRENCY_BAD : Currency.HEX_ZERO);
|
||||
this.parse_hex(shouldInterpretXrpAsIou
|
||||
? Currency.HEX_CURRENCY_BAD
|
||||
: Currency.HEX_ZERO);
|
||||
|
||||
// early break, we can't have interest on XRP
|
||||
break;
|
||||
}
|
||||
|
||||
// the full currency is matched as it is part of the valid currency format, but not stored
|
||||
// the full currency is matched as it is part of the valid currency
|
||||
// format, but not stored
|
||||
// var full_currency = matches[2] || '';
|
||||
var interest = matches[3] || '';
|
||||
|
||||
@@ -117,25 +156,28 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
currencyData[2] = currencyCode.charCodeAt(1) & 0xff;
|
||||
currencyData[3] = currencyCode.charCodeAt(2) & 0xff;
|
||||
|
||||
// byte 5-8 are for reference date, but should always be 0 so we won't fill it
|
||||
// byte 5-8 are for reference date, but should always be 0 so we
|
||||
// won't fill it
|
||||
|
||||
// byte 9-16 are for the interest
|
||||
percentage = parseFloat(percentage[0]);
|
||||
|
||||
// the interest or demurrage is expressed as a yearly (per annum) value
|
||||
// the interest or demurrage is expressed as a yearly (per annum)
|
||||
// value
|
||||
var secondsPerYear = 31536000; // 60 * 60 * 24 * 365
|
||||
|
||||
// Calculating the interest e-fold
|
||||
// 0.5% demurrage is expressed 0.995, 0.005 less than 1
|
||||
// 0.5% interest is expressed as 1.005, 0.005 more than 1
|
||||
var interestEfold = secondsPerYear / Math.log(1 + percentage/100);
|
||||
var interestEfold = secondsPerYear / Math.log(1 + percentage / 100);
|
||||
var bytes = Float.toIEEE754Double(interestEfold);
|
||||
|
||||
for (var i=0; i<=bytes.length; i++) {
|
||||
for (var i = 0; i <= bytes.length; i++) {
|
||||
currencyData[8 + i] = bytes[i] & 0xff;
|
||||
}
|
||||
|
||||
// the last 4 bytes are reserved for future use, so we won't fill those
|
||||
// the last 4 bytes are reserved for future use, so we won't fill
|
||||
// those
|
||||
|
||||
} else {
|
||||
currencyData[12] = currencyCode.charCodeAt(0) & 0xff;
|
||||
@@ -144,21 +186,6 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
}
|
||||
|
||||
this.parse_bytes(currencyData);
|
||||
} else {
|
||||
this.parse_hex(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'number':
|
||||
if (!isNaN(j)) {
|
||||
this.parse_number(j);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'object':
|
||||
if (j instanceof Currency) {
|
||||
this._value = j.copyTo({})._value;
|
||||
this._update();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -166,7 +193,6 @@ Currency.prototype.parse_json = function(j, shouldInterpretXrpAsIou) {
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
Currency.prototype.parse_human = function(j) {
|
||||
return this.parse_json(j);
|
||||
};
|
||||
@@ -176,6 +202,7 @@ Currency.prototype.parse_human = function(j) {
|
||||
*
|
||||
* You should never need to call this.
|
||||
*/
|
||||
|
||||
Currency.prototype._update = function() {
|
||||
var bytes = this.to_bytes();
|
||||
|
||||
@@ -183,7 +210,7 @@ Currency.prototype._update = function() {
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
if (!bytes) {
|
||||
return 'XRP';
|
||||
return;
|
||||
}
|
||||
|
||||
this._native = false;
|
||||
@@ -192,8 +219,9 @@ Currency.prototype._update = function() {
|
||||
this._interest_period = NaN;
|
||||
this._iso_code = '';
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || bytes[i]===0);
|
||||
for (var i = 0; i < 20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
|
||||
&& (i === 12 || i === 13 || i === 14 || bytes[i] === 0);
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
@@ -201,7 +229,7 @@ Currency.prototype._update = function() {
|
||||
+ String.fromCharCode(bytes[13])
|
||||
+ String.fromCharCode(bytes[14]);
|
||||
|
||||
if (this._iso_code === '\0\0\0') {
|
||||
if (this._iso_code === '\u0000\u0000\u0000') {
|
||||
this._native = true;
|
||||
this._iso_code = 'XRP';
|
||||
}
|
||||
@@ -215,8 +243,8 @@ Currency.prototype._update = function() {
|
||||
this._type = 1;
|
||||
this._interest_start = (bytes[4] << 24) +
|
||||
(bytes[5] << 16) +
|
||||
(bytes[6] << 8) +
|
||||
(bytes[7] );
|
||||
(bytes[6] << 8) +
|
||||
(bytes[7]);
|
||||
this._interest_period = Float.fromIEEE754Double(bytes.slice(8, 16));
|
||||
}
|
||||
};
|
||||
@@ -230,7 +258,8 @@ Currency.prototype.parse_bytes = function(byte_array) {
|
||||
var isZeroExceptInStandardPositions = true;
|
||||
|
||||
for (var i=0; i<20; i++) {
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions && (i===12 || i===13 || i===14 || byte_array[0]===0)
|
||||
isZeroExceptInStandardPositions = isZeroExceptInStandardPositions
|
||||
&& (i===12 || i===13 || i===14 || byte_array[0]===0)
|
||||
}
|
||||
|
||||
if (isZeroExceptInStandardPositions) {
|
||||
@@ -260,20 +289,25 @@ Currency.prototype.is_native = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether this currency is an interest-bearing/demurring currency.
|
||||
* @return {Boolean} whether this currency is an interest-bearing currency
|
||||
*/
|
||||
|
||||
Currency.prototype.has_interest = function() {
|
||||
return this._type === 1 && !isNaN(this._interest_start) && !isNaN(this._interest_period);
|
||||
return this._type === 1
|
||||
&& !isNaN(this._interest_start)
|
||||
&& !isNaN(this._interest_period);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param referenceDate - number of seconds since the Ripple Epoch (0:00 on January 1, 2000 UTC)
|
||||
* used to calculate the interest over provided interval
|
||||
* pass in one years worth of seconds to ge the yearly interest
|
||||
* @returns {number} - interest for provided interval, can be negative for demurred currencies
|
||||
* @param {number} referenceDate number of seconds since the Ripple Epoch
|
||||
* (0:00 on January 1, 2000 UTC) used to calculate the
|
||||
* interest over provided interval pass in one years
|
||||
* worth of seconds to ge the yearly interest
|
||||
* @returns {number} interest for provided interval, can be negative for
|
||||
* demurred currencies
|
||||
*/
|
||||
Currency.prototype.get_interest_at = function(referenceDate, decimals) {
|
||||
Currency.prototype.get_interest_at = function(referenceDate) {
|
||||
if (!this.has_interest()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -288,18 +322,20 @@ Currency.prototype.get_interest_at = function(referenceDate, decimals) {
|
||||
}
|
||||
|
||||
// calculate interest by e-fold number
|
||||
return Math.exp((referenceDate - this._interest_start) / this._interest_period);
|
||||
return Math.exp((referenceDate - this._interest_start)
|
||||
/ this._interest_period);
|
||||
};
|
||||
|
||||
Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals) {
|
||||
Currency.prototype.get_interest_percentage_at
|
||||
= function(referenceDate, decimals) {
|
||||
var interest = this.get_interest_at(referenceDate, decimals);
|
||||
|
||||
// convert to percentage
|
||||
var interest = (interest*100)-100;
|
||||
var decimalMultiplier = decimals ? Math.pow(10,decimals) : 100;
|
||||
interest = (interest * 100) - 100;
|
||||
var decimalMultiplier = decimals ? Math.pow(10, decimals) : 100;
|
||||
|
||||
// round to two decimals behind the dot
|
||||
return Math.round(interest*decimalMultiplier) / decimalMultiplier;
|
||||
return Math.round(interest * decimalMultiplier) / decimalMultiplier;
|
||||
};
|
||||
|
||||
// XXX Currently we inherit UInt.prototype.is_valid, which is mostly fine.
|
||||
@@ -307,9 +343,9 @@ Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals
|
||||
// We could be doing further checks into the internal format of the
|
||||
// currency data, since there are some values that are invalid.
|
||||
//
|
||||
//Currency.prototype.is_valid = function() {
|
||||
// return this._value instanceof BigInteger && ...;
|
||||
//};
|
||||
// Currency.prototype.is_valid = function() {
|
||||
// return UInt.prototype.is_valid() && ...;
|
||||
// };
|
||||
|
||||
Currency.prototype.to_json = function(opts) {
|
||||
if (!this.is_valid()) {
|
||||
@@ -317,28 +353,35 @@ Currency.prototype.to_json = function(opts) {
|
||||
return 'XRP';
|
||||
}
|
||||
|
||||
var opts = opts || {};
|
||||
if (!opts) {
|
||||
opts = {};
|
||||
}
|
||||
|
||||
var currency;
|
||||
var fullName = opts && opts.full_name ? ' - ' + opts.full_name : '';
|
||||
opts.show_interest = opts.show_interest !== void(0) ? opts.show_interest : this.has_interest();
|
||||
opts.show_interest = opts.show_interest !== undefined
|
||||
? opts.show_interest
|
||||
: this.has_interest();
|
||||
|
||||
if (!opts.force_hex && /^[A-Z0-9]{3}$/.test(this._iso_code)) {
|
||||
currency = this._iso_code + fullName;
|
||||
if (opts.show_interest) {
|
||||
var decimals = !isNaN(opts.decimals) ? opts.decimals : void(0);
|
||||
var interestPercentage = this.has_interest() ? this.get_interest_percentage_at(this._interest_start + 3600 * 24 * 365, decimals) : 0;
|
||||
var decimals = !isNaN(opts.decimals) ? opts.decimals : undefined;
|
||||
var interestPercentage = this.has_interest()
|
||||
? this.get_interest_percentage_at(
|
||||
this._interest_start + 3600 * 24 * 365, decimals
|
||||
)
|
||||
: 0;
|
||||
currency += ' (' + interestPercentage + '%pa)';
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// Fallback to returning the raw currency hex
|
||||
currency = this.to_hex();
|
||||
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very odd
|
||||
// behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
// XXX This is to maintain backwards compatibility, but it is very, very
|
||||
// odd behavior, so we should deprecate it and get rid of it as soon as
|
||||
// possible.
|
||||
if (currency === Currency.HEX_ONE) {
|
||||
currency = 1;
|
||||
}
|
||||
@@ -357,5 +400,3 @@ Currency.prototype.get_iso = function() {
|
||||
};
|
||||
|
||||
exports.Currency = Currency;
|
||||
|
||||
// vim:sw=2:sts=2:ts=8:et
|
||||
@@ -1,3 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
// TODO: move in helpers from serializedtypes to utils
|
||||
function toBytes(n) {
|
||||
return [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff];
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefix for hashing functions.
|
||||
*
|
||||
@@ -12,14 +19,18 @@
|
||||
*/
|
||||
|
||||
// transaction plus signature to give transaction ID
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
||||
// transaction plus metadata
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
||||
// inner node in tree
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
||||
// leaf node in tree
|
||||
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
|
||||
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
|
||||
// inner transaction to sign
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
||||
// inner transaction to sign (TESTNET)
|
||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
||||
|
||||
Object.keys(exports).forEach(function(k) {
|
||||
exports[k + '_BYTES'] = toBytes(exports[k]);
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user