Compare commits
699 Commits
v0.11.0
...
@0xproject
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3cc283478 | ||
|
|
9a2735d035 | ||
|
|
06cd2f1eb3 | ||
|
|
41315827c1 | ||
|
|
415fd101d3 | ||
|
|
0747e162fa | ||
|
|
8d6ba6ee7a | ||
|
|
173a707a2e | ||
|
|
c8c95b4bd2 | ||
|
|
037f466e1f | ||
|
|
98394c6e37 | ||
|
|
37a9b64503 | ||
|
|
0de00da753 | ||
|
|
a4ae3c1e14 | ||
|
|
7f595169c1 | ||
|
|
abee7d25a4 | ||
|
|
49ba456189 | ||
|
|
6a83687f45 | ||
|
|
70f4453e3e | ||
|
|
cd0f6716e8 | ||
|
|
5042b85d21 | ||
|
|
5015f2d7d7 | ||
|
|
5277d4a266 | ||
|
|
f25b2d9ab9 | ||
|
|
0402d3de80 | ||
|
|
e70c3976db | ||
|
|
2c055db0d7 | ||
|
|
50dc1d3db3 | ||
|
|
2e368b50eb | ||
|
|
7b61ad639b | ||
|
|
741774c4a7 | ||
|
|
33c1259fa2 | ||
|
|
9ee04fb14c | ||
|
|
e9a5ae21e0 | ||
|
|
0bcf7e56c2 | ||
|
|
141f185c72 | ||
|
|
e1b2c64654 | ||
|
|
d7e5c6f9b5 | ||
|
|
1e5cc3b0e5 | ||
|
|
85c3b2996d | ||
|
|
9a57f71ee6 | ||
|
|
827c245777 | ||
|
|
6096fe75d3 | ||
|
|
a9fbe921a0 | ||
|
|
ee8042b458 | ||
|
|
1e1b14edc5 | ||
|
|
4c505b6470 | ||
|
|
471abfa760 | ||
|
|
557faba31b | ||
|
|
2411749594 | ||
|
|
4e39a957c7 | ||
|
|
0cf719a744 | ||
|
|
fe7ad22cc1 | ||
|
|
59f82c5bfb | ||
|
|
a2b8933129 | ||
|
|
031b07264a | ||
|
|
87f8e93e12 | ||
|
|
5b5c31861a | ||
|
|
c315ca6c0c | ||
|
|
655b0636fa | ||
|
|
5bd8e172c9 | ||
|
|
ebb35fd65e | ||
|
|
df7195bda4 | ||
|
|
4bd950bb6f | ||
|
|
3f39b22a68 | ||
|
|
c019280e85 | ||
|
|
ff0b0ae1ab | ||
|
|
823015435d | ||
|
|
99854d7ecf | ||
|
|
bb7d9656a5 | ||
|
|
e41994a064 | ||
|
|
79df9ef8e6 | ||
|
|
3b52686125 | ||
|
|
af7c03212d | ||
|
|
bdeedb6c91 | ||
|
|
4d61d56639 | ||
|
|
430154d543 | ||
|
|
bb6631c7c6 | ||
|
|
9bb5e6f5ce | ||
|
|
05de07496f | ||
|
|
ab9dc66b8f | ||
|
|
f98042a7f8 | ||
|
|
247b4686fa | ||
|
|
c78cb27175 | ||
|
|
752603284d | ||
|
|
dcfe8bae1c | ||
|
|
8f4be963b2 | ||
|
|
61496d77a5 | ||
|
|
a12069f03f | ||
|
|
56b5619d24 | ||
|
|
bc61b92070 | ||
|
|
682fbd3b76 | ||
|
|
574ea453b0 | ||
|
|
2b806455a5 | ||
|
|
49898525af | ||
|
|
24493a4556 | ||
|
|
8de64c9495 | ||
|
|
02bbcf6b0e | ||
|
|
08963f269b | ||
|
|
2d0fd14d3c | ||
|
|
a15f78652f | ||
|
|
3d62312657 | ||
|
|
9b083eebd7 | ||
|
|
c7e57a4124 | ||
|
|
ee1a44ebeb | ||
|
|
5b70383ce6 | ||
|
|
abb2ad45ce | ||
|
|
087ea1f068 | ||
|
|
33f479c271 | ||
|
|
f936363440 | ||
|
|
315e4015de | ||
|
|
0c91b66f45 | ||
|
|
4354d3f121 | ||
|
|
3e8e3478a3 | ||
|
|
cf29530dd0 | ||
|
|
b0f13c17e2 | ||
|
|
1147cb56ba | ||
|
|
e29c3e4c70 | ||
|
|
b1b473d3cb | ||
|
|
c088d9ddd9 | ||
|
|
2b26981e3d | ||
|
|
f3e4576625 | ||
|
|
5dae2b8d2b | ||
|
|
62da364e5d | ||
|
|
58a318b754 | ||
|
|
ff07f49002 | ||
|
|
8d69d8553c | ||
|
|
a072954176 | ||
|
|
23de8185c6 | ||
|
|
d9c80e9b6a | ||
|
|
dcc4272c4e | ||
|
|
2060940ecc | ||
|
|
14274ef67b | ||
|
|
70661c179f | ||
|
|
8a0ae68f27 | ||
|
|
06ada87370 | ||
|
|
a5d9b71eb6 | ||
|
|
aa3385d516 | ||
|
|
af60b41c89 | ||
|
|
506bf45272 | ||
|
|
2655daa2f4 | ||
|
|
0b095ce5ce | ||
|
|
ae461b77f8 | ||
|
|
4f3b9dc61a | ||
|
|
a246702511 | ||
|
|
2ae47d64b7 | ||
|
|
6a76349730 | ||
|
|
e6482554f5 | ||
|
|
c4ee2d7386 | ||
|
|
a74ec0effa | ||
|
|
e33027c624 | ||
|
|
b0be323e89 | ||
|
|
a22661670f | ||
|
|
442f35a1fd | ||
|
|
5aef16c2aa | ||
|
|
e512e38efb | ||
|
|
7ea0b138bc | ||
|
|
d73fb5a23c | ||
|
|
610298a25d | ||
|
|
7b50a6490d | ||
|
|
fdb82d5dd4 | ||
|
|
a587697883 | ||
|
|
3204c077d1 | ||
|
|
d52825a5b1 | ||
|
|
84c965d459 | ||
|
|
22cd6989a0 | ||
|
|
a9ae555b88 | ||
|
|
d4dc428124 | ||
|
|
f5608d2c94 | ||
|
|
bcad937003 | ||
|
|
53c918cc78 | ||
|
|
009f81fe4f | ||
|
|
81ce4a0229 | ||
|
|
6bcd9adb9e | ||
|
|
61e7b735dc | ||
|
|
44c15fc1ef | ||
|
|
9d3fe1258a | ||
|
|
e72ba39c41 | ||
|
|
ffcc487763 | ||
|
|
473ce8b617 | ||
|
|
70436fa535 | ||
|
|
4921f61e76 | ||
|
|
75b390cf93 | ||
|
|
dcda8fe538 | ||
|
|
6edae86516 | ||
|
|
f163e6d8cc | ||
|
|
742660591f | ||
|
|
ddbcf5f470 | ||
|
|
6becf22a2f | ||
|
|
32246fd26b | ||
|
|
42e3ab91a7 | ||
|
|
6daf70b745 | ||
|
|
12298ea392 | ||
|
|
5e77e8809a | ||
|
|
1b3f84c9ad | ||
|
|
e06539e76d | ||
|
|
fdb3fa6801 | ||
|
|
1392a855bb | ||
|
|
d4cab6e62f | ||
|
|
5d2b6585c6 | ||
|
|
a2f89347a9 | ||
|
|
50ee23ebfa | ||
|
|
719c51f61a | ||
|
|
3e2a614eb9 | ||
|
|
abb23631df | ||
|
|
dae5a063cf | ||
|
|
e57a507ba0 | ||
|
|
4e194d7766 | ||
|
|
d34ea79d93 | ||
|
|
ee73659f16 | ||
|
|
394417ff07 | ||
|
|
a85b1f016d | ||
|
|
c6f97f20fb | ||
|
|
b66600338e | ||
|
|
4ae9482d50 | ||
|
|
72fcf7b2ab | ||
|
|
a8b6bbd6bc | ||
|
|
e5d04f4467 | ||
|
|
62ac8e1952 | ||
|
|
d61f34ec12 | ||
|
|
252fdd03d7 | ||
|
|
0fe5c5dac3 | ||
|
|
037e992de4 | ||
|
|
12023073f4 | ||
|
|
0db0694aad | ||
|
|
d4f763aa68 | ||
|
|
4e708c81ca | ||
|
|
041d00301c | ||
|
|
0ec51b124b | ||
|
|
6012926e82 | ||
|
|
ca9c1bca4a | ||
|
|
0d957ea71d | ||
|
|
697926641f | ||
|
|
2bf65fda1f | ||
|
|
4262ac3c89 | ||
|
|
76b66872d8 | ||
|
|
2f92aaea0c | ||
|
|
c5b347bb15 | ||
|
|
d11087c28f | ||
|
|
e6139e02b8 | ||
|
|
583b92e672 | ||
|
|
d90756e8ef | ||
|
|
9b9ab983d6 | ||
|
|
d5746652a2 | ||
|
|
47f9e171fc | ||
|
|
a1bc18e5cf | ||
|
|
0cd5bf7967 | ||
|
|
0205f9ede3 | ||
|
|
960c83315b | ||
|
|
f9c84fb6f4 | ||
|
|
1b14561748 | ||
|
|
27519e1dfa | ||
|
|
46e2da24a4 | ||
|
|
dbbcbed344 | ||
|
|
c0db88168b | ||
|
|
d98435b4dc | ||
|
|
595dc6de03 | ||
|
|
322e054f1a | ||
|
|
7f606e1e64 | ||
|
|
b0491b0ee2 | ||
|
|
0e69356ca9 | ||
|
|
90348e08c1 | ||
|
|
c60d7e2db8 | ||
|
|
50d3a14825 | ||
|
|
0ff3ba5250 | ||
|
|
02cc3f9116 | ||
|
|
62861d1e13 | ||
|
|
cd3c7f1b97 | ||
|
|
0c8886ad0c | ||
|
|
9d24325207 | ||
|
|
126a165f55 | ||
|
|
1c6e6842c6 | ||
|
|
6f5a55b5fe | ||
|
|
530f5a700e | ||
|
|
441c1f9ab7 | ||
|
|
d98d885924 | ||
|
|
6aa91d89e0 | ||
|
|
ecc54b07c7 | ||
|
|
ce11a38d70 | ||
|
|
c9e0b29878 | ||
|
|
3a96fec03b | ||
|
|
7a231b3166 | ||
|
|
31f6934787 | ||
|
|
c5dc89886d | ||
|
|
545cc0b026 | ||
|
|
9ff42053c3 | ||
|
|
41a0ce146d | ||
|
|
709fa06af6 | ||
|
|
5623400557 | ||
|
|
1351e02065 | ||
|
|
4f030ac45c | ||
|
|
c7c81a1f7e | ||
|
|
ae74965774 | ||
|
|
e952c98ca8 | ||
|
|
6f00c422c7 | ||
|
|
e592cedbb4 | ||
|
|
a10bb4b2fa | ||
|
|
c89eec4261 | ||
|
|
edc0fec808 | ||
|
|
bb5474660c | ||
|
|
63f16b5f99 | ||
|
|
0b84c469d3 | ||
|
|
ff5d18d327 | ||
|
|
1980b3fae4 | ||
|
|
6714b8958b | ||
|
|
f601a5d356 | ||
|
|
e7f60032bc | ||
|
|
589bd8694f | ||
|
|
eace1a9840 | ||
|
|
3ddb203317 | ||
|
|
84b8e77aaa | ||
|
|
247eefc33a | ||
|
|
fd54a6a3ad | ||
|
|
a2ffd7de2e | ||
|
|
cea2fb0fe6 | ||
|
|
23d7d7d140 | ||
|
|
f8179bc5a9 | ||
|
|
a4e93558aa | ||
|
|
cb3cae0f30 | ||
|
|
04e0199790 | ||
|
|
453f3405a7 | ||
|
|
a8585df81b | ||
|
|
c96c681758 | ||
|
|
6007609f71 | ||
|
|
5a6ed252c4 | ||
|
|
641dff8991 | ||
|
|
ee3115550e | ||
|
|
d39852c0cf | ||
|
|
c57894633f | ||
|
|
a7bedad9f0 | ||
|
|
92df3d953f | ||
|
|
a896904ae7 | ||
|
|
6bfcd253f8 | ||
|
|
456f7e7304 | ||
|
|
ed7917f9df | ||
|
|
2a25ece363 | ||
|
|
1c90c3af42 | ||
|
|
6ca6290f6a | ||
|
|
fd2c5d46ad | ||
|
|
a6c110f558 | ||
|
|
02b33f988f | ||
|
|
7bf6e6188a | ||
|
|
26394813f4 | ||
|
|
f21f42f11e | ||
|
|
68a8556cd2 | ||
|
|
e4d8b1c4d2 | ||
|
|
eac467fe9a | ||
|
|
1bb9c912cd | ||
|
|
02d470892f | ||
|
|
161f62dc27 | ||
|
|
f53472e717 | ||
|
|
7fa5d34c45 | ||
|
|
b49d1dae7d | ||
|
|
9b0496b049 | ||
|
|
fec8f8a881 | ||
|
|
e08c54878c | ||
|
|
e78398862b | ||
|
|
122a5e9b63 | ||
|
|
5d759d82ed | ||
|
|
744a9b8931 | ||
|
|
45c4042e2b | ||
|
|
bba7704732 | ||
|
|
fa3e88c454 | ||
|
|
e0ff550e19 | ||
|
|
48dbee2e10 | ||
|
|
1ba2df8024 | ||
|
|
e17f6979c3 | ||
|
|
8330dabda8 | ||
|
|
1fd8c2a6e2 | ||
|
|
0c6be94222 | ||
|
|
89103c40fb | ||
|
|
fbe34663da | ||
|
|
9d0ccdfdcc | ||
|
|
cd1f0c74c1 | ||
|
|
3deac0f100 | ||
|
|
641d3b75ea | ||
|
|
fab2fa9adf | ||
|
|
b3d8cefbe6 | ||
|
|
32e8e52ad7 | ||
|
|
77b3a43e17 | ||
|
|
ddec91ba52 | ||
|
|
0329b36430 | ||
|
|
f62dc0f46c | ||
|
|
f64638173a | ||
|
|
14a0dcecf5 | ||
|
|
e17cca9834 | ||
|
|
9b0f68f9a9 | ||
|
|
f4eb73ca7c | ||
|
|
4ea6ebb0ae | ||
|
|
8931f2e736 | ||
|
|
7ae78ca3c8 | ||
|
|
d24eeec1a1 | ||
|
|
a798f32cc8 | ||
|
|
df5fe4a84f | ||
|
|
9aef222f79 | ||
|
|
5591378245 | ||
|
|
dde2268f9f | ||
|
|
0eaca6c691 | ||
|
|
ba654c04a0 | ||
|
|
f4fbac2694 | ||
|
|
b86f6322e1 | ||
|
|
74c6be3698 | ||
|
|
d114613384 | ||
|
|
c23ea1e688 | ||
|
|
f9d4799ebe | ||
|
|
9ec4f6dcab | ||
|
|
bcdd063d70 | ||
|
|
22bc7cd692 | ||
|
|
080fe38d1c | ||
|
|
df32756556 | ||
|
|
d02b7c5fdf | ||
|
|
c8b54f3bac | ||
|
|
233f97891c | ||
|
|
056e0f26ab | ||
|
|
bda979a6c7 | ||
|
|
cfae1a8dfd | ||
|
|
468a20c9ea | ||
|
|
f24c94f1a8 | ||
|
|
ac27937a9c | ||
|
|
2b82354617 | ||
|
|
3fa98ec00e | ||
|
|
052fd5783f | ||
|
|
63aa3d0659 | ||
|
|
a4af1065ed | ||
|
|
ef8b2875cf | ||
|
|
1424a7302a | ||
|
|
54ac354809 | ||
|
|
f38d2f80a6 | ||
|
|
0c112a2a1c | ||
|
|
81297b44c6 | ||
|
|
a2cc127ea9 | ||
|
|
cfa75ed36c | ||
|
|
498cf5333d | ||
|
|
292aab9b18 | ||
|
|
637183e4b2 | ||
|
|
44e2929a4c | ||
|
|
1043def46c | ||
|
|
6af2ba5cff | ||
|
|
cd5327bc31 | ||
|
|
977fe0f8ef | ||
|
|
a406b4d134 | ||
|
|
1414b8ee8b | ||
|
|
209c31f361 | ||
|
|
721d969a85 | ||
|
|
7bcedc27b8 | ||
|
|
553cbb25f4 | ||
|
|
118381c1d1 | ||
|
|
f2100fa36d | ||
|
|
a537b2e40c | ||
|
|
1b6d3b0f0b | ||
|
|
7dd6352393 | ||
|
|
e37a3155cd | ||
|
|
542cf7b1cb | ||
|
|
60de7ecc41 | ||
|
|
624d108124 | ||
|
|
46f4c56a3d | ||
|
|
49a50efa9f | ||
|
|
80cbdf469e | ||
|
|
acdc65c895 | ||
|
|
0eb7b81636 | ||
|
|
0594667d36 | ||
|
|
cd16b35814 | ||
|
|
5bc7d716e0 | ||
|
|
3ec2402a98 | ||
|
|
04978f93d5 | ||
|
|
0fa978c959 | ||
|
|
f70cef081c | ||
|
|
b4717b8526 | ||
|
|
074040daf5 | ||
|
|
0caab98399 | ||
|
|
8b7caef0db | ||
|
|
836d9be7fe | ||
|
|
e5bdf60460 | ||
|
|
11c48ced00 | ||
|
|
cc3871aca5 | ||
|
|
8fb5e87243 | ||
|
|
504beeb2f3 | ||
|
|
9af47eb063 | ||
|
|
944f51d66c | ||
|
|
499e60c4a3 | ||
|
|
a6f4f83b5b | ||
|
|
2b9418b700 | ||
|
|
aa995ff994 | ||
|
|
d1e4f6efdd | ||
|
|
f65bfc1ab1 | ||
|
|
f26d49f077 | ||
|
|
ad7ce6c916 | ||
|
|
7c49224c7b | ||
|
|
451ded4963 | ||
|
|
837618c7a0 | ||
|
|
e6c138be5a | ||
|
|
087645e59f | ||
|
|
0a12fa7f4e | ||
|
|
87374d7f46 | ||
|
|
efa85f844b | ||
|
|
db08896274 | ||
|
|
44abf283ec | ||
|
|
671bc7c917 | ||
|
|
6bbdc98ba2 | ||
|
|
a9681072ee | ||
|
|
16af052a16 | ||
|
|
40e0706954 | ||
|
|
b859f4b8ab | ||
|
|
835c17c961 | ||
|
|
7b545aa0e0 | ||
|
|
ea08fc8642 | ||
|
|
5410924810 | ||
|
|
5d21d10437 | ||
|
|
5d554ab882 | ||
|
|
c2ce8732e7 | ||
|
|
76c8d7108f | ||
|
|
024bc1756e | ||
|
|
0cf5cc778a | ||
|
|
ed4536f57f | ||
|
|
5bc33257b6 | ||
|
|
0c3a14b662 | ||
|
|
4b7b46071f | ||
|
|
cbc9e87d65 | ||
|
|
1c10440a15 | ||
|
|
a0af271996 | ||
|
|
d21fbbc4c8 | ||
|
|
db419ffcc7 | ||
|
|
b537636b42 | ||
|
|
4e0b4415f0 | ||
|
|
333665370f | ||
|
|
1d4506427f | ||
|
|
be12c5b538 | ||
|
|
5449fc5093 | ||
|
|
a87dd7af7d | ||
|
|
949fc2fc82 | ||
|
|
5f7afce49d | ||
|
|
f8d5b72367 | ||
|
|
ad6e848821 | ||
|
|
9ee88ba06d | ||
|
|
deac665b42 | ||
|
|
fbac611337 | ||
|
|
9886383638 | ||
|
|
5bea6ff581 | ||
|
|
3c40526bff | ||
|
|
8a29f12a61 | ||
|
|
e704aea643 | ||
|
|
25116940c0 | ||
|
|
2148eb6d99 | ||
|
|
ee0adc8a7e | ||
|
|
d3f729dd71 | ||
|
|
9effd57229 | ||
|
|
e347297aaa | ||
|
|
68c240aa4d | ||
|
|
5e6c4e0ec3 | ||
|
|
5e92ca039c | ||
|
|
d1ce78a64f | ||
|
|
0a19a7e8d1 | ||
|
|
d424933d70 | ||
|
|
91679caf93 | ||
|
|
1efcc5ad62 | ||
|
|
96d853fc4b | ||
|
|
0f942f95f0 | ||
|
|
f1cb3a4f37 | ||
|
|
66db021900 | ||
|
|
e7af0eb20c | ||
|
|
5e40f76996 | ||
|
|
903e2f4f8a | ||
|
|
4aeb6226d5 | ||
|
|
e19d5b3c78 | ||
|
|
946978e454 | ||
|
|
40a0d345b5 | ||
|
|
504e7a25a5 | ||
|
|
8db90538a1 | ||
|
|
fe9f692a4f | ||
|
|
e9448953ac | ||
|
|
217c16027e | ||
|
|
be0b9a1d7c | ||
|
|
fdf54668ae | ||
|
|
1d64b542d8 | ||
|
|
e60153a4fb | ||
|
|
762d02b2e0 | ||
|
|
1dcfd4102b | ||
|
|
aaae22642e | ||
|
|
6999e15972 | ||
|
|
1501b47c10 | ||
|
|
fbb4e844a7 | ||
|
|
a705a7d612 | ||
|
|
a0a39fa4dd | ||
|
|
36de4c7b0f | ||
|
|
4e22c289af | ||
|
|
af217e316a | ||
|
|
b5435b6ab8 | ||
|
|
c7418e131f | ||
|
|
c567249819 | ||
|
|
d2dc4d18be | ||
|
|
67ed341f24 | ||
|
|
0b6e874a0d | ||
|
|
88791f732f | ||
|
|
b1feb5ac29 | ||
|
|
873aa26f63 | ||
|
|
a57b22a6bc | ||
|
|
7c61b09dce | ||
|
|
b38aff8808 | ||
|
|
912d15cb73 | ||
|
|
9dc13360c9 | ||
|
|
cb44b77d0b | ||
|
|
1baf065317 | ||
|
|
88c96c7052 | ||
|
|
9680cc1270 | ||
|
|
68d051d4a7 | ||
|
|
f1ed572819 | ||
|
|
9ae56485a9 | ||
|
|
a7ba16ef4a | ||
|
|
35f3396295 | ||
|
|
10817aa337 | ||
|
|
542aae6cd9 | ||
|
|
92eb68bf2c | ||
|
|
aa7d10e510 | ||
|
|
7377df8a4d | ||
|
|
70a7f02d0f | ||
|
|
5fb4b54153 | ||
|
|
c64154e33a | ||
|
|
07da617c05 | ||
|
|
35c133caed | ||
|
|
18a52a1ea7 | ||
|
|
501f054d51 | ||
|
|
f0a5ad2d20 | ||
|
|
258b4fac31 | ||
|
|
8ebc724379 | ||
|
|
df904f80e3 | ||
|
|
e6e12e946e | ||
|
|
2fd5f2781b | ||
|
|
2f97ddb727 | ||
|
|
a7b2131db7 | ||
|
|
f057267955 | ||
|
|
b0547819fd | ||
|
|
dff63f9b89 | ||
|
|
ee00769be1 | ||
|
|
ec22097efb | ||
|
|
bc5fd316df | ||
|
|
92b101fac8 | ||
|
|
78c46d7cc9 | ||
|
|
9f12ef61b0 | ||
|
|
e05dfab1fc | ||
|
|
b5c6c91962 | ||
|
|
f6a945dfe4 | ||
|
|
a12df1c73a | ||
|
|
876032a8a7 | ||
|
|
96d2a55eff | ||
|
|
5d57a2f0e9 | ||
|
|
2b547f94a4 | ||
|
|
c9e490bdae | ||
|
|
6325a03818 | ||
|
|
2577d8f662 | ||
|
|
1ad395cf86 | ||
|
|
1c2d4cbb1a | ||
|
|
9818eb2835 | ||
|
|
59fed02a8b | ||
|
|
0275ac9dad | ||
|
|
62452db5d8 | ||
|
|
792646888a | ||
|
|
cac36e781e | ||
|
|
02f736ac06 | ||
|
|
05296b7f48 | ||
|
|
edeed527c2 | ||
|
|
4c40b60a2d | ||
|
|
134b9dfb23 | ||
|
|
18a5f7485c | ||
|
|
0ff6cc1997 | ||
|
|
66066b9722 | ||
|
|
4620c1c818 | ||
|
|
4370e19880 | ||
|
|
5f44b5f711 | ||
|
|
c83e1d57fc | ||
|
|
60ad5024ce | ||
|
|
5149fcdd74 | ||
|
|
130a7967aa | ||
|
|
6e34cf2c3b | ||
|
|
a98bb1f7ac | ||
|
|
da2661cf33 | ||
|
|
7d82d14a7d | ||
|
|
e5bb3bc75e | ||
|
|
3f99281309 | ||
|
|
28b4ff42ea | ||
|
|
372fc39a6b | ||
|
|
c6b99fcca0 | ||
|
|
b4a95428c1 | ||
|
|
07a872f802 | ||
|
|
9516a50f64 | ||
|
|
bfac021085 | ||
|
|
a19b40b051 | ||
|
|
836cea6fd7 | ||
|
|
66dd659a2f | ||
|
|
c765f115ae | ||
|
|
e372b0c61f | ||
|
|
0f646da970 | ||
|
|
8eb8037f9b | ||
|
|
5b60d9f0f4 | ||
|
|
dcfc0ecac6 | ||
|
|
a6a1601799 | ||
|
|
a8fd3f30cf | ||
|
|
05ce9733da | ||
|
|
96da2c26dc | ||
|
|
0afc95982b |
28
.circleci/config.yml
Normal file
28
.circleci/config.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
version: 2
|
||||
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
- image: circleci/node:6.12
|
||||
environment:
|
||||
CONTRACTS_COMMIT_HASH: '78fe8dd'
|
||||
steps:
|
||||
- checkout
|
||||
- run: echo 'export PATH=$HOME/CIRCLE_PROJECT_REPONAME/node_modules/.bin:$PATH' >> $BASH_ENV
|
||||
- run:
|
||||
name: yarn
|
||||
command: yarn
|
||||
- save_cache:
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
- run: wget https://s3.amazonaws.com/testrpc-shapshots/${CONTRACTS_COMMIT_HASH}.zip
|
||||
- run: unzip ${CONTRACTS_COMMIT_HASH}.zip -d testrpc_snapshot
|
||||
- run: node ./node_modules/lerna/bin/lerna.js bootstrap
|
||||
- run: yarn lerna:run bootstrap
|
||||
- run:
|
||||
name: testrpc
|
||||
command: npm run testrpc -- --db testrpc_snapshot
|
||||
background: true
|
||||
- run: yarn lerna:run test:circleci
|
||||
- run: yarn lerna:run lint
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -58,9 +58,9 @@ typings/
|
||||
.env
|
||||
|
||||
# built library using in commonjs module syntax
|
||||
lib
|
||||
lib/
|
||||
# UMD bundles that export the global variable
|
||||
_bundles
|
||||
|
||||
# generated documentation
|
||||
docs
|
||||
docs/
|
||||
|
||||
96
CHANGELOG.md
96
CHANGELOG.md
@@ -1,96 +0,0 @@
|
||||
# CHANGELOG
|
||||
|
||||
v0.11.0 - _August 24, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.token.setUnlimitedProxyAllowanceAsync` (#137)
|
||||
* Added `zeroEx.token.setUnlimitedAllowanceAsync` (#137)
|
||||
* Added `zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS` (#137)
|
||||
|
||||
v0.10.4 - _Aug 24, 2017_
|
||||
------------------------
|
||||
* Fixed a bug where checksummed addresses were being pulled from artifacts and not lower-cased. (#135)
|
||||
|
||||
v0.10.1 - _Aug 24, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.exchange.validateFillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.isRoundingErrorAsync` (#128)
|
||||
* Added `zeroEx.proxy.getContractAddressAsync` (#130)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressesAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenByNameIfExistsAsync` (#132)
|
||||
* Added clear error message when checksummed address is passed to a public method (#124)
|
||||
* Fixes the description of `shouldThrowOnInsufficientBalanceOrAllowance` in docs (#127)
|
||||
|
||||
v0.9.3 - _Aug 22, 2017_
|
||||
------------------------
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.2 - _Aug 21, 2017_
|
||||
------------------------
|
||||
* *This version was unpublished because of a publishing issue.*
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.1 - _Aug. 16, 2017_
|
||||
------------------------
|
||||
* Fixed the bug causing `zeroEx.token.getBalanceAsync()` to fail if no addresses available (#120)
|
||||
|
||||
v0.9.0 - _Jul. 26, 2017_
|
||||
------------------------
|
||||
* Migrated to the new version of smart contracts (#101)
|
||||
* Removed the ability to call methods on multiple authorized Exchange smart contracts (#106)
|
||||
* Made `zeroEx.getOrderHashHex` a static method (#107)
|
||||
* Cached `net_version` requests and invalidate the cache on calls to `setProvider` (#95)
|
||||
* Renamed `zeroEx.exchange.batchCancelOrderAsync` to `zeroEx.exchange.batchCancelOrdersAsync`
|
||||
* Renamed `zeroEx.exchange.batchFillOrderAsync` to `zeroEx.exchange.batchFillOrdersAsync`
|
||||
* Updated to typescript v2.4 (#104)
|
||||
* Fixed an issue with incorrect balance/allowance validation when ZRX is one of the tokens traded (#109)
|
||||
|
||||
v0.8.0 - _Jul. 4, 2017_
|
||||
------------------------
|
||||
* Added the ability to call methods on different authorized versions of the Exchange smart contract (#82)
|
||||
* Updated contract artifacts to reflect latest changes to the smart contracts (0xproject/contracts#59)
|
||||
* Added `zeroEx.proxy.isAuthorizedAsync` and `zeroEx.proxy.getAuthorizedAddressesAsync` (#89)
|
||||
* Added `zeroEx.token.subscribeAsync` (#90)
|
||||
* Made contract invalidation functions private (#90)
|
||||
* `zeroEx.token.invalidateContractInstancesAsync`
|
||||
* `zeroEx.exchange.invalidateContractInstancesAsync`
|
||||
* `zeroEx.proxy.invalidateContractInstance`
|
||||
* `zeroEx.tokenRegistry.invalidateContractInstance`
|
||||
* Fixed the bug where `zeroEx.setProviderAsync` didn't invalidate etherToken contract's instance
|
||||
|
||||
v0.7.1 - _Jun. 26, 2017_
|
||||
------------------------
|
||||
* Added the ability to convert Ether to wrapped Ether tokens and back via `zeroEx.etherToken.depostAsync` and `zeroEx.etherToken.withdrawAsync` (#81)
|
||||
|
||||
v0.7.0 - _Jun. 22, 2017_
|
||||
------------------------
|
||||
* Added Kovan smart contract artifacts (#78)
|
||||
* Started returning fillAmount from `fillOrderAsync` and `fillUpToAsync` (#72)
|
||||
* Started returning cancelledAmount from `cancelOrderAsync` (#72)
|
||||
* Renamed type `LogCancelArgs` to `LogCancelContractEventArgs` and `LogFillArgs` to `LogFillContractEventArgs`
|
||||
|
||||
v0.6.2 - _Jun. 21, 2017_
|
||||
------------------------
|
||||
* Reduced bundle size
|
||||
* Improved documentation
|
||||
|
||||
v0.6.1 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
* Improved documentation
|
||||
|
||||
v0.6.0 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
* Made `ZeroEx` class accept `Web3Provider` instance instead of `Web3` instance
|
||||
* Added types for contract event arguments
|
||||
|
||||
v0.5.2 - _Jun. 15, 2017_
|
||||
------------------------
|
||||
* Fixed the bug in `postpublish` script that caused that only unminified UMD bundle was uploaded to release page
|
||||
|
||||
v0.5.1 - _Jun. 15, 2017_
|
||||
------------------------
|
||||
* Added `postpublish` script to publish to Github Releases with assets.
|
||||
65
CONTRIBUTING.md
Normal file
65
CONTRIBUTING.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 0x.js CONTRIBUTING.md
|
||||
|
||||
Thank you for your interest in contributing to 0x.js! We welcome contributions from anyone on the internet, and are grateful for even the smallest of fixes!
|
||||
|
||||
## Developer's guide
|
||||
|
||||
## How to contribute
|
||||
|
||||
If you'd like to contribute to 0x.js, please fork the repo, fix, commit and send a pull request against the `development` branch for the maintainers to review and merge into the main code base. If you wish to submit more complex changes though, please check up with a core dev first on [our gitter channel](https://gitter.im/0xProject/Lobby) or in the `#dev` channel on our [slack](https://slack.0xproject.com/) to ensure those changes are in line with the general philosophy of the project and/or to get some early feedback which can make both your efforts easier as well as our review and merge procedures quick and simple.
|
||||
|
||||
We encourage a “PR early” approach so create the PR as early as possible even without the fix/feature ready, so that devs and other volunteers know you have picked up the issue. These early PRs should indicate an 'in progress' status by adding the '[WIP]' prefix to the PR title. Please make sure your contributions adhere to our coding guidelines:
|
||||
|
||||
* Pull requests adding features or refactoring should be opened against the `development` branch
|
||||
* Pull requests fixing bugs in the latest release version should be opened again the `master` branch
|
||||
* Write [good commit messages](https://chris.beams.io/posts/git-commit/)
|
||||
|
||||
## Code quality
|
||||
|
||||
Because 0x.js is used by multiple relayers in production and their businesses depend on it, we strive for excellent code quality. Please follow the existing code standards and conventions. `tslint` (described below) will help you.
|
||||
If you're adding functionality, please also add tests and make sure they pass. We have an automatic coverage reporting tool, so we'll see it if they are missing ;)
|
||||
If you're adding a new public function/member, make sure you document it with Java doc-style comments. We use typedoc to generate [awesome documentation](https://0xproject.com/docs/0xjs) from the comments within our source code.
|
||||
|
||||
## Running and building
|
||||
|
||||
First thing to do with an unknown code base is to run the tests.
|
||||
We assume that you have `npm` and `yarn` installed.
|
||||
|
||||
To do that:
|
||||
|
||||
* Install dependencies: `yarn`
|
||||
* Initialize the testrpc state (migrate the contracts) by doing one of the following:
|
||||
* Manual contracts migration:
|
||||
* Run testrpc: `yarn testrpc`
|
||||
* Clone the `[contracts](https://github.com/0xProject/contracts)` repo and run `yarn migrate`
|
||||
* Use one of the existing testrpc snapshots
|
||||
* Check out `circle.yml` for an example
|
||||
* Run tests: `yarn test`
|
||||
|
||||
To build run: `yarn build`
|
||||
|
||||
We also recommend you read through the tests.
|
||||
|
||||
## Styleguide
|
||||
|
||||
We use `[tslint](https://palantir.github.io/tslint/)` with [custom configs](https://github.com/0xProject/tslint-config-0xproject) to keep our code style consistent.
|
||||
|
||||
To lint your code just run: `yarn lint`
|
||||
|
||||
If using the Atom text editor, we recommend you install the following packages:
|
||||
|
||||
* [atom-typescript](https://atom.io/packages/atom-typescript)
|
||||
* [linter-tslint](https://atom.io/packages/linter-tslint)
|
||||
|
||||
Our CI will also run it as a part of the test run when you submit your PR.
|
||||
|
||||
|
||||
## Branch structure & versioning
|
||||
|
||||
We use [semantic versioning](http://semver.org/), but before we reach v1.0.0 all breaking changes as well as new features will be minor version bumps.
|
||||
|
||||
We have two main branches: `master` and `development`.
|
||||
|
||||
`master` represents the most recent released (published on npm) version.
|
||||
|
||||
`development` represents the development state and is a default branch to which you will submit a PR. We use this structure so that we can push hotfixes to the currently released version without needing to publish all the changes made towards the next release. If a hotfix is implemented on `master`, it is back-ported to `development`.
|
||||
2
PULL_REQUEST_TEMPLATE.md
Normal file
2
PULL_REQUEST_TEMPLATE.md
Normal file
@@ -0,0 +1,2 @@
|
||||
This PR:
|
||||
*
|
||||
52
README.md
52
README.md
@@ -4,50 +4,24 @@
|
||||
|
||||
[0x][website-url] is an open protocol that facilitates trustless, low friction exchange of Ethereum-based assets. A full description of the protocol may be found in our [whitepaper][whitepaper-url].
|
||||
|
||||
This repository contains a Javascript library that makes it easy to build Relayers and other DApps that use the 0x protocol.
|
||||
This repository contains all the 0x developer tools written in TypeScript. Our hope is that these tools make it easy to build Relayers and other DApps that use the 0x protocol.
|
||||
|
||||
[website-url]: https://0xproject.com/
|
||||
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
|
||||
|
||||
[](https://circleci.com/gh/0xProject/0x.js)
|
||||
[](https://badge.fury.io/js/0x.js)
|
||||
[](https://coveralls.io/github/0xProject/0x.js?branch=master)
|
||||
[](http://slack.0xProject.com)
|
||||
[](https://chat.0xproject.com)
|
||||
[](https://gitter.im/0xProject/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://opensource.org/licenses/Apache-2.0)
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
## Installation
|
||||
### Core Packages
|
||||
|
||||
0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package.
|
||||
|
||||
#### CommonJS *(recommended)*:
|
||||
|
||||
**Install**
|
||||
|
||||
```bash
|
||||
npm install 0x.js --save
|
||||
```
|
||||
|
||||
**Import**
|
||||
|
||||
```javascript
|
||||
import {ZeroEx} from '0x.js';
|
||||
```
|
||||
|
||||
#### UMD:
|
||||
|
||||
**Install**
|
||||
|
||||
Download the UMD module from our [releases page](https://github.com/0xProject/0x.js/releases) and add it to your project.
|
||||
|
||||
**Import**
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="0x.js"></script>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Extensive documentation of 0x.js can be found on [our website][docs-url].
|
||||
|
||||
[website-url]: https://0xproject.com/
|
||||
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
|
||||
[docs-url]: https://0xproject.com/docs/0xjs
|
||||
| Package | Version | Description |
|
||||
|--------|-------|------------|
|
||||
| [`0x.js`](/packages/0x.js) | [](https://www.npmjs.com/package/0x.js) | A Javascript library for interacting with the 0x protocol |
|
||||
| [`@0xproject/assert`](/packages/assert) | [](https://www.npmjs.com/package/@0xproject/assert) | Standard type and schema assertions |
|
||||
| [`@0xproject/json-schemas`](/packages/json-schemas) | [](https://www.npmjs.com/package/@0xproject/json-schemas) | 0x-related json schemas |
|
||||
| [`@0xproject/tslint-config`](/packages/tslint-config) | [](https://www.npmjs.com/package/@0xproject/tslint-config) | Custom 0x project TSLint rules |
|
||||
|
||||
23
circle.yml
23
circle.yml
@@ -1,23 +0,0 @@
|
||||
machine:
|
||||
node:
|
||||
version: 6.5.0
|
||||
environment:
|
||||
CONTRACTS_COMMIT_HASH: '35053f9'
|
||||
PATH: "${PATH}:${HOME}/${CIRCLE_PROJECT_REPONAME}/node_modules/.bin"
|
||||
|
||||
dependencies:
|
||||
override:
|
||||
- yarn
|
||||
cache_directories:
|
||||
- ~/.cache/yarn
|
||||
|
||||
test:
|
||||
override:
|
||||
- wget https://s3.amazonaws.com/testrpc-shapshots/${CONTRACTS_COMMIT_HASH}.zip
|
||||
- unzip ${CONTRACTS_COMMIT_HASH}.zip -d testrpc_snapshot
|
||||
- npm run testrpc -- --db testrpc_snapshot:
|
||||
background: true
|
||||
- yarn test:coverage
|
||||
- yarn report_test_coverage
|
||||
- if [ $CIRCLE_BRANCH = "master" ]; then yarn test:umd; fi
|
||||
- yarn lint
|
||||
9
lerna.json
Normal file
9
lerna.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"lerna": "2.5.1",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "independent",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true
|
||||
}
|
||||
123
package.json
123
package.json
@@ -1,105 +1,22 @@
|
||||
{
|
||||
"name": "0x.js",
|
||||
"version": "0.11.0",
|
||||
"description": "A javascript library for interacting with the 0x protocol",
|
||||
"keywords": [
|
||||
"0x.js",
|
||||
"0xproject",
|
||||
"ethereum",
|
||||
"tokens",
|
||||
"exchange"
|
||||
],
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean",
|
||||
"build": "run-p build:umd:prod build:commonjs",
|
||||
"prepublishOnly": "run-p build",
|
||||
"postpublish": "run-s release docs:json upload_docs_json",
|
||||
"release": "publish-release --assets _bundles/index.js,_bundles/index.min.js --tag $(git describe --tags) --owner 0xProject --repo 0x.js",
|
||||
"upload_docs_json": "aws s3 cp docs/index.json s3://0xjs-docs-jsons/$(git describe --tags).json --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json",
|
||||
"lint": "tslint src/*.ts test/*.ts",
|
||||
"test": "run-s clean test:commonjs",
|
||||
"test:umd": "./scripts/test_umd.sh",
|
||||
"test:coverage": "nyc npm run test --all",
|
||||
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;",
|
||||
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json docs/index.json .",
|
||||
"docs:generate": "typedoc --out docs .",
|
||||
"docs:open": "opn docs/index.html",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"build:umd:dev": "webpack",
|
||||
"build:umd:prod": "NODE_ENV=production webpack",
|
||||
"build:commonjs": "tsc; copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;",
|
||||
"test:commonjs": "run-s build:commonjs run_mocha",
|
||||
"pretest:umd": "run-s clean build:*:dev",
|
||||
"substitute_umd_bundle": "npm run remove_src_files_not_used_by_tests; shx mv _bundles/* lib/src",
|
||||
"remove_src_files_not_used_by_tests": "find ./lib/src \\( -path ./lib/src/utils -o -path ./lib/src/subproviders -o -path ./lib/src/schemas -o -path \"./lib/src/types.*\" \\) -prune -o -type f -print | xargs rm",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js --timeout 4000 --bail"
|
||||
},
|
||||
"config": {
|
||||
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken",
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bignumber.js": "^4.0.2",
|
||||
"@types/jsonschema": "^1.1.1",
|
||||
"@types/lodash": "^4.14.64",
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^8.0.1",
|
||||
"@types/sinon": "^2.2.2",
|
||||
"awesome-typescript-loader": "^3.1.3",
|
||||
"bignumber.js": "^4.0.2",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-as-promised-typescript-typings": "0.0.3",
|
||||
"chai-bignumber": "^2.0.1",
|
||||
"chai-typescript-typings": "^0.0.0",
|
||||
"copyfiles": "^1.2.0",
|
||||
"coveralls": "^2.13.1",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"ethereumjs-testrpc": "4.0.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^3.4.1",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"nyc": "^11.0.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.4",
|
||||
"shx": "^0.2.2",
|
||||
"sinon": "^3.0.0",
|
||||
"source-map-support": "^0.4.15",
|
||||
"truffle-hdwallet-provider": "^0.0.3",
|
||||
"tslint": "^5.3.2",
|
||||
"tslint-config-0xproject": "^0.0.2",
|
||||
"typedoc": "^0.8.0",
|
||||
"types-bn": "^0.0.1",
|
||||
"types-ethereumjs-util": "^0.0.5",
|
||||
"typescript": "^2.4.1",
|
||||
"web3-provider-engine": "^13.0.1",
|
||||
"web3-typescript-typings": "^0.3.2",
|
||||
"webpack": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"bignumber.js": "^4.0.2",
|
||||
"compare-versions": "^3.0.1",
|
||||
"es6-promisify": "^5.0.0",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"find-versions": "^2.0.0",
|
||||
"jsonschema": "^1.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"publish-release": "^1.3.3",
|
||||
"truffle-contract": "^2.0.1",
|
||||
"web3": "^0.20.0"
|
||||
}
|
||||
"private": true,
|
||||
"name": "0x.js",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"scripts": {
|
||||
"testrpc": "testrpc -p 8545 --networkId 50 -m \"${npm_package_config_mnemonic}\"",
|
||||
"lerna:run": "lerna run",
|
||||
"lerna:publish": "lerna run clean; lerna run build; lerna publish"
|
||||
},
|
||||
"config": {
|
||||
"mnemonic": "concert load couple harbor equip island argue ramp clarify fence smart topic"
|
||||
},
|
||||
"devDependencies": {
|
||||
"lerna": "^2.5.1",
|
||||
"async-child-process": "^1.1.1",
|
||||
"semver-sort": "^0.0.4",
|
||||
"publish-release": "0xproject/publish-release",
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
228
packages/0x.js/CHANGELOG.md
Normal file
228
packages/0x.js/CHANGELOG.md
Normal file
@@ -0,0 +1,228 @@
|
||||
# CHANGELOG
|
||||
|
||||
vx.x.x
|
||||
------------------------
|
||||
* Add post-formatter for logs converting `blockNumber`, `logIndex`, `transactionIndex` from hexes to numbers (#231)
|
||||
* Remove support for Async callback types when used in Subscribe functions (#222)
|
||||
* In OrderWatcher subscribe to ZRX Token Transfer and Approval events when maker token is different (#225)
|
||||
|
||||
v0.25.1 - _November 13, 2017_
|
||||
------------------------
|
||||
* Standardise on Cancelled over Canceled (#217)
|
||||
* Add missing `DecodedLogEvent` type to exported types (#205)
|
||||
* Normalized the transactionReceipt status to be `null|0|1`, 1 meaning transaction execution successful, 0 unsuccessful and `null` if it is a pre-byzantinium transaction. (#200)
|
||||
|
||||
v0.23.0 - _November 12, 2017_
|
||||
------------------------
|
||||
* Fixed unhandled promise rejection error in subscribe methods (#209)
|
||||
* Subscribe callbacks now receive an error object as their first argument
|
||||
|
||||
v0.22.6 - _November 10, 2017_
|
||||
------------------------
|
||||
* Add a timeout parameter to transaction awaiting (#206)
|
||||
|
||||
v0.22.5 - _November 7, 2017_
|
||||
------------------------
|
||||
* Re-publish v0.22.4 to fix publishing issue
|
||||
|
||||
v0.22.4 - _October 25, 2017_
|
||||
------------------------
|
||||
* Upgraded bignumber.js to a new version that ships with native typings
|
||||
|
||||
v0.22.3 - _October 25, 2017_
|
||||
------------------------
|
||||
* Fixed an issue with new version of testrpc and unlimited proxy allowance (#199)
|
||||
|
||||
v0.22.2 - _October 24, 2017_
|
||||
------------------------
|
||||
* Fixed rounding of maker fill amount and incorrect validation of partial fees (#197)
|
||||
|
||||
v0.22.0 - _October 16, 2017_
|
||||
------------------------
|
||||
* Started using `OrderFillRequest` interface instead of `OrderFillOrKillRequest` interface for `zeroEx.exchange.batchFillOrKill` (#187)
|
||||
* Removed `OrderFillOrKillRequest` (#187)
|
||||
|
||||
v0.21.4 - _October 13, 2017_
|
||||
------------------------
|
||||
* Made 0x.js more type-safe by making `getLogsAsync` and `subscribe/subscribeAsync` generics parametrized with arg type (#194)
|
||||
|
||||
v0.21.3 - _October 12, 2017_
|
||||
------------------------
|
||||
* Fixed a bug causing order fills to throw `INSUFFICIENT_TAKER_ALLOWANCE` (#193)
|
||||
|
||||
v0.21.2 - _October 11, 2017_
|
||||
------------------------
|
||||
* Exported `ContractEventArg` as a public type (#190)
|
||||
|
||||
v0.21.1 - _October 11, 2017_
|
||||
------------------------
|
||||
* Fixed a bug in subscriptions (#189)
|
||||
|
||||
v0.21.0 - _October 10, 2017_
|
||||
------------------------
|
||||
* Complete rewrite of subscription logic (#182)
|
||||
* Subscriptions no longer return historical logs. If you want them - use `getLogsAsync`
|
||||
* Subscriptions now use [ethereumjs-blockstream](https://github.com/ethereumjs/ethereumjs-blockstream) under the hood
|
||||
* Subscriptions correctly handle block re-orgs (forks)
|
||||
* Subscriptions correctly backfill logs (connection problems)
|
||||
* They no longer setup filters on the underlying nodes, so you can use them with infura without a filter Subprovider
|
||||
* Removed `ContractEventEmitter` and added `LogEvent`
|
||||
* Renamed `zeroEx.token.subscribeAsync` to `zeroEx.token.subscribe`
|
||||
* Added `zeroEx.token.unsubscribe` and `zeroEx.exchange.unsubscribe`
|
||||
* Renamed `zeroEx.exchange.stopWatchingAllEventsAsync` to `zeroEx.exhange.unsubscribeAll`
|
||||
* Renamed `zeroEx.token.stopWatchingAllEventsAsync` to `zeroEx.token.unsubscribeAll`
|
||||
* Fixed the batch fills validation by emulating all balance & proxy allowance changes (#185)
|
||||
|
||||
v0.20.0 - _October 5, 2017_
|
||||
------------------------
|
||||
* Add `zeroEx.token.getLogsAsync` (#178)
|
||||
* Add `zeroEx.exchange.getLogsAsync` (#178)
|
||||
* Fixed fees validation when one of the tokens transferred is ZRX (#181)
|
||||
|
||||
v0.19.0 - _September 29, 2017_
|
||||
------------------------
|
||||
* Made order validation optional (#172)
|
||||
* Added Ropsten testnet support (#173)
|
||||
* Fixed a bug causing awaitTransactionMinedAsync to DDos backend nodes (#175)
|
||||
|
||||
v0.18.0 - _September 26, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.exchange.validateOrderFillableOrThrowAsync` to simplify orderbook pruning (#170)
|
||||
|
||||
v0.17.0 - _September 26, 2017_
|
||||
------------------------
|
||||
* Made `zeroEx.exchange.getZRXTokenAddressAsync` public (#171)
|
||||
|
||||
v0.16.0 - _September 20, 2017_
|
||||
------------------------
|
||||
* Added the ability to specify custom contract addresses to be used with 0x.js (#165)
|
||||
* ZeroExConfig.exchangeContractAddress
|
||||
* ZeroExConfig.tokenRegistryContractAddress
|
||||
* ZeroExConfig.etherTokenContractAddress
|
||||
* Added `zeroEx.tokenRegistry.getContractAddressAsync` (#165)
|
||||
|
||||
v0.15.0 - _September 8, 2017_
|
||||
------------------------
|
||||
* Added the ability to specify a historical `blockNumber` at which to query the blockchain's state when calling a token or exchange method (#161)
|
||||
|
||||
v0.14.2 - _September 7, 2017_
|
||||
------------------------
|
||||
* Fixed an issue with bignumber.js types not found (#160)
|
||||
|
||||
v0.14.1 - _September 7, 2017_
|
||||
------------------------
|
||||
* Fixed an issue with Artifact type not found (#159)
|
||||
|
||||
v0.14.0 - _September 6, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.exchange.throwLogErrorsAsErrors` method to public interface (#157)
|
||||
* Fixed an issue with overlapping async intervals in `zeroEx.awaitTransactionMinedAsync` (#157)
|
||||
* Fixed an issue with log decoder returning `BigNumber`s as `strings` (#157)
|
||||
|
||||
v0.13.0 - _September 6, 2017_
|
||||
------------------------
|
||||
* Made all the functions submitting transactions to the network to immediately return transaction hash (#151)
|
||||
* Added `zeroEx.awaitTransactionMinedAsync` (#151)
|
||||
* Added `TransactionReceiptWithDecodedLogs`, `LogWithDecodedArgs`, `DecodedLogArgs` to public types (#151)
|
||||
* Added signature validation to `validateFillOrderThrowIfInvalidAsync` (#152)
|
||||
|
||||
v0.12.1 - _September 2, 2017_
|
||||
------------------------
|
||||
* Added the support for web3@1.x.x provider (#142)
|
||||
* Added the optional `zeroExConfig` parameter to the constructor of `ZeroEx` (#139)
|
||||
* Added the ability to specify `gasPrice` when instantiating `ZeroEx` (#139)
|
||||
|
||||
v0.11.0 - _August 24, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.token.setUnlimitedProxyAllowanceAsync` (#137)
|
||||
* Added `zeroEx.token.setUnlimitedAllowanceAsync` (#137)
|
||||
* Added `zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS` (#137)
|
||||
|
||||
v0.10.4 - _Aug 24, 2017_
|
||||
------------------------
|
||||
* Fixed a bug where checksummed addresses were being pulled from artifacts and not lower-cased. (#135)
|
||||
|
||||
v0.10.1 - _Aug 24, 2017_
|
||||
------------------------
|
||||
* Added `zeroEx.exchange.validateFillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync` (#128)
|
||||
* Added `zeroEx.exchange.isRoundingErrorAsync` (#128)
|
||||
* Added `zeroEx.proxy.getContractAddressAsync` (#130)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressesAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressBySymbolIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenAddressByNameIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenBySymbolIfExistsAsync` (#132)
|
||||
* Added `zeroEx.tokenRegistry.getTokenByNameIfExistsAsync` (#132)
|
||||
* Added clear error message when checksummed address is passed to a public method (#124)
|
||||
* Fixes the description of `shouldThrowOnInsufficientBalanceOrAllowance` in docs (#127)
|
||||
|
||||
v0.9.3 - _Aug 22, 2017_
|
||||
------------------------
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.2 - _Aug 21, 2017_
|
||||
------------------------
|
||||
* *This version was unpublished because of a publishing issue.*
|
||||
* Update contract artifacts to include latest Kovan and Mainnet deploys (#118)
|
||||
|
||||
v0.9.1 - _Aug. 16, 2017_
|
||||
------------------------
|
||||
* Fixed the bug causing `zeroEx.token.getBalanceAsync()` to fail if no addresses available (#120)
|
||||
|
||||
v0.9.0 - _Jul. 26, 2017_
|
||||
------------------------
|
||||
* Migrated to the new version of smart contracts (#101)
|
||||
* Removed the ability to call methods on multiple authorized Exchange smart contracts (#106)
|
||||
* Made `zeroEx.getOrderHashHex` a static method (#107)
|
||||
* Cached `net_version` requests and invalidate the cache on calls to `setProvider` (#95)
|
||||
* Renamed `zeroEx.exchange.batchCancelOrderAsync` to `zeroEx.exchange.batchCancelOrdersAsync`
|
||||
* Renamed `zeroEx.exchange.batchFillOrderAsync` to `zeroEx.exchange.batchFillOrdersAsync`
|
||||
* Updated to typescript v2.4 (#104)
|
||||
* Fixed an issue with incorrect balance/allowance validation when ZRX is one of the tokens traded (#109)
|
||||
|
||||
v0.8.0 - _Jul. 4, 2017_
|
||||
------------------------
|
||||
* Added the ability to call methods on different authorized versions of the Exchange smart contract (#82)
|
||||
* Updated contract artifacts to reflect latest changes to the smart contracts (0xproject/contracts#59)
|
||||
* Added `zeroEx.proxy.isAuthorizedAsync` and `zeroEx.proxy.getAuthorizedAddressesAsync` (#89)
|
||||
* Added `zeroEx.token.subscribeAsync` (#90)
|
||||
* Made contract invalidation functions private (#90)
|
||||
* `zeroEx.token.invalidateContractInstancesAsync`
|
||||
* `zeroEx.exchange.invalidateContractInstancesAsync`
|
||||
* `zeroEx.proxy.invalidateContractInstance`
|
||||
* `zeroEx.tokenRegistry.invalidateContractInstance`
|
||||
* Fixed the bug where `zeroEx.setProviderAsync` didn't invalidate etherToken contract's instance
|
||||
|
||||
v0.7.1 - _Jun. 26, 2017_
|
||||
------------------------
|
||||
* Added the ability to convert Ether to wrapped Ether tokens and back via `zeroEx.etherToken.depostAsync` and `zeroEx.etherToken.withdrawAsync` (#81)
|
||||
|
||||
v0.7.0 - _Jun. 22, 2017_
|
||||
------------------------
|
||||
* Added Kovan smart contract artifacts (#78)
|
||||
* Started returning fillAmount from `fillOrderAsync` and `fillUpToAsync` (#72)
|
||||
* Started returning cancelledAmount from `cancelOrderAsync` (#72)
|
||||
* Renamed type `LogCancelArgs` to `LogCancelContractEventArgs` and `LogFillArgs` to `LogFillContractEventArgs`
|
||||
|
||||
v0.6.2 - _Jun. 21, 2017_
|
||||
------------------------
|
||||
* Reduced bundle size
|
||||
* Improved documentation
|
||||
|
||||
v0.6.1 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
* Improved documentation
|
||||
|
||||
v0.6.0 - _Jun. 19, 2017_
|
||||
------------------------
|
||||
* Made `ZeroEx` class accept `Web3Provider` instance instead of `Web3` instance
|
||||
* Added types for contract event arguments
|
||||
|
||||
v0.5.2 - _Jun. 15, 2017_
|
||||
------------------------
|
||||
* Fixed the bug in `postpublish` script that caused that only unminified UMD bundle was uploaded to release page
|
||||
|
||||
v0.5.1 - _Jun. 15, 2017_
|
||||
------------------------
|
||||
* Added `postpublish` script to publish to Github Releases with assets.
|
||||
40
packages/0x.js/README.md
Normal file
40
packages/0x.js/README.md
Normal file
@@ -0,0 +1,40 @@
|
||||
0x.js
|
||||
-----
|
||||
|
||||
## Installation
|
||||
|
||||
0x.js ships as both a [UMD](https://github.com/umdjs/umd) module and a [CommonJS](https://en.wikipedia.org/wiki/CommonJS) package.
|
||||
|
||||
#### CommonJS *(recommended)*:
|
||||
|
||||
**Install**
|
||||
|
||||
```bash
|
||||
npm install 0x.js --save
|
||||
```
|
||||
|
||||
**Import**
|
||||
|
||||
```javascript
|
||||
import {ZeroEx} from '0x.js';
|
||||
```
|
||||
|
||||
#### UMD:
|
||||
|
||||
**Install**
|
||||
|
||||
Download the UMD module from our [releases page](https://github.com/0xProject/0x.js/releases) and add it to your project.
|
||||
|
||||
**Import**
|
||||
|
||||
```html
|
||||
<script type="text/javascript" src="0x.js"></script>
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Extensive documentation of 0x.js can be found on [our website][docs-url].
|
||||
|
||||
[website-url]: https://0xproject.com/
|
||||
[whitepaper-url]: https://0xproject.com/pdfs/0x_white_paper.pdf
|
||||
[docs-url]: https://0xproject.com/docs/0xjs
|
||||
100
packages/0x.js/package.json
Normal file
100
packages/0x.js/package.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"name": "0x.js",
|
||||
"version": "0.26.0",
|
||||
"description": "A javascript library for interacting with the 0x protocol",
|
||||
"keywords": [
|
||||
"0x.js",
|
||||
"0xproject",
|
||||
"ethereum",
|
||||
"tokens",
|
||||
"exchange"
|
||||
],
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"prebuild": "npm run clean",
|
||||
"build": "run-p build:umd:prod build:commonjs; exit 0;",
|
||||
"docs:json": "typedoc --excludePrivate --excludeExternals --target ES5 --json $JSON_FILE_PATH $PROJECT_DIR",
|
||||
"upload_docs_json": "aws s3 cp docs/index.json $S3_URL --profile 0xproject --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers --content-type aplication/json",
|
||||
"lint": "tslint src/**/*.ts test/**/*.ts",
|
||||
"test:circleci": "run-s test:coverage report_test_coverage; if [ $CIRCLE_BRANCH = \"development\" ]; then yarn test:umd; fi",
|
||||
"test": "run-s clean test:commonjs",
|
||||
"test:umd": "./scripts/test_umd.sh",
|
||||
"test:coverage": "nyc npm run test --all",
|
||||
"report_test_coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"update_contracts": "for i in ${npm_package_config_artifacts}; do copyfiles -u 4 ../contracts/build/contracts/$i.json ../0x.js/src/artifacts; done;",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"build:umd:dev": "webpack",
|
||||
"build:umd:prod": "NODE_ENV=production webpack",
|
||||
"build:commonjs": "tsc && copyfiles -u 2 './src/artifacts/**/*.json' ./lib/src/artifacts;",
|
||||
"test:commonjs": "run-s build:commonjs run_mocha",
|
||||
"pretest:umd": "run-s clean build:umd:dev build:commonjs",
|
||||
"substitute_umd_bundle": "shx mv _bundles/* lib/src",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js --timeout 5000 --bail --exit"
|
||||
},
|
||||
"config": {
|
||||
"artifacts": "TokenTransferProxy Exchange TokenRegistry Token EtherToken"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/tslint-config": "^0.1.1",
|
||||
"@types/jsonschema": "^1.1.1",
|
||||
"@types/lodash": "^4.14.64",
|
||||
"@types/mocha": "^2.2.41",
|
||||
"@types/node": "^8.0.1",
|
||||
"@types/sinon": "^2.2.2",
|
||||
"@types/uuid": "^3.4.2",
|
||||
"awesome-typescript-loader": "^3.1.3",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-as-promised-typescript-typings": "0.0.3",
|
||||
"chai-bignumber": "^2.0.1",
|
||||
"chai-typescript-typings": "^0.0.1",
|
||||
"copyfiles": "^1.2.0",
|
||||
"coveralls": "^3.0.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"ethereumjs-testrpc": "4.0.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"mocha": "^4.0.0",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"nyc": "^11.0.1",
|
||||
"opn-cli": "^3.1.0",
|
||||
"request": "^2.81.0",
|
||||
"request-promise-native": "^1.0.4",
|
||||
"shx": "^0.2.2",
|
||||
"sinon": "^4.0.0",
|
||||
"source-map-support": "^0.5.0",
|
||||
"truffle-hdwallet-provider": "^0.0.3",
|
||||
"tslint": "5.8.0",
|
||||
"typedoc": "~0.8.0",
|
||||
"types-bn": "^0.0.1",
|
||||
"types-ethereumjs-util": "0xProject/types-ethereumjs-util",
|
||||
"typescript": "~2.6.1",
|
||||
"web3-provider-engine": "^13.0.1",
|
||||
"web3-typescript-typings": "^0.7.1",
|
||||
"webpack": "^3.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0xproject/assert": "^0.0.5",
|
||||
"@0xproject/json-schemas": "^0.6.8",
|
||||
"bignumber.js": "~4.1.0",
|
||||
"bn.js": "4.11.8",
|
||||
"compare-versions": "^3.0.1",
|
||||
"es6-promisify": "^5.0.0",
|
||||
"ethereumjs-abi": "^0.6.4",
|
||||
"ethereumjs-blockstream": "^2.0.6",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"find-versions": "^2.0.0",
|
||||
"js-sha3": "^0.6.1",
|
||||
"lodash": "^4.17.4",
|
||||
"uuid": "^3.1.0",
|
||||
"web3": "^0.20.0"
|
||||
}
|
||||
}
|
||||
43
packages/0x.js/scripts/postpublish.js
Normal file
43
packages/0x.js/scripts/postpublish.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const execAsync = require('async-child-process').execAsync;
|
||||
const postpublish_utils = require('../../../scripts/postpublish_utils');
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const cwd = __dirname + '/..';
|
||||
const subPackageName = packageJSON.name;
|
||||
const S3BucketPath = 's3://0xjs-docs-jsons/';
|
||||
|
||||
let tag;
|
||||
let version;
|
||||
postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
|
||||
.then(function(result) {
|
||||
tag = result.tag;
|
||||
version = result.version;
|
||||
const releaseName = postpublish_utils.getReleaseName(subPackageName, version);
|
||||
const assets = [
|
||||
__dirname + '/../_bundles/index.js',
|
||||
__dirname + '/../_bundles/index.min.js',
|
||||
];
|
||||
return postpublish_utils.publishReleaseNotes(tag, releaseName, assets);
|
||||
})
|
||||
.then(function(release) {
|
||||
console.log('POSTPUBLISH: Release successful, generating docs...');
|
||||
return execAsync(
|
||||
'JSON_FILE_PATH=' + __dirname + '/../docs/index.json PROJECT_DIR=' + __dirname + '/.. yarn docs:json',
|
||||
{
|
||||
cwd,
|
||||
}
|
||||
);
|
||||
})
|
||||
.then(function(result) {
|
||||
if (result.stderr !== '') {
|
||||
throw new Error(result.stderr);
|
||||
}
|
||||
const fileName = 'v' + version + '.json';
|
||||
console.log('POSTPUBLISH: Doc generation successful, uploading docs... as ', fileName);
|
||||
const s3Url = S3BucketPath + fileName;
|
||||
return execAsync('S3_URL=' + s3Url + ' yarn upload_docs_json', {
|
||||
cwd,
|
||||
});
|
||||
}).catch (function(err) {
|
||||
throw err;
|
||||
});
|
||||
@@ -3,5 +3,4 @@
|
||||
# UMD tests should only be run after building the commonjs because they reuse some of the commonjs build artifacts
|
||||
run-s substitute_umd_bundle run_mocha
|
||||
return_code=$?
|
||||
npm run clean
|
||||
exit $return_code
|
||||
@@ -1,25 +1,34 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {SchemaValidator, schemas} from '@0xproject/json-schemas';
|
||||
import {bigNumberConfigs} from './bignumber_config';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import contract = require('truffle-contract');
|
||||
import findVersions = require('find-versions');
|
||||
import compareVersions = require('compare-versions');
|
||||
import {Web3Wrapper} from './web3_wrapper';
|
||||
import {constants} from './utils/constants';
|
||||
import {utils} from './utils/utils';
|
||||
import {signatureUtils} from './utils/signature_utils';
|
||||
import {assert} from './utils/assert';
|
||||
import {AbiDecoder} from './utils/abi_decoder';
|
||||
import {intervalUtils} from './utils/interval_utils';
|
||||
import {artifacts} from './artifacts';
|
||||
import {ExchangeWrapper} from './contract_wrappers/exchange_wrapper';
|
||||
import {TokenRegistryWrapper} from './contract_wrappers/token_registry_wrapper';
|
||||
import {EtherTokenWrapper} from './contract_wrappers/ether_token_wrapper';
|
||||
import {ecSignatureSchema} from './schemas/ec_signature_schema';
|
||||
import {TokenWrapper} from './contract_wrappers/token_wrapper';
|
||||
import {TokenTransferProxyWrapper} from './contract_wrappers/token_transfer_proxy_wrapper';
|
||||
import {ECSignature, ZeroExError, Order, SignedOrder, Web3Provider} from './types';
|
||||
import {orderHashSchema} from './schemas/order_hash_schema';
|
||||
import {orderSchema} from './schemas/order_schemas';
|
||||
import {SchemaValidator} from './utils/schema_validator';
|
||||
import {OrderStateWatcher} from './order_watcher/order_state_watcher';
|
||||
import {OrderStateUtils} from './utils/order_state_utils';
|
||||
import {
|
||||
ECSignature,
|
||||
ZeroExError,
|
||||
Order,
|
||||
SignedOrder,
|
||||
Web3Provider,
|
||||
ZeroExConfig,
|
||||
OrderStateWatcherConfig,
|
||||
TransactionReceiptWithDecodedLogs,
|
||||
} from './types';
|
||||
import {zeroExConfigSchema} from './schemas/zero_ex_config_schema';
|
||||
|
||||
// Customize our BigNumber instances
|
||||
bigNumberConfigs.configure();
|
||||
@@ -59,7 +68,13 @@ export class ZeroEx {
|
||||
* tokenTransferProxy smart contract.
|
||||
*/
|
||||
public proxy: TokenTransferProxyWrapper;
|
||||
/**
|
||||
* An instance of the OrderStateWatcher class containing methods for watching a set of orders for relevant
|
||||
* blockchain state changes.
|
||||
*/
|
||||
public orderStateWatcher: OrderStateWatcher;
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _abiDecoder: AbiDecoder;
|
||||
/**
|
||||
* Verifies that the elliptic curve signature `signature` was generated
|
||||
* by signing `data` with the private key corresponding to the `signerAddress` address.
|
||||
@@ -70,22 +85,11 @@ export class ZeroEx {
|
||||
*/
|
||||
public static isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
|
||||
assert.isHexString('data', data);
|
||||
assert.doesConformToSchema('signature', signature, ecSignatureSchema);
|
||||
assert.doesConformToSchema('signature', signature, schemas.ecSignatureSchema);
|
||||
assert.isETHAddressHex('signerAddress', signerAddress);
|
||||
|
||||
const dataBuff = ethUtil.toBuffer(data);
|
||||
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
|
||||
try {
|
||||
const pubKey = ethUtil.ecrecover(
|
||||
msgHashBuff,
|
||||
signature.v,
|
||||
ethUtil.toBuffer(signature.r),
|
||||
ethUtil.toBuffer(signature.s));
|
||||
const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
|
||||
return retrievedAddress === signerAddress;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
const isValidSignature = signatureUtils.isValidSignature(data, signature, signerAddress);
|
||||
return isValidSignature;
|
||||
}
|
||||
/**
|
||||
* Generates a pseudo-random 256-bit salt.
|
||||
@@ -93,7 +97,7 @@ export class ZeroEx {
|
||||
* and will not collide with other outstanding orders that are identical in all other parameters.
|
||||
* @return A pseudo-random 256-bit number that can be used as a salt.
|
||||
*/
|
||||
public static generatePseudoRandomSalt(): BigNumber.BigNumber {
|
||||
public static generatePseudoRandomSalt(): BigNumber {
|
||||
// BigNumber.random returns a pseudo-random number between 0 & 1 with a passed in number of decimal places.
|
||||
// Source: https://mikemcl.github.io/bignumber.js/#random
|
||||
const randomNumber = BigNumber.random(constants.MAX_DIGITS_IN_UNSIGNED_256_INT);
|
||||
@@ -113,7 +117,7 @@ export class ZeroEx {
|
||||
// format, we only assert that we were indeed passed a string.
|
||||
assert.isString('orderHash', orderHash);
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const isValidOrderHash = schemaValidator.validate(orderHash, orderHashSchema).valid;
|
||||
const isValidOrderHash = schemaValidator.validate(orderHash, schemas.orderHashSchema).valid;
|
||||
return isValidOrderHash;
|
||||
}
|
||||
/**
|
||||
@@ -124,7 +128,7 @@ export class ZeroEx {
|
||||
* @param decimals The number of decimal places the unit amount has.
|
||||
* @return The amount in units.
|
||||
*/
|
||||
public static toUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.BigNumber {
|
||||
public static toUnitAmount(amount: BigNumber, decimals: number): BigNumber {
|
||||
assert.isBigNumber('amount', amount);
|
||||
assert.isNumber('decimals', decimals);
|
||||
|
||||
@@ -140,7 +144,7 @@ export class ZeroEx {
|
||||
* @param decimals The number of decimal places the unit amount has.
|
||||
* @return The amount in baseUnits.
|
||||
*/
|
||||
public static toBaseUnitAmount(amount: BigNumber.BigNumber, decimals: number): BigNumber.BigNumber {
|
||||
public static toBaseUnitAmount(amount: BigNumber, decimals: number): BigNumber {
|
||||
assert.isBigNumber('amount', amount);
|
||||
assert.isNumber('decimals', decimals);
|
||||
|
||||
@@ -154,7 +158,7 @@ export class ZeroEx {
|
||||
* @return The resulting orderHash from hashing the supplied order.
|
||||
*/
|
||||
public static getOrderHashHex(order: Order|SignedOrder): string {
|
||||
assert.doesConformToSchema('order', order, orderSchema);
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
const orderHashHex = utils.getOrderHashHex(order);
|
||||
return orderHashHex;
|
||||
}
|
||||
@@ -162,15 +166,48 @@ export class ZeroEx {
|
||||
* Instantiates a new ZeroEx instance that provides the public interface to the 0x.js library.
|
||||
* @param provider The Web3.js Provider instance you would like the 0x.js library to use for interacting with
|
||||
* the Ethereum network.
|
||||
* @param config The configuration object. Look up the type for the description.
|
||||
* @return An instance of the 0x.js ZeroEx class.
|
||||
*/
|
||||
constructor(provider: Web3Provider) {
|
||||
this._web3Wrapper = new Web3Wrapper(provider);
|
||||
this.token = new TokenWrapper(this._web3Wrapper);
|
||||
this.proxy = new TokenTransferProxyWrapper(this._web3Wrapper);
|
||||
this.exchange = new ExchangeWrapper(this._web3Wrapper, this.token);
|
||||
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper);
|
||||
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token);
|
||||
constructor(provider: Web3Provider, config?: ZeroExConfig) {
|
||||
assert.isWeb3Provider('provider', provider);
|
||||
if (!_.isUndefined(config)) {
|
||||
assert.doesConformToSchema('config', config, zeroExConfigSchema);
|
||||
}
|
||||
const artifactJSONs = _.values(artifacts);
|
||||
const abiArrays = _.map(artifactJSONs, artifact => artifact.abi);
|
||||
this._abiDecoder = new AbiDecoder(abiArrays);
|
||||
const gasPrice = _.isUndefined(config) ? undefined : config.gasPrice;
|
||||
const defaults = {
|
||||
gasPrice,
|
||||
};
|
||||
this._web3Wrapper = new Web3Wrapper(provider, defaults);
|
||||
this.token = new TokenWrapper(
|
||||
this._web3Wrapper,
|
||||
this._abiDecoder,
|
||||
this._getTokenTransferProxyAddressAsync.bind(this),
|
||||
);
|
||||
const exchageContractAddressIfExists = _.isUndefined(config) ? undefined : config.exchangeContractAddress;
|
||||
this.exchange = new ExchangeWrapper(
|
||||
this._web3Wrapper,
|
||||
this._abiDecoder,
|
||||
this.token,
|
||||
exchageContractAddressIfExists,
|
||||
);
|
||||
this.proxy = new TokenTransferProxyWrapper(
|
||||
this._web3Wrapper,
|
||||
this._getTokenTransferProxyAddressAsync.bind(this),
|
||||
);
|
||||
const tokenRegistryContractAddressIfExists = _.isUndefined(config) ?
|
||||
undefined :
|
||||
config.tokenRegistryContractAddress;
|
||||
this.tokenRegistry = new TokenRegistryWrapper(this._web3Wrapper, tokenRegistryContractAddressIfExists);
|
||||
const etherTokenContractAddressIfExists = _.isUndefined(config) ? undefined : config.etherTokenContractAddress;
|
||||
this.etherToken = new EtherTokenWrapper(this._web3Wrapper, this.token, etherTokenContractAddressIfExists);
|
||||
const orderWatcherConfig = _.isUndefined(config) ? undefined : config.orderWatcherConfig;
|
||||
this.orderStateWatcher = new OrderStateWatcher(
|
||||
this._web3Wrapper, this._abiDecoder, this.token, this.exchange, orderWatcherConfig,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Sets a new web3 provider for 0x.js. Updating the provider will stop all
|
||||
@@ -243,4 +280,54 @@ export class ZeroEx {
|
||||
|
||||
throw new Error(ZeroExError.InvalidSignature);
|
||||
}
|
||||
/**
|
||||
* Waits for a transaction to be mined and returns the transaction receipt.
|
||||
* @param txHash Transaction hash
|
||||
* @param pollingIntervalMs How often (in ms) should we check if the transaction is mined.
|
||||
* @param timeoutMs How long (in ms) to poll for transaction mined until aborting.
|
||||
* @return Transaction receipt with decoded log args.
|
||||
*/
|
||||
public async awaitTransactionMinedAsync(
|
||||
txHash: string, pollingIntervalMs = 1000, timeoutMs?: number): Promise<TransactionReceiptWithDecodedLogs> {
|
||||
let timeoutExceeded = false;
|
||||
if (timeoutMs) {
|
||||
setTimeout(() => timeoutExceeded = true, timeoutMs);
|
||||
}
|
||||
|
||||
const txReceiptPromise = new Promise(
|
||||
(resolve: (receipt: TransactionReceiptWithDecodedLogs) => void, reject) => {
|
||||
const intervalId = intervalUtils.setAsyncExcludingInterval(async () => {
|
||||
if (timeoutExceeded) {
|
||||
intervalUtils.clearAsyncExcludingInterval(intervalId);
|
||||
return reject(ZeroExError.TransactionMiningTimeout);
|
||||
}
|
||||
|
||||
const transactionReceipt = await this._web3Wrapper.getTransactionReceiptAsync(txHash);
|
||||
if (!_.isNull(transactionReceipt)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(intervalId);
|
||||
const logsWithDecodedArgs = _.map(
|
||||
transactionReceipt.logs,
|
||||
this._abiDecoder.tryToDecodeLogOrNoop.bind(this._abiDecoder),
|
||||
);
|
||||
const transactionReceiptWithDecodedLogArgs: TransactionReceiptWithDecodedLogs = {
|
||||
...transactionReceipt,
|
||||
logs: logsWithDecodedArgs,
|
||||
};
|
||||
resolve(transactionReceiptWithDecodedLogArgs);
|
||||
}
|
||||
}, pollingIntervalMs);
|
||||
});
|
||||
|
||||
return txReceiptPromise;
|
||||
}
|
||||
/*
|
||||
* HACK: `TokenWrapper` needs a token transfer proxy address. `TokenTransferProxy` address is fetched from
|
||||
* an `ExchangeWrapper`. `ExchangeWrapper` needs `TokenWrapper` to validate orders, creating a dependency cycle.
|
||||
* In order to break this - we create this function here and pass it as a parameter to the `TokenWrapper`
|
||||
* and `ProxyWrapper`.
|
||||
*/
|
||||
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
|
||||
const tokenTransferProxyAddress = await (this.exchange as any)._getTokenTransferProxyAddressAsync();
|
||||
return tokenTransferProxyAddress;
|
||||
}
|
||||
}
|
||||
14
packages/0x.js/src/artifacts.ts
Normal file
14
packages/0x.js/src/artifacts.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import {Artifact} from './types';
|
||||
import * as TokenArtifact from './artifacts/Token.json';
|
||||
import * as ExchangeArtifact from './artifacts/Exchange.json';
|
||||
import * as EtherTokenArtifact from './artifacts/EtherToken.json';
|
||||
import * as TokenRegistryArtifact from './artifacts/TokenRegistry.json';
|
||||
import * as TokenTransferProxyArtifact from './artifacts/TokenTransferProxy.json';
|
||||
|
||||
export const artifacts = {
|
||||
TokenArtifact: TokenArtifact as any as Artifact,
|
||||
ExchangeArtifact: ExchangeArtifact as any as Artifact,
|
||||
EtherTokenArtifact: EtherTokenArtifact as any as Artifact,
|
||||
TokenRegistryArtifact: TokenRegistryArtifact as any as Artifact,
|
||||
TokenTransferProxyArtifact: TokenTransferProxyArtifact as any as Artifact,
|
||||
};
|
||||
@@ -286,6 +286,57 @@
|
||||
"updated_at": 1502488087000,
|
||||
"address": "0x2956356cd2a2bf3202f771f50d3d14a367b48070"
|
||||
},
|
||||
"3": {
|
||||
"links": {},
|
||||
"events": {
|
||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
"0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "_spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "_value",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1506602007000,
|
||||
"address": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a"
|
||||
},
|
||||
"42": {
|
||||
"links": {},
|
||||
"events": {
|
||||
@@ -391,4 +442,4 @@
|
||||
},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1503318938233
|
||||
}
|
||||
}
|
||||
@@ -725,6 +725,139 @@
|
||||
"updated_at": 1502480340000,
|
||||
"address": "0x12459c951127e0c374ff9105dda097662a027093"
|
||||
},
|
||||
"3": {
|
||||
"links": {},
|
||||
"events": {
|
||||
"0x0d0b9391970d9a25552f37d436d2aae2925e2bfe1b2a923754bada030c498cb3": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "maker",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "taker",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "feeRecipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "makerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "takerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "filledMakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "filledTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "paidMakerFee",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "paidTakerFee",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "tokens",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "LogFill",
|
||||
"type": "event"
|
||||
},
|
||||
"0x67d66f160bc93d925d05dae1794c90d2d6d6688b29b84ff069398a9b04587131": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "maker",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "feeRecipient",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "makerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "takerToken",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "cancelledMakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "cancelledTakerTokenAmount",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "tokens",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "LogCancel",
|
||||
"type": "event"
|
||||
},
|
||||
"0x36d86c59e00bd73dc19ba3adfe068e4b64ac7e92be35546adeddf1b956a87e90": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "errorId",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "orderHash",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "LogError",
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1506602007000,
|
||||
"address": "0x479cc461fecd078f766ecc58533d6f69580cf3ac"
|
||||
},
|
||||
"42": {
|
||||
"links": {},
|
||||
"events": {
|
||||
@@ -994,4 +1127,4 @@
|
||||
},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1503318938231
|
||||
}
|
||||
}
|
||||
@@ -698,6 +698,175 @@
|
||||
"updated_at": 1502488442000,
|
||||
"address": "0x926a74c5c36adf004c87399e65f75628b0f98d2c"
|
||||
},
|
||||
"3": {
|
||||
"links": {},
|
||||
"events": {
|
||||
"0xd8d928b0b50ca11d9dc273236b46f3526515b03602f71f3a6af4f45bd9fa9144": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "decimals",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "ipfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogAddToken",
|
||||
"type": "event"
|
||||
},
|
||||
"0x32c54f1e2ea75844ded7517e7dbcd3895da7cd0c28f9ab9f9cf6ecf5f83762c6": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "name",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "symbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "decimals",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "ipfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "swarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogRemoveToken",
|
||||
"type": "event"
|
||||
},
|
||||
"0x4a6dbfc867b179991dec22ff19960f0a94d8d9d891fc556f547764670340e8ae": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldName",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newName",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenNameChange",
|
||||
"type": "event"
|
||||
},
|
||||
"0x53d878a6530e56c9bc96548fa0a8cae4f1d1f49c86b0e934c086b992ebb6998f": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldSymbol",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newSymbol",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenSymbolChange",
|
||||
"type": "event"
|
||||
},
|
||||
"0x5b19f79ac4e8cfa820815502e11615f1a449e28155dc289ec5cac1a11f908694": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldIpfsHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newIpfsHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenIpfsHashChange",
|
||||
"type": "event"
|
||||
},
|
||||
"0xc3168fdc13112e44a031057dbf6c609b33353addb4d8037d24543e22cbfe2acd": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "token",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "oldSwarmHash",
|
||||
"type": "bytes"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"name": "newSwarmHash",
|
||||
"type": "bytes"
|
||||
}
|
||||
],
|
||||
"name": "LogTokenSwarmHashChange",
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1506602007000,
|
||||
"address": "0x6b1a50f0bb5a7995444bd3877b22dc89c62843ed"
|
||||
},
|
||||
"42": {
|
||||
"links": {},
|
||||
"events": {
|
||||
@@ -1039,4 +1208,4 @@
|
||||
},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1503318938228
|
||||
}
|
||||
}
|
||||
@@ -168,131 +168,7 @@
|
||||
}
|
||||
],
|
||||
"unlinked_binary": "0x60606040525b60008054600160a060020a03191633600160a060020a03161790555b5b6106e6806100316000396000f300606060405236156100725763ffffffff60e060020a60003504166315dacbea811461007457806342f1181e146100b3578063494503d4146100d157806370712939146101005780638da5cb5b1461011e578063b91816111461014a578063d39de6e91461017a578063f2fde38b146101e5575bfe5b341561007c57fe5b61009f600160a060020a0360043581169060243581169060443516606435610203565b604080519115158252519081900360200190f35b34156100bb57fe5b6100cf600160a060020a03600435166102ae565b005b34156100d957fe5b6100e4600435610390565b60408051600160a060020a039092168252519081900360200190f35b341561010857fe5b6100cf600160a060020a03600435166103c2565b005b341561012657fe5b6100e461055a565b60408051600160a060020a039092168252519081900360200190f35b341561015257fe5b61009f600160a060020a0360043516610569565b604080519115158252519081900360200190f35b341561018257fe5b61018a61057e565b60408051602080825283518183015283519192839290830191858101910280838382156101d2575b8051825260208311156101d257601f1990920191602091820191016101b2565b5050509050019250505060405180910390f35b34156101ed57fe5b6100cf600160a060020a03600435166105e7565b005b600160a060020a03331660009081526001602052604081205460ff16151561022b5760006000fd5b6040805160006020918201819052825160e060020a6323b872dd028152600160a060020a0388811660048301528781166024830152604482018790529351938916936323b872dd9360648084019491938390030190829087803b151561028d57fe5b6102c65a03f1151561029b57fe5b5050604051519150505b5b949350505050565b60005433600160a060020a039081169116146102ca5760006000fd5b600160a060020a038116600090815260016020526040902054819060ff16156102f35760006000fd5b600160a060020a0382166000908152600160208190526040909120805460ff191682179055600280549091810161032a8382610633565b916000526020600020900160005b81546101009190910a600160a060020a0381810219909216868316918202179092556040513390911692507f94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca90600090a35b5b505b50565b600280548290811061039e57fe5b906000526020600020900160005b915054906101000a9004600160a060020a031681565b6000805433600160a060020a039081169116146103df5760006000fd5b600160a060020a038216600090815260016020526040902054829060ff1615156104095760006000fd5b600160a060020a0383166000908152600160205260408120805460ff1916905591505b6002548210156105195782600160a060020a031660028381548110151561044f57fe5b906000526020600020900160005b9054906101000a9004600160a060020a0316600160a060020a0316141561050d5760028054600019810190811061049057fe5b906000526020600020900160005b9054906101000a9004600160a060020a03166002838154811015156104bf57fe5b906000526020600020900160005b6101000a815481600160a060020a030219169083600160a060020a0316021790555060016002818180549050039150816105079190610633565b50610519565b5b60019091019061042c565b604051600160a060020a0333811691908516907ff5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c90600090a35b5b505b5050565b600054600160a060020a031681565b60016020526000908152604090205460ff1681565b610586610687565b60028054806020026020016040519081016040528092919081815260200182805480156105dc57602002820191906000526020600020905b8154600160a060020a031681526001909101906020018083116105be575b505050505090505b90565b60005433600160a060020a039081169116146106035760006000fd5b600160a060020a0381161561038d5760008054600160a060020a031916600160a060020a0383161790555b5b5b50565b81548183558181151161055357600083815260209020610553918101908301610699565b5b505050565b81548183558181151161055357600083815260209020610553918101908301610699565b5b505050565b60408051602081019091526000815290565b6105e491905b808211156106b3576000815560010161069f565b5090565b905600a165627a7a72305820d2924957bb88a128789172e164d874fe5445218fc2dde2f5eb265839a1f341a20029",
|
||||
"networks": {
|
||||
"1": {
|
||||
"links": {},
|
||||
"events": {
|
||||
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressAdded",
|
||||
"type": "event"
|
||||
},
|
||||
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressRemoved",
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1502478966000,
|
||||
"address": "0x8da0d80f5007ef1e431dd2127178d224e32c2ef4"
|
||||
},
|
||||
"42": {
|
||||
"links": {},
|
||||
"events": {
|
||||
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressAdded",
|
||||
"type": "event"
|
||||
},
|
||||
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressRemoved",
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1502391794384,
|
||||
"address": "0x087eed4bc1ee3de49befbd66c662b434b15d49d4"
|
||||
},
|
||||
"50": {
|
||||
"links": {},
|
||||
"events": {
|
||||
"0x94bb87f4c15c4587ff559a7584006fa01ddf9299359be6b512b94527aa961aca": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressAdded",
|
||||
"type": "event"
|
||||
},
|
||||
"0xf5b347a1e40749dd050f5f07fbdbeb7e3efa9756903044dd29401fd1d4bb4a1c": {
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "target",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "LogAuthorizedAddressRemoved",
|
||||
"type": "event"
|
||||
}
|
||||
},
|
||||
"updated_at": 1503318938227,
|
||||
"address": "0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c"
|
||||
}
|
||||
},
|
||||
"networks": {},
|
||||
"schema_version": "0.0.5",
|
||||
"updated_at": 1503318938227
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const bigNumberConfigs = {
|
||||
configure() {
|
||||
80
packages/0x.js/src/contract.ts
Normal file
80
packages/0x.js/src/contract.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import * as Web3 from 'web3';
|
||||
import * as _ from 'lodash';
|
||||
import promisify = require('es6-promisify');
|
||||
import {SchemaValidator, schemas} from '@0xproject/json-schemas';
|
||||
import {AbiType} from './types';
|
||||
|
||||
export class Contract implements Web3.ContractInstance {
|
||||
public address: string;
|
||||
public abi: Web3.ContractAbi;
|
||||
private contract: Web3.ContractInstance;
|
||||
private defaults: Partial<Web3.TxData>;
|
||||
private validator: SchemaValidator;
|
||||
// This class instance is going to be populated with functions and events depending on the ABI
|
||||
// and we don't know their types in advance
|
||||
[name: string]: any;
|
||||
constructor(web3ContractInstance: Web3.ContractInstance, defaults: Partial<Web3.TxData>) {
|
||||
this.contract = web3ContractInstance;
|
||||
this.address = web3ContractInstance.address;
|
||||
this.abi = web3ContractInstance.abi;
|
||||
this.defaults = defaults;
|
||||
this.populateEvents();
|
||||
this.populateFunctions();
|
||||
this.validator = new SchemaValidator();
|
||||
}
|
||||
private populateFunctions(): void {
|
||||
const functionsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Function);
|
||||
_.forEach(functionsAbi, (functionAbi: Web3.MethodAbi) => {
|
||||
if (functionAbi.constant) {
|
||||
const cbStyleCallFunction = this.contract[functionAbi.name].call;
|
||||
this[functionAbi.name] = {
|
||||
callAsync: promisify(cbStyleCallFunction, this.contract),
|
||||
};
|
||||
} else {
|
||||
const cbStyleFunction = this.contract[functionAbi.name];
|
||||
const cbStyleEstimateGasFunction = this.contract[functionAbi.name].estimateGas;
|
||||
this[functionAbi.name] = {
|
||||
estimateGasAsync: promisify(cbStyleEstimateGasFunction, this.contract),
|
||||
sendTransactionAsync: this.promisifyWithDefaultParams(cbStyleFunction),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
private populateEvents(): void {
|
||||
const eventsAbi = _.filter(this.abi, abiPart => abiPart.type === AbiType.Event);
|
||||
_.forEach(eventsAbi, (eventAbi: Web3.EventAbi) => {
|
||||
this[eventAbi.name] = this.contract[eventAbi.name];
|
||||
});
|
||||
}
|
||||
private promisifyWithDefaultParams(fn: (...args: any[]) => void): (...args: any[]) => Promise<any> {
|
||||
const promisifiedWithDefaultParams = (...args: any[]) => {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const lastArg = args[args.length - 1];
|
||||
let txData: Partial<Web3.TxData> = {};
|
||||
if (this.isTxData(lastArg)) {
|
||||
txData = args.pop();
|
||||
}
|
||||
txData = {
|
||||
...this.defaults,
|
||||
...txData,
|
||||
};
|
||||
const callback = (err: Error, data: any) => {
|
||||
if (_.isNull(err)) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject(err);
|
||||
}
|
||||
};
|
||||
args.push(txData);
|
||||
args.push(callback);
|
||||
fn.apply(this.contract, args);
|
||||
});
|
||||
return promise;
|
||||
};
|
||||
return promisifiedWithDefaultParams;
|
||||
}
|
||||
private isTxData(lastArg: any): boolean {
|
||||
const isValid = this.validator.isValid(lastArg, schemas.txDataSchema);
|
||||
return isValid;
|
||||
}
|
||||
}
|
||||
152
packages/0x.js/src/contract_wrappers/contract_wrapper.ts
Normal file
152
packages/0x.js/src/contract_wrappers/contract_wrapper.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import {BlockAndLogStreamer, Block} from 'ethereumjs-blockstream';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {AbiDecoder} from '../utils/abi_decoder';
|
||||
import {
|
||||
ZeroExError,
|
||||
InternalZeroExError,
|
||||
Artifact,
|
||||
LogWithDecodedArgs,
|
||||
RawLog,
|
||||
ContractEvents,
|
||||
SubscriptionOpts,
|
||||
IndexedFilterValues,
|
||||
EventCallback,
|
||||
BlockParamLiteral,
|
||||
ContractEventArgs,
|
||||
} from '../types';
|
||||
import {constants} from '../utils/constants';
|
||||
import {intervalUtils} from '../utils/interval_utils';
|
||||
import {filterUtils} from '../utils/filter_utils';
|
||||
|
||||
export class ContractWrapper {
|
||||
protected _web3Wrapper: Web3Wrapper;
|
||||
private _abiDecoder?: AbiDecoder;
|
||||
private _blockAndLogStreamer: BlockAndLogStreamer|undefined;
|
||||
private _blockAndLogStreamInterval: NodeJS.Timer;
|
||||
private _filters: {[filterToken: string]: Web3.FilterObject};
|
||||
private _filterCallbacks: {[filterToken: string]: EventCallback<ContractEventArgs>};
|
||||
private _onLogAddedSubscriptionToken: string|undefined;
|
||||
private _onLogRemovedSubscriptionToken: string|undefined;
|
||||
constructor(web3Wrapper: Web3Wrapper, abiDecoder?: AbiDecoder) {
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
this._abiDecoder = abiDecoder;
|
||||
this._filters = {};
|
||||
this._filterCallbacks = {};
|
||||
this._blockAndLogStreamer = undefined;
|
||||
this._onLogAddedSubscriptionToken = undefined;
|
||||
this._onLogRemovedSubscriptionToken = undefined;
|
||||
}
|
||||
/**
|
||||
* Cancels all existing subscriptions
|
||||
*/
|
||||
public unsubscribeAll(): void {
|
||||
const filterTokens = _.keys(this._filterCallbacks);
|
||||
_.each(filterTokens, filterToken => {
|
||||
this._unsubscribe(filterToken);
|
||||
});
|
||||
}
|
||||
protected _unsubscribe(filterToken: string, err?: Error): void {
|
||||
if (_.isUndefined(this._filters[filterToken])) {
|
||||
throw new Error(ZeroExError.SubscriptionNotFound);
|
||||
}
|
||||
if (!_.isUndefined(err)) {
|
||||
const callback = this._filterCallbacks[filterToken];
|
||||
callback(err, undefined);
|
||||
}
|
||||
delete this._filters[filterToken];
|
||||
delete this._filterCallbacks[filterToken];
|
||||
if (_.isEmpty(this._filters)) {
|
||||
this._stopBlockAndLogStream();
|
||||
}
|
||||
}
|
||||
protected _subscribe<ArgsType extends ContractEventArgs>(
|
||||
address: string, eventName: ContractEvents, indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi,
|
||||
callback: EventCallback<ArgsType>): string {
|
||||
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi);
|
||||
if (_.isUndefined(this._blockAndLogStreamer)) {
|
||||
this._startBlockAndLogStream();
|
||||
}
|
||||
const filterToken = filterUtils.generateUUID();
|
||||
this._filters[filterToken] = filter;
|
||||
this._filterCallbacks[filterToken] = callback;
|
||||
return filterToken;
|
||||
}
|
||||
protected async _getLogsAsync<ArgsType extends ContractEventArgs>(
|
||||
address: string, eventName: ContractEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
const filter = filterUtils.getFilter(address, eventName, indexFilterValues, abi, subscriptionOpts);
|
||||
const logs = await this._web3Wrapper.getLogsAsync(filter);
|
||||
const logsWithDecodedArguments = _.map(logs, this._tryToDecodeLogOrNoop.bind(this));
|
||||
return logsWithDecodedArguments;
|
||||
}
|
||||
protected _tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
|
||||
log: Web3.LogEntry): LogWithDecodedArgs<ArgsType>|RawLog {
|
||||
if (_.isUndefined(this._abiDecoder)) {
|
||||
throw new Error(InternalZeroExError.NoAbiDecoder);
|
||||
}
|
||||
const logWithDecodedArgs = this._abiDecoder.tryToDecodeLogOrNoop(log);
|
||||
return logWithDecodedArgs;
|
||||
}
|
||||
protected async _instantiateContractIfExistsAsync<ContractType extends Web3.ContractInstance>(
|
||||
artifact: Artifact, addressIfExists?: string): Promise<ContractType> {
|
||||
const contractInstance =
|
||||
await this._web3Wrapper.getContractInstanceFromArtifactAsync<ContractType>(artifact, addressIfExists);
|
||||
return contractInstance;
|
||||
}
|
||||
private _onLogStateChanged<ArgsType extends ContractEventArgs>(removed: boolean, log: Web3.LogEntry): void {
|
||||
_.forEach(this._filters, (filter: Web3.FilterObject, filterToken: string) => {
|
||||
if (filterUtils.matchesFilter(log, filter)) {
|
||||
const decodedLog = this._tryToDecodeLogOrNoop(log) as LogWithDecodedArgs<ArgsType>;
|
||||
const logEvent = {
|
||||
...decodedLog,
|
||||
removed,
|
||||
};
|
||||
this._filterCallbacks[filterToken](null, logEvent);
|
||||
}
|
||||
});
|
||||
}
|
||||
private _startBlockAndLogStream(): void {
|
||||
this._blockAndLogStreamer = new BlockAndLogStreamer(
|
||||
this._web3Wrapper.getBlockAsync.bind(this._web3Wrapper),
|
||||
this._web3Wrapper.getLogsAsync.bind(this._web3Wrapper),
|
||||
);
|
||||
const catchAllLogFilter = {};
|
||||
this._blockAndLogStreamer.addLogFilter(catchAllLogFilter);
|
||||
this._blockAndLogStreamInterval = intervalUtils.setAsyncExcludingInterval(
|
||||
this._reconcileBlockAsync.bind(this), constants.DEFAULT_BLOCK_POLLING_INTERVAL,
|
||||
);
|
||||
let removed = false;
|
||||
this._onLogAddedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogAdded(
|
||||
this._onLogStateChanged.bind(this, removed),
|
||||
);
|
||||
removed = true;
|
||||
this._onLogRemovedSubscriptionToken = this._blockAndLogStreamer.subscribeToOnLogRemoved(
|
||||
this._onLogStateChanged.bind(this, removed),
|
||||
);
|
||||
}
|
||||
private _stopBlockAndLogStream(): void {
|
||||
(this._blockAndLogStreamer as BlockAndLogStreamer).unsubscribeFromOnLogAdded(
|
||||
this._onLogAddedSubscriptionToken as string);
|
||||
(this._blockAndLogStreamer as BlockAndLogStreamer).unsubscribeFromOnLogRemoved(
|
||||
this._onLogRemovedSubscriptionToken as string);
|
||||
intervalUtils.clearAsyncExcludingInterval(this._blockAndLogStreamInterval);
|
||||
delete this._blockAndLogStreamer;
|
||||
}
|
||||
private async _reconcileBlockAsync(): Promise<void> {
|
||||
try {
|
||||
const latestBlock = await this._web3Wrapper.getBlockAsync(BlockParamLiteral.Latest);
|
||||
// We need to coerce to Block type cause Web3.Block includes types for mempool blocks
|
||||
if (!_.isUndefined(this._blockAndLogStreamer)) {
|
||||
// If we clear the interval while fetching the block - this._blockAndLogStreamer will be undefined
|
||||
this._blockAndLogStreamer.reconcileNewBlock(latestBlock as any as Block);
|
||||
}
|
||||
} catch (err) {
|
||||
const filterTokens = _.keys(this._filterCallbacks);
|
||||
_.each(filterTokens, filterToken => {
|
||||
this._unsubscribe(filterToken, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
import * as _ from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import {TokenWrapper} from './token_wrapper';
|
||||
import {EtherTokenContract, ZeroExError} from '../types';
|
||||
import {assert} from '../utils/assert';
|
||||
import * as EtherTokenArtifacts from '../artifacts/EtherToken.json';
|
||||
import {artifacts} from '../artifacts';
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with a wrapped Ether ERC20 token contract.
|
||||
@@ -13,9 +14,11 @@ import * as EtherTokenArtifacts from '../artifacts/EtherToken.json';
|
||||
export class EtherTokenWrapper extends ContractWrapper {
|
||||
private _etherTokenContractIfExists?: EtherTokenContract;
|
||||
private _tokenWrapper: TokenWrapper;
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
|
||||
private _contractAddressIfExists?: string;
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper, contractAddressIfExists?: string) {
|
||||
super(web3Wrapper);
|
||||
this._tokenWrapper = tokenWrapper;
|
||||
this._contractAddressIfExists = contractAddressIfExists;
|
||||
}
|
||||
/**
|
||||
* Deposit ETH into the Wrapped ETH smart contract and issues the equivalent number of wrapped ETH tokens
|
||||
@@ -23,28 +26,31 @@ export class EtherTokenWrapper extends ContractWrapper {
|
||||
* for ETH.
|
||||
* @param amountInWei Amount of ETH in Wei the caller wishes to deposit.
|
||||
* @param depositor The hex encoded user Ethereum address that would like to make the deposit.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async depositAsync(amountInWei: BigNumber.BigNumber, depositor: string): Promise<void> {
|
||||
assert.isBigNumber('amountInWei', amountInWei);
|
||||
public async depositAsync(amountInWei: BigNumber, depositor: string): Promise<string> {
|
||||
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
|
||||
await assert.isSenderAddressAsync('depositor', depositor, this._web3Wrapper);
|
||||
|
||||
const ethBalanceInWei = await this._web3Wrapper.getBalanceInWeiAsync(depositor);
|
||||
assert.assert(ethBalanceInWei.gte(amountInWei), ZeroExError.InsufficientEthBalanceForDeposit);
|
||||
|
||||
const wethContract = await this._getEtherTokenContractAsync();
|
||||
await wethContract.deposit({
|
||||
const txHash = await wethContract.deposit.sendTransactionAsync({
|
||||
from: depositor,
|
||||
value: amountInWei,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Withdraw ETH to the withdrawer's address from the wrapped ETH smart contract in exchange for the
|
||||
* equivalent number of wrapped ETH tokens.
|
||||
* @param amountInWei Amount of ETH in Wei the caller wishes to withdraw.
|
||||
* @param withdrawer The hex encoded user Ethereum address that would like to make the withdrawl.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async withdrawAsync(amountInWei: BigNumber.BigNumber, withdrawer: string): Promise<void> {
|
||||
assert.isBigNumber('amountInWei', amountInWei);
|
||||
public async withdrawAsync(amountInWei: BigNumber, withdrawer: string): Promise<string> {
|
||||
assert.isValidBaseUnitAmount('amountInWei', amountInWei);
|
||||
await assert.isSenderAddressAsync('withdrawer', withdrawer, this._web3Wrapper);
|
||||
|
||||
const wethContractAddress = await this.getContractAddressAsync();
|
||||
@@ -52,9 +58,10 @@ export class EtherTokenWrapper extends ContractWrapper {
|
||||
assert.assert(WETHBalanceInBaseUnits.gte(amountInWei), ZeroExError.InsufficientWEthBalanceForWithdrawal);
|
||||
|
||||
const wethContract = await this._getEtherTokenContractAsync();
|
||||
await wethContract.withdraw(amountInWei, {
|
||||
const txHash = await wethContract.withdraw.sendTransactionAsync(amountInWei, {
|
||||
from: withdrawer,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Retrieves the Wrapped Ether token contract address
|
||||
@@ -71,7 +78,9 @@ export class EtherTokenWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(this._etherTokenContractIfExists)) {
|
||||
return this._etherTokenContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((EtherTokenArtifacts as any));
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<EtherTokenContract>(
|
||||
artifacts.EtherTokenArtifact, this._contractAddressIfExists,
|
||||
);
|
||||
this._etherTokenContractIfExists = contractInstance as EtherTokenContract;
|
||||
return this._etherTokenContractIfExists;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {schemas} from '@0xproject/json-schemas';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {
|
||||
ECSignature,
|
||||
@@ -11,47 +12,49 @@ import {
|
||||
OrderValues,
|
||||
OrderAddresses,
|
||||
Order,
|
||||
OrderFillOrKillRequest,
|
||||
SignedOrder,
|
||||
ContractEvent,
|
||||
ExchangeEvents,
|
||||
ContractEventEmitter,
|
||||
SubscriptionOpts,
|
||||
IndexedFilterValues,
|
||||
CreateContractEvent,
|
||||
ContractEventObj,
|
||||
ContractResponse,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
LogErrorContractEventArgs,
|
||||
LogFillContractEventArgs,
|
||||
LogCancelContractEventArgs,
|
||||
LogWithDecodedArgs,
|
||||
MethodOpts,
|
||||
ValidateOrderFillableOpts,
|
||||
OrderTransactionOpts,
|
||||
RawLog,
|
||||
EventCallback,
|
||||
ExchangeContractEventArgs,
|
||||
DecodedLogArgs,
|
||||
} from '../types';
|
||||
import {assert} from '../utils/assert';
|
||||
import {utils} from '../utils/utils';
|
||||
import {eventUtils} from '../utils/event_utils';
|
||||
import {OrderValidationUtils} from '../utils/order_validation_utils';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import {ecSignatureSchema} from '../schemas/ec_signature_schema';
|
||||
import {signedOrdersSchema} from '../schemas/signed_orders_schema';
|
||||
import {subscriptionOptsSchema} from '../schemas/subscription_opts_schema';
|
||||
import {indexFilterValuesSchema} from '../schemas/index_filter_values_schema';
|
||||
import {orderFillRequestsSchema} from '../schemas/order_fill_requests_schema';
|
||||
import {orderCancellationRequestsSchema} from '../schemas/order_cancel_schema';
|
||||
import {orderFillOrKillRequestsSchema} from '../schemas/order_fill_or_kill_requests_schema';
|
||||
import {orderHashSchema} from '../schemas/order_hash_schema';
|
||||
import {signedOrderSchema, orderSchema} from '../schemas/order_schemas';
|
||||
import {constants} from '../utils/constants';
|
||||
import {TokenWrapper} from './token_wrapper';
|
||||
import {decorators} from '../utils/decorators';
|
||||
import * as ExchangeArtifacts from '../artifacts/Exchange.json';
|
||||
import {AbiDecoder} from '../utils/abi_decoder';
|
||||
import {ExchangeTransferSimulator} from '../utils/exchange_transfer_simulator';
|
||||
import {artifacts} from '../artifacts';
|
||||
|
||||
const SHOULD_VALIDATE_BY_DEFAULT = true;
|
||||
|
||||
interface ExchangeContractErrCodesToMsgs {
|
||||
[exchangeContractErrCodes: number]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to calling methods and subscribing to
|
||||
* events of the 0x Exchange smart contract.
|
||||
*/
|
||||
export class ExchangeWrapper extends ContractWrapper {
|
||||
private _exchangeContractErrCodesToMsg = {
|
||||
private _exchangeContractIfExists?: ExchangeContract;
|
||||
private _orderValidationUtils: OrderValidationUtils;
|
||||
private _tokenWrapper: TokenWrapper;
|
||||
private _exchangeContractErrCodesToMsg: ExchangeContractErrCodesToMsgs = {
|
||||
[ExchangeContractErrCodes.ERROR_FILL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
|
||||
[ExchangeContractErrCodes.ERROR_CANCEL_EXPIRED]: ExchangeContractErrs.OrderFillExpired,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_NO_VALUE]: ExchangeContractErrs.OrderRemainingFillAmountZero,
|
||||
@@ -59,10 +62,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
[ExchangeContractErrCodes.ERROR_FILL_TRUNCATION]: ExchangeContractErrs.OrderFillRoundingError,
|
||||
[ExchangeContractErrCodes.ERROR_FILL_BALANCE_ALLOWANCE]: ExchangeContractErrs.FillBalanceAllowanceError,
|
||||
};
|
||||
private _exchangeContractIfExists?: ExchangeContract;
|
||||
private _exchangeLogEventEmitters: ContractEventEmitter[];
|
||||
private _orderValidationUtils: OrderValidationUtils;
|
||||
private _tokenWrapper: TokenWrapper;
|
||||
private _contractAddressIfExists?: string;
|
||||
private static _getOrderAddressesAndValues(order: Order): [OrderAddresses, OrderValues] {
|
||||
const orderAddresses: OrderAddresses = [
|
||||
order.maker,
|
||||
@@ -81,11 +81,12 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
];
|
||||
return [orderAddresses, orderValues];
|
||||
}
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenWrapper: TokenWrapper) {
|
||||
super(web3Wrapper);
|
||||
constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder,
|
||||
tokenWrapper: TokenWrapper, contractAddressIfExists?: string) {
|
||||
super(web3Wrapper, abiDecoder);
|
||||
this._tokenWrapper = tokenWrapper;
|
||||
this._orderValidationUtils = new OrderValidationUtils(tokenWrapper, this);
|
||||
this._exchangeLogEventEmitters = [];
|
||||
this._contractAddressIfExists = contractAddressIfExists;
|
||||
}
|
||||
/**
|
||||
* Returns the unavailable takerAmount of an order. Unavailable amount is defined as the total
|
||||
@@ -93,13 +94,18 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* subtracting the unavailable amount from the total order takerAmount.
|
||||
* @param orderHash The hex encoded orderHash for which you would like to retrieve the
|
||||
* unavailable takerAmount.
|
||||
* @return The amount of the order (in taker tokens) that has either been filled or canceled.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
* @return The amount of the order (in taker tokens) that has either been filled or cancelled.
|
||||
*/
|
||||
public async getUnavailableTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, orderHashSchema);
|
||||
public async getUnavailableTakerAmountAsync(orderHash: string,
|
||||
methodOpts?: MethodOpts): Promise<BigNumber> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.call(orderHash);
|
||||
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
|
||||
let unavailableTakerTokenAmount = await exchangeContract.getUnavailableTakerTokenAmount.callAsync(
|
||||
orderHash, defaultBlock,
|
||||
);
|
||||
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
|
||||
unavailableTakerTokenAmount = new BigNumber(unavailableTakerTokenAmount);
|
||||
return unavailableTakerTokenAmount;
|
||||
@@ -107,13 +113,15 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
/**
|
||||
* Retrieve the takerAmount of an order that has already been filled.
|
||||
* @param orderHash The hex encoded orderHash for which you would like to retrieve the filled takerAmount.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
* @return The amount of the order (in taker tokens) that has already been filled.
|
||||
*/
|
||||
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, orderHashSchema);
|
||||
public async getFilledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let fillAmountInBaseUnits = await exchangeContract.filled.call(orderHash);
|
||||
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
|
||||
let fillAmountInBaseUnits = await exchangeContract.filled.callAsync(orderHash, defaultBlock);
|
||||
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
|
||||
fillAmountInBaseUnits = new BigNumber(fillAmountInBaseUnits);
|
||||
return fillAmountInBaseUnits;
|
||||
@@ -122,13 +130,15 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* Retrieve the takerAmount of an order that has been cancelled.
|
||||
* @param orderHash The hex encoded orderHash for which you would like to retrieve the
|
||||
* cancelled takerAmount.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
* @return The amount of the order (in taker tokens) that has been cancelled.
|
||||
*/
|
||||
public async getCanceledTakerAmountAsync(orderHash: string): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, orderHashSchema);
|
||||
public async getCancelledTakerAmountAsync(orderHash: string, methodOpts?: MethodOpts): Promise<BigNumber> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.call(orderHash);
|
||||
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
|
||||
let cancelledAmountInBaseUnits = await exchangeContract.cancelled.callAsync(orderHash, defaultBlock);
|
||||
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
|
||||
cancelledAmountInBaseUnits = new BigNumber(cancelledAmountInBaseUnits);
|
||||
return cancelledAmountInBaseUnits;
|
||||
@@ -149,23 +159,33 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @param takerAddress The user Ethereum address who would like to fill this order.
|
||||
* Must be available via the supplied Web3.Provider
|
||||
* passed to 0x.js.
|
||||
* @return The amount of the order that was filled (in taker token baseUnits).
|
||||
* @param orderTransactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
public async fillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
takerAddress: string): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
takerAddress: string,
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
await this.validateFillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
|
||||
|
||||
const gas = await exchangeInstance.fillOrder.estimateGas(
|
||||
const gas = await exchangeInstance.fillOrder.estimateGasAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerTokenAmount,
|
||||
@@ -177,7 +197,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fillOrder(
|
||||
const txHash: string = await exchangeInstance.fillOrder.sendTransactionAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerTokenAmount,
|
||||
@@ -190,10 +210,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
const logFillArgs = response.logs[0].args as LogFillContractEventArgs;
|
||||
const filledTakerTokenAmount = new BigNumber(logFillArgs.filledTakerTokenAmount);
|
||||
return filledTakerTokenAmount;
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Sequentially and atomically fills signedOrders up to the specified takerTokenFillAmount.
|
||||
@@ -209,28 +226,39 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @param takerAddress The user Ethereum address who would like to fill these
|
||||
* orders. Must be available via the supplied Web3.Provider
|
||||
* passed to 0x.js.
|
||||
* @return The amount of the orders that was filled (in taker token baseUnits).
|
||||
* @param orderTransactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
public async fillOrdersUpToAsync(signedOrders: SignedOrder[], fillTakerTokenAmount: BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
takerAddress: string): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('signedOrders', signedOrders, signedOrdersSchema);
|
||||
takerAddress: string,
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('signedOrders', signedOrders, schemas.signedOrdersSchema);
|
||||
const takerTokenAddresses = _.map(signedOrders, signedOrder => signedOrder.takerTokenAddress);
|
||||
assert.hasAtMostOneUniqueValue(takerTokenAddresses,
|
||||
ExchangeContractErrs.MultipleTakerTokensInFillUpToDisallowed);
|
||||
const exchangeContractAddresses = _.map(signedOrders, signedOrder => signedOrder.exchangeContractAddress);
|
||||
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
for (const signedOrder of signedOrders) {
|
||||
await this.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress);
|
||||
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
for (const signedOrder of signedOrders) {
|
||||
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
}
|
||||
|
||||
if (_.isEmpty(signedOrders)) {
|
||||
return new BigNumber(0); // no-op
|
||||
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
|
||||
}
|
||||
|
||||
const orderAddressesValuesAndSignatureArray = _.map(signedOrders, signedOrder => {
|
||||
@@ -247,7 +275,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const gas = await exchangeInstance.fillOrdersUpTo.estimateGas(
|
||||
const gas = await exchangeInstance.fillOrdersUpTo.estimateGasAsync(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
fillTakerTokenAmount,
|
||||
@@ -259,7 +287,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fillOrdersUpTo(
|
||||
const txHash = await exchangeInstance.fillOrdersUpTo.sendTransactionAsync(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
fillTakerTokenAmount,
|
||||
@@ -272,20 +300,14 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
let filledTakerTokenAmount = new BigNumber(0);
|
||||
_.each(response.logs, log => {
|
||||
filledTakerTokenAmount = filledTakerTokenAmount.plus(
|
||||
(log.args as LogFillContractEventArgs).filledTakerTokenAmount);
|
||||
});
|
||||
return filledTakerTokenAmount;
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Batch version of fillOrderAsync.
|
||||
* Executes multiple fills atomically in a single transaction.
|
||||
* If shouldThrowOnInsufficientBalanceOrAllowance is set to true, it will continue filling subsequent orders even
|
||||
* If shouldThrowOnInsufficientBalanceOrAllowance is set to false, it will continue filling subsequent orders even
|
||||
* when earlier ones fail.
|
||||
* When shouldThrowOnInsufficientBalanceOrAllowance is set to false, if any fill fails, the entire batch fails.
|
||||
* When shouldThrowOnInsufficientBalanceOrAllowance is set to true, if any fill fails, the entire batch fails.
|
||||
* @param orderFillRequests An array of objects that conform to the
|
||||
* OrderFillRequest interface.
|
||||
* @param shouldThrowOnInsufficientBalanceOrAllowance Whether or not you wish for the contract call to throw
|
||||
@@ -296,12 +318,15 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @param takerAddress The user Ethereum address who would like to fill
|
||||
* these orders. Must be available via the supplied
|
||||
* Web3.Provider passed to 0x.js.
|
||||
* @param orderTransactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async batchFillOrdersAsync(orderFillRequests: OrderFillRequest[],
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('orderFillRequests', orderFillRequests, orderFillRequestsSchema);
|
||||
takerAddress: string,
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('orderFillRequests', orderFillRequests, schemas.orderFillRequestsSchema);
|
||||
const exchangeContractAddresses = _.map(
|
||||
orderFillRequests,
|
||||
orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress,
|
||||
@@ -310,12 +335,21 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
assert.isBoolean('shouldThrowOnInsufficientBalanceOrAllowance', shouldThrowOnInsufficientBalanceOrAllowance);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
for (const orderFillRequest of orderFillRequests) {
|
||||
await this.validateFillOrderThrowIfInvalidAsync(
|
||||
orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount, takerAddress);
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
for (const orderFillRequest of orderFillRequests) {
|
||||
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount,
|
||||
takerAddress, zrxTokenAddress,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (_.isEmpty(orderFillRequests)) {
|
||||
return; // no-op
|
||||
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
|
||||
}
|
||||
|
||||
const orderAddressesValuesAmountsAndSignatureArray = _.map(orderFillRequests, orderFillRequest => {
|
||||
@@ -333,7 +367,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const gas = await exchangeInstance.batchFillOrders.estimateGas(
|
||||
const gas = await exchangeInstance.batchFillOrders.estimateGasAsync(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
fillTakerTokenAmounts,
|
||||
@@ -345,7 +379,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.batchFillOrders(
|
||||
const txHash = await exchangeInstance.batchFillOrders.sendTransactionAsync(
|
||||
orderAddressesArray,
|
||||
orderValuesArray,
|
||||
fillTakerTokenAmounts,
|
||||
@@ -358,7 +392,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Attempts to fill a specific amount of an order. If the entire amount specified cannot be filled,
|
||||
@@ -368,21 +402,32 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @param fillTakerTokenAmount The total amount of the takerTokens you would like to fill.
|
||||
* @param takerAddress The user Ethereum address who would like to fill this order.
|
||||
* Must be available via the supplied Web3.Provider passed to 0x.js.
|
||||
* @param orderTransactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
public async fillOrKillOrderAsync(signedOrder: SignedOrder, fillTakerTokenAmount: BigNumber,
|
||||
takerAddress: string,
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
|
||||
await this.validateFillOrKillOrderThrowIfInvalidAsync(signedOrder, fillTakerTokenAmount, takerAddress);
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(signedOrder);
|
||||
|
||||
const gas = await exchangeInstance.fillOrKillOrder.estimateGas(
|
||||
const gas = await exchangeInstance.fillOrKillOrder.estimateGasAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerTokenAmount,
|
||||
@@ -393,7 +438,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.fillOrKillOrder(
|
||||
const txHash = await exchangeInstance.fillOrKillOrder.sendTransactionAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerTokenAmount,
|
||||
@@ -405,39 +450,53 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Batch version of fillOrKill. Allows a taker to specify a batch of orders that will either be atomically
|
||||
* filled (each to the specified fillAmount) or aborted.
|
||||
* @param orderFillOrKillRequests An array of objects that conform to the OrderFillOrKillRequest interface.
|
||||
* @param orderFillRequests An array of objects that conform to the OrderFillRequest interface.
|
||||
* @param takerAddress The user Ethereum address who would like to fill there orders.
|
||||
* Must be available via the supplied Web3.Provider passed to 0x.js.
|
||||
* @param orderTransactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async batchFillOrKillAsync(orderFillOrKillRequests: OrderFillOrKillRequest[],
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('orderFillOrKillRequests', orderFillOrKillRequests, orderFillOrKillRequestsSchema);
|
||||
public async batchFillOrKillAsync(orderFillRequests: OrderFillRequest[],
|
||||
takerAddress: string,
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('orderFillRequests', orderFillRequests,
|
||||
schemas.orderFillRequestsSchema);
|
||||
const exchangeContractAddresses = _.map(
|
||||
orderFillOrKillRequests,
|
||||
orderFillOrKillRequest => orderFillOrKillRequest.signedOrder.exchangeContractAddress,
|
||||
orderFillRequests,
|
||||
orderFillRequest => orderFillRequest.signedOrder.exchangeContractAddress,
|
||||
);
|
||||
assert.hasAtMostOneUniqueValue(exchangeContractAddresses,
|
||||
ExchangeContractErrs.BatchOrdersMustHaveSameExchangeAddress);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
if (_.isEmpty(orderFillOrKillRequests)) {
|
||||
return; // no-op
|
||||
if (_.isEmpty(orderFillRequests)) {
|
||||
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
|
||||
}
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
for (const request of orderFillOrKillRequests) {
|
||||
await this.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
request.signedOrder, request.fillTakerAmount, takerAddress);
|
||||
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
for (const orderFillRequest of orderFillRequests) {
|
||||
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, orderFillRequest.signedOrder, orderFillRequest.takerTokenFillAmount,
|
||||
takerAddress, zrxTokenAddress,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillOrKillRequests, request => {
|
||||
const orderAddressesValuesAndTakerTokenFillAmounts = _.map(orderFillRequests, request => {
|
||||
return [
|
||||
...ExchangeWrapper._getOrderAddressesAndValues(request.signedOrder),
|
||||
request.fillTakerAmount,
|
||||
request.takerTokenFillAmount,
|
||||
request.signedOrder.ecSignature.v,
|
||||
request.signedOrder.ecSignature.r,
|
||||
request.signedOrder.ecSignature.s,
|
||||
@@ -448,7 +507,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
const [orderAddresses, orderValues, fillTakerTokenAmounts, vParams, rParams, sParams] =
|
||||
_.unzip<any>(orderAddressesValuesAndTakerTokenFillAmounts);
|
||||
|
||||
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGas(
|
||||
const gas = await exchangeInstance.batchFillOrKillOrders.estimateGasAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerTokenAmounts,
|
||||
@@ -459,7 +518,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: takerAddress,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.batchFillOrKillOrders(
|
||||
const txHash = await exchangeInstance.batchFillOrKillOrders.sendTransactionAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
fillTakerTokenAmounts,
|
||||
@@ -471,27 +530,38 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Cancel a given fill amount of an order. Cancellations are cumulative.
|
||||
* @param order An object that conforms to the Order or SignedOrder interface.
|
||||
* The order you would like to cancel.
|
||||
* @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
|
||||
* @return The amount of the order that was cancelled (in taker token baseUnits).
|
||||
* @param transactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async cancelOrderAsync(
|
||||
order: Order|SignedOrder, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<BigNumber.BigNumber> {
|
||||
assert.doesConformToSchema('order', order, orderSchema);
|
||||
assert.isBigNumber('takerTokenCancelAmount', cancelTakerTokenAmount);
|
||||
public async cancelOrderAsync(order: Order|SignedOrder,
|
||||
cancelTakerTokenAmount: BigNumber,
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
assert.isValidBaseUnitAmount('takerTokenCancelAmount', cancelTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('order.maker', order.maker, this._web3Wrapper);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
await this.validateCancelOrderThrowIfInvalidAsync(order, cancelTakerTokenAmount);
|
||||
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
const orderHash = utils.getOrderHashHex(order);
|
||||
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
|
||||
await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
|
||||
order, cancelTakerTokenAmount, unavailableTakerTokenAmount);
|
||||
}
|
||||
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
|
||||
const gas = await exchangeInstance.cancelOrder.estimateGas(
|
||||
const gas = await exchangeInstance.cancelOrder.estimateGasAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
cancelTakerTokenAmount,
|
||||
@@ -499,7 +569,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: order.maker,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.cancelOrder(
|
||||
const txHash = await exchangeInstance.cancelOrder.sendTransactionAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
cancelTakerTokenAmount,
|
||||
@@ -508,21 +578,21 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
const logFillArgs = response.logs[0].args as LogCancelContractEventArgs;
|
||||
const cancelledTakerTokenAmount = new BigNumber(logFillArgs.cancelledTakerTokenAmount);
|
||||
return cancelledTakerTokenAmount;
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Batch version of cancelOrderAsync. Atomically cancels multiple orders in a single transaction.
|
||||
* All orders must be from the same maker.
|
||||
* @param orderCancellationRequests An array of objects that conform to the OrderCancellationRequest
|
||||
* interface.
|
||||
* @param transactionOpts Optional arguments this method accepts.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
@decorators.contractCallErrorHandler
|
||||
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[]): Promise<void> {
|
||||
public async batchCancelOrdersAsync(orderCancellationRequests: OrderCancellationRequest[],
|
||||
orderTransactionOpts?: OrderTransactionOpts): Promise<string> {
|
||||
assert.doesConformToSchema('orderCancellationRequests', orderCancellationRequests,
|
||||
orderCancellationRequestsSchema);
|
||||
schemas.orderCancellationRequestsSchema);
|
||||
const exchangeContractAddresses = _.map(
|
||||
orderCancellationRequests,
|
||||
orderCancellationRequest => orderCancellationRequest.order.exchangeContractAddress,
|
||||
@@ -533,13 +603,22 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
assert.hasAtMostOneUniqueValue(makers, ExchangeContractErrs.MultipleMakersInSingleCancelBatchDisallowed);
|
||||
const maker = makers[0];
|
||||
await assert.isSenderAddressAsync('maker', maker, this._web3Wrapper);
|
||||
for (const cancellationRequest of orderCancellationRequests) {
|
||||
await this.validateCancelOrderThrowIfInvalidAsync(
|
||||
cancellationRequest.order, cancellationRequest.takerTokenCancelAmount,
|
||||
);
|
||||
const shouldValidate = _.isUndefined(orderTransactionOpts) ?
|
||||
SHOULD_VALIDATE_BY_DEFAULT :
|
||||
orderTransactionOpts.shouldValidate;
|
||||
if (shouldValidate) {
|
||||
for (const orderCancellationRequest of orderCancellationRequests) {
|
||||
const orderHash = utils.getOrderHashHex(orderCancellationRequest.order);
|
||||
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
|
||||
await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
|
||||
orderCancellationRequest.order, orderCancellationRequest.takerTokenCancelAmount,
|
||||
unavailableTakerTokenAmount,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
if (_.isEmpty(orderCancellationRequests)) {
|
||||
return; // no-op
|
||||
throw new Error(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
|
||||
}
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const orderAddressesValuesAndTakerTokenCancelAmounts = _.map(orderCancellationRequests, cancellationRequest => {
|
||||
@@ -551,7 +630,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
// We use _.unzip<any> because _.unzip doesn't type check if values have different types :'(
|
||||
const [orderAddresses, orderValues, cancelTakerTokenAmounts] =
|
||||
_.unzip<any>(orderAddressesValuesAndTakerTokenCancelAmounts);
|
||||
const gas = await exchangeInstance.batchCancelOrders.estimateGas(
|
||||
const gas = await exchangeInstance.batchCancelOrders.estimateGasAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
cancelTakerTokenAmounts,
|
||||
@@ -559,7 +638,7 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
from: maker,
|
||||
},
|
||||
);
|
||||
const response: ContractResponse = await exchangeInstance.batchCancelOrders(
|
||||
const txHash = await exchangeInstance.batchCancelOrders.sendTransactionAsync(
|
||||
orderAddresses,
|
||||
orderValues,
|
||||
cancelTakerTokenAmounts,
|
||||
@@ -568,53 +647,54 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
gas,
|
||||
},
|
||||
);
|
||||
this._throwErrorLogsAsErrors(response.logs);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Subscribe to an event type emitted by the Exchange smart contract
|
||||
* @param eventName The exchange contract event you would like to subscribe to.
|
||||
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
|
||||
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
|
||||
* @return ContractEventEmitter object
|
||||
* Subscribe to an event type emitted by the Exchange contract.
|
||||
* @param eventName The exchange contract event you would like to subscribe to.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
|
||||
* @param callback Callback that gets called when a log is added/removed
|
||||
* @return Subscription token used later to unsubscribe
|
||||
*/
|
||||
public async subscribeAsync(eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexedFilterValues, exchangeContractAddress: string):
|
||||
Promise<ContractEventEmitter> {
|
||||
assert.isETHAddressHex('exchangeContractAddress', exchangeContractAddress);
|
||||
public async subscribeAsync<ArgsType extends ExchangeContractEventArgs>(
|
||||
eventName: ExchangeEvents, indexFilterValues: IndexedFilterValues,
|
||||
callback: EventCallback<ArgsType>): Promise<string> {
|
||||
assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents);
|
||||
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, subscriptionOptsSchema);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, indexFilterValuesSchema);
|
||||
const exchangeContract = await this._getExchangeContractAsync();
|
||||
let createLogEvent: CreateContractEvent;
|
||||
switch (eventName) {
|
||||
case ExchangeEvents.LogFill:
|
||||
createLogEvent = exchangeContract.LogFill;
|
||||
break;
|
||||
case ExchangeEvents.LogError:
|
||||
createLogEvent = exchangeContract.LogError;
|
||||
break;
|
||||
case ExchangeEvents.LogCancel:
|
||||
createLogEvent = exchangeContract.LogCancel;
|
||||
break;
|
||||
default:
|
||||
throw utils.spawnSwitchErr('ExchangeEvents', eventName);
|
||||
}
|
||||
|
||||
const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts);
|
||||
const eventEmitter = eventUtils.wrapEventEmitter(logEventObj);
|
||||
this._exchangeLogEventEmitters.push(eventEmitter);
|
||||
return eventEmitter;
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
assert.isFunction('callback', callback);
|
||||
const exchangeContractAddress = await this.getContractAddressAsync();
|
||||
const subscriptionToken = this._subscribe<ArgsType>(
|
||||
exchangeContractAddress, eventName, indexFilterValues, artifacts.ExchangeArtifact.abi, callback,
|
||||
);
|
||||
return subscriptionToken;
|
||||
}
|
||||
/**
|
||||
* Stops watching for all exchange events
|
||||
* Cancel a subscription
|
||||
* @param subscriptionToken Subscription token returned by `subscribe()`
|
||||
*/
|
||||
public async stopWatchingAllEventsAsync(): Promise<void> {
|
||||
const stopWatchingPromises = _.map(this._exchangeLogEventEmitters,
|
||||
logEventObj => logEventObj.stopWatchingAsync());
|
||||
await Promise.all(stopWatchingPromises);
|
||||
this._exchangeLogEventEmitters = [];
|
||||
public unsubscribe(subscriptionToken: string): void {
|
||||
this._unsubscribe(subscriptionToken);
|
||||
}
|
||||
/**
|
||||
* Gets historical logs without creating a subscription
|
||||
* @param eventName The exchange contract event you would like to subscribe to.
|
||||
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
|
||||
* @return Array of logs that match the parameters
|
||||
*/
|
||||
public async getLogsAsync<ArgsType extends ExchangeContractEventArgs>(
|
||||
eventName: ExchangeEvents, subscriptionOpts: SubscriptionOpts, indexFilterValues: IndexedFilterValues,
|
||||
): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
assert.doesBelongToStringEnum('eventName', eventName, ExchangeEvents);
|
||||
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
const exchangeContractAddress = await this.getContractAddressAsync();
|
||||
const logs = await this._getLogsAsync<ArgsType>(
|
||||
exchangeContractAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.ExchangeArtifact.abi,
|
||||
);
|
||||
return logs;
|
||||
}
|
||||
/**
|
||||
* Retrieves the Ethereum address of the Exchange contract deployed on the network
|
||||
@@ -626,6 +706,26 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
const exchangeAddress = exchangeInstance.address;
|
||||
return exchangeAddress;
|
||||
}
|
||||
/**
|
||||
* Checks if order is still fillable and throws an error otherwise. Useful for orderbook
|
||||
* pruning where you want to remove stale orders without knowing who the taker will be.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
* signedOrder you wish to validate.
|
||||
* @param opts An object that conforms to the ValidateOrderFillableOpts
|
||||
* interface. Allows specifying a specific fillTakerTokenAmount
|
||||
* to validate for.
|
||||
*/
|
||||
public async validateOrderFillableOrThrowAsync(
|
||||
signedOrder: SignedOrder, opts?: ValidateOrderFillableOpts,
|
||||
): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const expectedFillTakerTokenAmount = !_.isUndefined(opts) ? opts.expectedFillTakerTokenAmount : undefined;
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
await this._orderValidationUtils.validateOrderFillableOrThrowAsync(
|
||||
exchangeTradeEmulator, signedOrder, zrxTokenAddress, expectedFillTakerTokenAmount,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Checks if order fill will succeed and throws an error otherwise.
|
||||
* @param signedOrder An object that conforms to the SignedOrder interface. The
|
||||
@@ -635,14 +735,15 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* Must be available via the supplied Web3.Provider passed to 0x.js.
|
||||
*/
|
||||
public async validateFillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
await this._orderValidationUtils.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
/**
|
||||
* Checks if cancelling a given order will succeed and throws an informative error if it won't.
|
||||
@@ -651,9 +752,9 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @param cancelTakerTokenAmount The amount (specified in taker tokens) that you would like to cancel.
|
||||
*/
|
||||
public async validateCancelOrderThrowIfInvalidAsync(
|
||||
order: Order, cancelTakerTokenAmount: BigNumber.BigNumber): Promise<void> {
|
||||
assert.doesConformToSchema('order', order, orderSchema);
|
||||
assert.isBigNumber('cancelTakerTokenAmount', cancelTakerTokenAmount);
|
||||
order: Order, cancelTakerTokenAmount: BigNumber): Promise<void> {
|
||||
assert.doesConformToSchema('order', order, schemas.orderSchema);
|
||||
assert.isValidBaseUnitAmount('cancelTakerTokenAmount', cancelTakerTokenAmount);
|
||||
const orderHash = utils.getOrderHashHex(order);
|
||||
const unavailableTakerTokenAmount = await this.getUnavailableTakerAmountAsync(orderHash);
|
||||
await this._orderValidationUtils.validateCancelOrderThrowIfInvalidAsync(
|
||||
@@ -668,14 +769,15 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* Must be available via the supplied Web3.Provider passed to 0x.js.
|
||||
*/
|
||||
public async validateFillOrKillOrderThrowIfInvalidAsync(signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
takerAddress: string): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, signedOrderSchema);
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
await assert.isSenderAddressAsync('takerAddress', takerAddress, this._web3Wrapper);
|
||||
const zrxTokenAddress = await this._getZRXTokenAddressAsync();
|
||||
const zrxTokenAddress = await this.getZRXTokenAddressAsync();
|
||||
const exchangeTradeEmulator = new ExchangeTransferSimulator(this._tokenWrapper);
|
||||
await this._orderValidationUtils.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress);
|
||||
}
|
||||
/**
|
||||
* Checks if rounding error will be > 0.1% when computing makerTokenAmount by doing:
|
||||
@@ -686,31 +788,55 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
* @param takerTokenAmount The order size on the taker side
|
||||
* @param makerTokenAmount The order size on the maker side
|
||||
*/
|
||||
public async isRoundingErrorAsync(fillTakerTokenAmount: BigNumber.BigNumber,
|
||||
takerTokenAmount: BigNumber.BigNumber,
|
||||
makerTokenAmount: BigNumber.BigNumber): Promise<boolean> {
|
||||
assert.isBigNumber('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isBigNumber('takerTokenAmount', takerTokenAmount);
|
||||
assert.isBigNumber('makerTokenAmount', makerTokenAmount);
|
||||
public async isRoundingErrorAsync(fillTakerTokenAmount: BigNumber,
|
||||
takerTokenAmount: BigNumber,
|
||||
makerTokenAmount: BigNumber): Promise<boolean> {
|
||||
assert.isValidBaseUnitAmount('fillTakerTokenAmount', fillTakerTokenAmount);
|
||||
assert.isValidBaseUnitAmount('takerTokenAmount', takerTokenAmount);
|
||||
assert.isValidBaseUnitAmount('makerTokenAmount', makerTokenAmount);
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const isRoundingError = await exchangeInstance.isRoundingError.call(
|
||||
const isRoundingError = await exchangeInstance.isRoundingError.callAsync(
|
||||
fillTakerTokenAmount, takerTokenAmount, makerTokenAmount,
|
||||
);
|
||||
return isRoundingError;
|
||||
}
|
||||
/**
|
||||
* Checks if logs contain LogError, which is emmited by Exchange contract on transaction failure.
|
||||
* @param logs Transaction logs as returned by `zeroEx.awaitTransactionMinedAsync`
|
||||
*/
|
||||
public throwLogErrorsAsErrors(logs: Array<LogWithDecodedArgs<DecodedLogArgs>|Web3.LogEntry>): void {
|
||||
const errLog = _.find(logs, {
|
||||
event: ExchangeEvents.LogError,
|
||||
}) as LogWithDecodedArgs<LogErrorContractEventArgs>|undefined;
|
||||
if (!_.isUndefined(errLog)) {
|
||||
const logArgs = errLog.args;
|
||||
const errCode = logArgs.errorId.toNumber();
|
||||
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Returns the ZRX token address used by the exchange contract.
|
||||
* @return Address of ZRX token
|
||||
*/
|
||||
public async getZRXTokenAddressAsync(): Promise<string> {
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.callAsync();
|
||||
return ZRXtokenAddress;
|
||||
}
|
||||
private async _invalidateContractInstancesAsync(): Promise<void> {
|
||||
await this.stopWatchingAllEventsAsync();
|
||||
this.unsubscribeAll();
|
||||
delete this._exchangeContractIfExists;
|
||||
}
|
||||
private async _isValidSignatureUsingContractCallAsync(dataHex: string, ecSignature: ECSignature,
|
||||
signerAddressHex: string): Promise<boolean> {
|
||||
assert.isHexString('dataHex', dataHex);
|
||||
assert.doesConformToSchema('ecSignature', ecSignature, ecSignatureSchema);
|
||||
assert.doesConformToSchema('ecSignature', ecSignature, schemas.ecSignatureSchema);
|
||||
assert.isETHAddressHex('signerAddressHex', signerAddressHex);
|
||||
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
|
||||
const isValidSignature = await exchangeInstance.isValidSignature.call(
|
||||
const isValidSignature = await exchangeInstance.isValidSignature.callAsync(
|
||||
signerAddressHex,
|
||||
dataHex,
|
||||
ecSignature.v,
|
||||
@@ -722,28 +848,23 @@ export class ExchangeWrapper extends ContractWrapper {
|
||||
private async _getOrderHashHexUsingContractCallAsync(order: Order|SignedOrder): Promise<string> {
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const [orderAddresses, orderValues] = ExchangeWrapper._getOrderAddressesAndValues(order);
|
||||
const orderHashHex = await exchangeInstance.getOrderHash.call(orderAddresses, orderValues);
|
||||
const orderHashHex = await exchangeInstance.getOrderHash.callAsync(orderAddresses, orderValues);
|
||||
return orderHashHex;
|
||||
}
|
||||
private _throwErrorLogsAsErrors(logs: ContractEvent[]): void {
|
||||
const errEvent = _.find(logs, {event: 'LogError'});
|
||||
if (!_.isUndefined(errEvent)) {
|
||||
const errCode = (errEvent.args as LogErrorContractEventArgs).errorId.toNumber();
|
||||
const errMessage = this._exchangeContractErrCodesToMsg[errCode];
|
||||
throw new Error(errMessage);
|
||||
}
|
||||
}
|
||||
private async _getExchangeContractAsync(): Promise<ExchangeContract> {
|
||||
if (!_.isUndefined(this._exchangeContractIfExists)) {
|
||||
return this._exchangeContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((ExchangeArtifacts as any));
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<ExchangeContract>(
|
||||
artifacts.ExchangeArtifact, this._contractAddressIfExists,
|
||||
);
|
||||
this._exchangeContractIfExists = contractInstance as ExchangeContract;
|
||||
return this._exchangeContractIfExists;
|
||||
}
|
||||
private async _getZRXTokenAddressAsync(): Promise<string> {
|
||||
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
|
||||
const exchangeInstance = await this._getExchangeContractAsync();
|
||||
const ZRXtokenAddress = await exchangeInstance.ZRX_TOKEN_CONTRACT.call();
|
||||
return ZRXtokenAddress;
|
||||
const tokenTransferProxyAddress = await exchangeInstance.TOKEN_TRANSFER_PROXY_CONTRACT.callAsync();
|
||||
const tokenTransferProxyAddressLowerCase = tokenTransferProxyAddress.toLowerCase();
|
||||
return tokenTransferProxyAddressLowerCase;
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,17 @@ import {assert} from '../utils/assert';
|
||||
import {Token, TokenRegistryContract, TokenMetadata} from '../types';
|
||||
import {constants} from '../utils/constants';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import * as TokenRegistryArtifacts from '../artifacts/TokenRegistry.json';
|
||||
import {artifacts} from '../artifacts';
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with the 0x Token Registry smart contract.
|
||||
*/
|
||||
export class TokenRegistryWrapper extends ContractWrapper {
|
||||
private _tokenRegistryContractIfExists?: TokenRegistryContract;
|
||||
constructor(web3Wrapper: Web3Wrapper) {
|
||||
private _contractAddressIfExists?: string;
|
||||
constructor(web3Wrapper: Web3Wrapper, contractAddressIfExists?: string) {
|
||||
super(web3Wrapper);
|
||||
this._contractAddressIfExists = contractAddressIfExists;
|
||||
}
|
||||
/**
|
||||
* Retrieves all the tokens currently listed in the Token Registry smart contract
|
||||
@@ -35,7 +37,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
*/
|
||||
public async getTokenAddressesAsync(): Promise<string[]> {
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addresses = await tokenRegistryContract.getTokenAddresses.call();
|
||||
const addresses = await tokenRegistryContract.getTokenAddresses.callAsync();
|
||||
return addresses;
|
||||
}
|
||||
/**
|
||||
@@ -46,14 +48,14 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
assert.isETHAddressHex('address', address);
|
||||
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenMetaData.call(address);
|
||||
const metadata = await tokenRegistryContract.getTokenMetaData.callAsync(address);
|
||||
const token = this._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
public async getTokenAddressBySymbolIfExistsAsync(symbol: string): Promise<string|undefined> {
|
||||
assert.isString('symbol', symbol);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.call(symbol);
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressBySymbol.callAsync(symbol);
|
||||
if (addressIfExists === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -62,7 +64,7 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
public async getTokenAddressByNameIfExistsAsync(name: string): Promise<string|undefined> {
|
||||
assert.isString('name', name);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.call(name);
|
||||
const addressIfExists = await tokenRegistryContract.getTokenAddressByName.callAsync(name);
|
||||
if (addressIfExists === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -71,17 +73,27 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
public async getTokenBySymbolIfExistsAsync(symbol: string): Promise<Token|undefined> {
|
||||
assert.isString('symbol', symbol);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenBySymbol.call(symbol);
|
||||
const metadata = await tokenRegistryContract.getTokenBySymbol.callAsync(symbol);
|
||||
const token = this._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
public async getTokenByNameIfExistsAsync(name: string): Promise<Token|undefined> {
|
||||
assert.isString('name', name);
|
||||
const tokenRegistryContract = await this._getTokenRegistryContractAsync();
|
||||
const metadata = await tokenRegistryContract.getTokenByName.call(name);
|
||||
const metadata = await tokenRegistryContract.getTokenByName.callAsync(name);
|
||||
const token = this._createTokenFromMetadata(metadata);
|
||||
return token;
|
||||
}
|
||||
/**
|
||||
* Retrieves the Ethereum address of the TokenRegistry contract deployed on the network
|
||||
* that the user-passed web3 provider is connected to.
|
||||
* @returns The Ethereum address of the TokenRegistry contract being used.
|
||||
*/
|
||||
public async getContractAddressAsync(): Promise<string> {
|
||||
const tokenRegistryInstance = await this._getTokenRegistryContractAsync();
|
||||
const tokenRegistryAddress = tokenRegistryInstance.address;
|
||||
return tokenRegistryAddress;
|
||||
}
|
||||
private _createTokenFromMetadata(metadata: TokenMetadata): Token|undefined {
|
||||
if (metadata[0] === constants.NULL_ADDRESS) {
|
||||
return undefined;
|
||||
@@ -101,7 +113,9 @@ export class TokenRegistryWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(this._tokenRegistryContractIfExists)) {
|
||||
return this._tokenRegistryContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((TokenRegistryArtifacts as any));
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<TokenRegistryContract>(
|
||||
artifacts.TokenRegistryArtifact, this._contractAddressIfExists,
|
||||
);
|
||||
this._tokenRegistryContractIfExists = contractInstance as TokenRegistryContract;
|
||||
return this._tokenRegistryContractIfExists;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as _ from 'lodash';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
|
||||
import {artifacts} from '../artifacts';
|
||||
import {TokenTransferProxyContract} from '../types';
|
||||
|
||||
/**
|
||||
@@ -8,6 +9,11 @@ import {TokenTransferProxyContract} from '../types';
|
||||
*/
|
||||
export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
private _tokenTransferProxyContractIfExists?: TokenTransferProxyContract;
|
||||
private _tokenTransferProxyContractAddressFetcher: () => Promise<string>;
|
||||
constructor(web3Wrapper: Web3Wrapper, tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
|
||||
super(web3Wrapper);
|
||||
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
|
||||
}
|
||||
/**
|
||||
* Check if the Exchange contract address is authorized by the TokenTransferProxy contract.
|
||||
* @param exchangeContractAddress The hex encoded address of the Exchange contract to call.
|
||||
@@ -15,7 +21,7 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
*/
|
||||
public async isAuthorizedAsync(exchangeContractAddress: string): Promise<boolean> {
|
||||
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
|
||||
const isAuthorized = await tokenTransferProxyContractInstance.authorized.call(exchangeContractAddress);
|
||||
const isAuthorized = await tokenTransferProxyContractInstance.authorized.callAsync(exchangeContractAddress);
|
||||
return isAuthorized;
|
||||
}
|
||||
/**
|
||||
@@ -24,7 +30,7 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
*/
|
||||
public async getAuthorizedAddressesAsync(): Promise<string[]> {
|
||||
const tokenTransferProxyContractInstance = await this._getTokenTransferProxyContractAsync();
|
||||
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.call();
|
||||
const authorizedAddresses = await tokenTransferProxyContractInstance.getAuthorizedAddresses.callAsync();
|
||||
return authorizedAddresses;
|
||||
}
|
||||
/**
|
||||
@@ -44,7 +50,10 @@ export class TokenTransferProxyWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(this._tokenTransferProxyContractIfExists)) {
|
||||
return this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((TokenTransferProxyArtifacts as any));
|
||||
const contractAddress = await this._tokenTransferProxyContractAddressFetcher();
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<TokenTransferProxyContract>(
|
||||
artifacts.TokenTransferProxyArtifact, contractAddress,
|
||||
);
|
||||
this._tokenTransferProxyContractIfExists = contractInstance as TokenTransferProxyContract;
|
||||
return this._tokenTransferProxyContractIfExists;
|
||||
}
|
||||
@@ -1,27 +1,25 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {schemas} from '@0xproject/json-schemas';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {assert} from '../utils/assert';
|
||||
import {utils} from '../utils/utils';
|
||||
import {eventUtils} from '../utils/event_utils';
|
||||
import {constants} from '../utils/constants';
|
||||
import {ContractWrapper} from './contract_wrapper';
|
||||
import * as TokenArtifacts from '../artifacts/Token.json';
|
||||
import * as TokenTransferProxyArtifacts from '../artifacts/TokenTransferProxy.json';
|
||||
import {subscriptionOptsSchema} from '../schemas/subscription_opts_schema';
|
||||
import {indexFilterValuesSchema} from '../schemas/index_filter_values_schema';
|
||||
import {AbiDecoder} from '../utils/abi_decoder';
|
||||
import {artifacts} from '../artifacts';
|
||||
import {
|
||||
TokenContract,
|
||||
ZeroExError,
|
||||
TokenEvents,
|
||||
IndexedFilterValues,
|
||||
SubscriptionOpts,
|
||||
CreateContractEvent,
|
||||
ContractEventEmitter,
|
||||
ContractEventObj,
|
||||
MethodOpts,
|
||||
LogWithDecodedArgs,
|
||||
EventCallback,
|
||||
TokenContractEventArgs,
|
||||
} from '../types';
|
||||
|
||||
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
|
||||
const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47275;
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with ERC20 token contracts.
|
||||
@@ -31,24 +29,28 @@ const ALLOWANCE_TO_ZERO_GAS_AMOUNT = 47155;
|
||||
export class TokenWrapper extends ContractWrapper {
|
||||
public UNLIMITED_ALLOWANCE_IN_BASE_UNITS = constants.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||
private _tokenContractsByAddress: {[address: string]: TokenContract};
|
||||
private _tokenLogEventEmitters: ContractEventEmitter[];
|
||||
constructor(web3Wrapper: Web3Wrapper) {
|
||||
super(web3Wrapper);
|
||||
private _tokenTransferProxyContractAddressFetcher: () => Promise<string>;
|
||||
constructor(web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder,
|
||||
tokenTransferProxyContractAddressFetcher: () => Promise<string>) {
|
||||
super(web3Wrapper, abiDecoder);
|
||||
this._tokenContractsByAddress = {};
|
||||
this._tokenLogEventEmitters = [];
|
||||
this._tokenTransferProxyContractAddressFetcher = tokenTransferProxyContractAddressFetcher;
|
||||
}
|
||||
/**
|
||||
* Retrieves an owner's ERC20 token balance.
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
* @param ownerAddress The hex encoded user Ethereum address whose balance you would like to check.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
* @return The owner's ERC20 token balance in base units.
|
||||
*/
|
||||
public async getBalanceAsync(tokenAddress: string, ownerAddress: string): Promise<BigNumber.BigNumber> {
|
||||
public async getBalanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
methodOpts?: MethodOpts): Promise<BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
let balance = await tokenContract.balanceOf.call(ownerAddress);
|
||||
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
|
||||
let balance = await tokenContract.balanceOf.callAsync(ownerAddress, defaultBlock);
|
||||
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
|
||||
balance = new BigNumber(balance);
|
||||
return balance;
|
||||
@@ -61,13 +63,14 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* for spenderAddress.
|
||||
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
|
||||
* @param amountInBaseUnits The allowance amount you would like to set.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string,
|
||||
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
|
||||
amountInBaseUnits: BigNumber): Promise<string> {
|
||||
await assert.isSenderAddressAsync('ownerAddress', ownerAddress, this._web3Wrapper);
|
||||
assert.isETHAddressHex('spenderAddress', spenderAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
// Hack: for some reason default estimated gas amount causes `base fee exceeds gas limit` exception
|
||||
@@ -75,10 +78,11 @@ export class TokenWrapper extends ContractWrapper {
|
||||
// TODO: Debug issue in testrpc and submit a PR, then remove this hack
|
||||
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
const gas = networkIdIfExists === constants.TESTRPC_NETWORK_ID ? ALLOWANCE_TO_ZERO_GAS_AMOUNT : undefined;
|
||||
await tokenContract.approve(spenderAddress, amountInBaseUnits, {
|
||||
const txHash = await tokenContract.approve.sendTransactionAsync(spenderAddress, amountInBaseUnits, {
|
||||
from: ownerAddress,
|
||||
gas,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Sets the spender's allowance to an unlimited number of baseUnits on behalf of the owner address.
|
||||
@@ -89,12 +93,14 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param ownerAddress The hex encoded user Ethereum address who would like to set an allowance
|
||||
* for spenderAddress.
|
||||
* @param spenderAddress The hex encoded user Ethereum address who will be able to spend the set allowance.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setUnlimitedAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
spenderAddress: string): Promise<void> {
|
||||
await this.setAllowanceAsync(
|
||||
spenderAddress: string): Promise<string> {
|
||||
const txHash = await this.setAllowanceAsync(
|
||||
tokenAddress, ownerAddress, spenderAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Retrieves the owners allowance in baseUnits set to the spender's address.
|
||||
@@ -102,13 +108,16 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param ownerAddress The hex encoded user Ethereum address whose allowance to spenderAddress
|
||||
* you would like to retrieve.
|
||||
* @param spenderAddress The hex encoded user Ethereum address who can spend the allowance you are fetching.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
*/
|
||||
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string, spenderAddress: string) {
|
||||
public async getAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
spenderAddress: string, methodOpts?: MethodOpts): Promise<BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
let allowanceInBaseUnits = await tokenContract.allowance.call(ownerAddress, spenderAddress);
|
||||
const defaultBlock = _.isUndefined(methodOpts) ? undefined : methodOpts.defaultBlock;
|
||||
let allowanceInBaseUnits = await tokenContract.allowance.callAsync(ownerAddress, spenderAddress, defaultBlock);
|
||||
// Wrap BigNumbers returned from web3 with our own (later) version of BigNumber
|
||||
allowanceInBaseUnits = new BigNumber(allowanceInBaseUnits);
|
||||
return allowanceInBaseUnits;
|
||||
@@ -117,13 +126,15 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* Retrieves the owner's allowance in baseUnits set to the 0x proxy contract.
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
* @param ownerAddress The hex encoded user Ethereum address whose proxy contract allowance we are retrieving.
|
||||
* @param methodOpts Optional arguments this method accepts.
|
||||
*/
|
||||
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string) {
|
||||
public async getProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
methodOpts?: MethodOpts): Promise<BigNumber> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
|
||||
const proxyAddress = await this._getProxyAddressAsync();
|
||||
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress);
|
||||
const proxyAddress = await this._getTokenTransferProxyAddressAsync();
|
||||
const allowanceInBaseUnits = await this.getAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, methodOpts);
|
||||
return allowanceInBaseUnits;
|
||||
}
|
||||
/**
|
||||
@@ -133,15 +144,17 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
|
||||
* for the Proxy contract.
|
||||
* @param amountInBaseUnits The allowance amount specified in baseUnits.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setProxyAllowanceAsync(tokenAddress: string, ownerAddress: string,
|
||||
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
|
||||
amountInBaseUnits: BigNumber): Promise<string> {
|
||||
assert.isETHAddressHex('ownerAddress', ownerAddress);
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const proxyAddress = await this._getProxyAddressAsync();
|
||||
await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
|
||||
const proxyAddress = await this._getTokenTransferProxyAddressAsync();
|
||||
const txHash = await this.setAllowanceAsync(tokenAddress, ownerAddress, proxyAddress, amountInBaseUnits);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Sets the 0x proxy contract's allowance to a unlimited number of a tokens' baseUnits on behalf
|
||||
@@ -151,9 +164,13 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param tokenAddress The hex encoded contract Ethereum address where the ERC20 token is deployed.
|
||||
* @param ownerAddress The hex encoded user Ethereum address who is setting an allowance
|
||||
* for the Proxy contract.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<void> {
|
||||
await this.setProxyAllowanceAsync(tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
public async setUnlimitedProxyAllowanceAsync(tokenAddress: string, ownerAddress: string): Promise<string> {
|
||||
const txHash = await this.setProxyAllowanceAsync(
|
||||
tokenAddress, ownerAddress, this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS,
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
|
||||
@@ -161,13 +178,14 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* @param fromAddress The hex encoded user Ethereum address that will send the funds.
|
||||
* @param toAddress The hex encoded user Ethereum address that will receive the funds.
|
||||
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async transferAsync(tokenAddress: string, fromAddress: string, toAddress: string,
|
||||
amountInBaseUnits: BigNumber.BigNumber): Promise<void> {
|
||||
amountInBaseUnits: BigNumber): Promise<string> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
await assert.isSenderAddressAsync('fromAddress', fromAddress, this._web3Wrapper);
|
||||
assert.isETHAddressHex('toAddress', toAddress);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
|
||||
@@ -176,9 +194,10 @@ export class TokenWrapper extends ContractWrapper {
|
||||
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
|
||||
}
|
||||
|
||||
await tokenContract.transfer(toAddress, amountInBaseUnits, {
|
||||
const txHash = await tokenContract.transfer.sendTransactionAsync(toAddress, amountInBaseUnits, {
|
||||
from: fromAddress,
|
||||
});
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Transfers `amountInBaseUnits` ERC20 tokens from `fromAddress` to `toAddress`.
|
||||
@@ -191,15 +210,16 @@ export class TokenWrapper extends ContractWrapper {
|
||||
* `fromAddress` must have set an allowance to the `senderAddress`
|
||||
* before this call.
|
||||
* @param amountInBaseUnits The amount (specified in baseUnits) of the token to transfer.
|
||||
* @return Transaction hash.
|
||||
*/
|
||||
public async transferFromAsync(tokenAddress: string, fromAddress: string, toAddress: string,
|
||||
senderAddress: string, amountInBaseUnits: BigNumber.BigNumber):
|
||||
Promise<void> {
|
||||
senderAddress: string, amountInBaseUnits: BigNumber):
|
||||
Promise<string> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.isETHAddressHex('fromAddress', fromAddress);
|
||||
assert.isETHAddressHex('toAddress', toAddress);
|
||||
await assert.isSenderAddressAsync('senderAddress', senderAddress, this._web3Wrapper);
|
||||
assert.isBigNumber('amountInBaseUnits', amountInBaseUnits);
|
||||
assert.isValidBaseUnitAmount('amountInBaseUnits', amountInBaseUnits);
|
||||
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
|
||||
@@ -213,54 +233,65 @@ export class TokenWrapper extends ContractWrapper {
|
||||
throw new Error(ZeroExError.InsufficientBalanceForTransfer);
|
||||
}
|
||||
|
||||
await tokenContract.transferFrom(fromAddress, toAddress, amountInBaseUnits, {
|
||||
from: senderAddress,
|
||||
});
|
||||
const txHash = await tokenContract.transferFrom.sendTransactionAsync(
|
||||
fromAddress, toAddress, amountInBaseUnits,
|
||||
{
|
||||
from: senderAddress,
|
||||
},
|
||||
);
|
||||
return txHash;
|
||||
}
|
||||
/**
|
||||
* Subscribe to an event type emitted by the Token contract.
|
||||
* @param tokenAddress The hex encoded address where the ERC20 token is deployed.
|
||||
* @param eventName The token contract event you would like to subscribe to.
|
||||
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{maker: aUserAddressHex}`
|
||||
* @return ContractEventEmitter object
|
||||
* @param callback Callback that gets called when a log is added/removed
|
||||
* @return Subscription token used later to unsubscribe
|
||||
*/
|
||||
public async subscribeAsync(tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexedFilterValues): Promise<ContractEventEmitter> {
|
||||
public subscribe<ArgsType extends TokenContractEventArgs>(
|
||||
tokenAddress: string, eventName: TokenEvents, indexFilterValues: IndexedFilterValues,
|
||||
callback: EventCallback<ArgsType>): string {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
|
||||
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, subscriptionOptsSchema);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, indexFilterValuesSchema);
|
||||
const tokenContract = await this._getTokenContractAsync(tokenAddress);
|
||||
let createLogEvent: CreateContractEvent;
|
||||
switch (eventName) {
|
||||
case TokenEvents.Approval:
|
||||
createLogEvent = tokenContract.Approval;
|
||||
break;
|
||||
case TokenEvents.Transfer:
|
||||
createLogEvent = tokenContract.Transfer;
|
||||
break;
|
||||
default:
|
||||
throw utils.spawnSwitchErr('TokenEvents', eventName);
|
||||
}
|
||||
|
||||
const logEventObj: ContractEventObj = createLogEvent(indexFilterValues, subscriptionOpts);
|
||||
const eventEmitter = eventUtils.wrapEventEmitter(logEventObj);
|
||||
this._tokenLogEventEmitters.push(eventEmitter);
|
||||
return eventEmitter;
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
assert.isFunction('callback', callback);
|
||||
const subscriptionToken = this._subscribe<ArgsType>(
|
||||
tokenAddress, eventName, indexFilterValues, artifacts.TokenArtifact.abi, callback,
|
||||
);
|
||||
return subscriptionToken;
|
||||
}
|
||||
/**
|
||||
* Stops watching for all token events
|
||||
* Cancel a subscription
|
||||
* @param subscriptionToken Subscription token returned by `subscribe()`
|
||||
*/
|
||||
public async stopWatchingAllEventsAsync(): Promise<void> {
|
||||
const stopWatchingPromises = _.map(this._tokenLogEventEmitters,
|
||||
logEventObj => logEventObj.stopWatchingAsync());
|
||||
await Promise.all(stopWatchingPromises);
|
||||
this._tokenLogEventEmitters = [];
|
||||
public unsubscribe(subscriptionToken: string): void {
|
||||
this._unsubscribe(subscriptionToken);
|
||||
}
|
||||
private async _invalidateContractInstancesAsync(): Promise<void> {
|
||||
await this.stopWatchingAllEventsAsync();
|
||||
/**
|
||||
* Gets historical logs without creating a subscription
|
||||
* @param tokenAddress An address of the token that emmited the logs.
|
||||
* @param eventName The token contract event you would like to subscribe to.
|
||||
* @param subscriptionOpts Subscriptions options that let you configure the subscription.
|
||||
* @param indexFilterValues An object where the keys are indexed args returned by the event and
|
||||
* the value is the value you are interested in. E.g `{_from: aUserAddressHex}`
|
||||
* @return Array of logs that match the parameters
|
||||
*/
|
||||
public async getLogsAsync<ArgsType extends TokenContractEventArgs>(
|
||||
tokenAddress: string, eventName: TokenEvents, subscriptionOpts: SubscriptionOpts,
|
||||
indexFilterValues: IndexedFilterValues): Promise<Array<LogWithDecodedArgs<ArgsType>>> {
|
||||
assert.isETHAddressHex('tokenAddress', tokenAddress);
|
||||
assert.doesBelongToStringEnum('eventName', eventName, TokenEvents);
|
||||
assert.doesConformToSchema('subscriptionOpts', subscriptionOpts, schemas.subscriptionOptsSchema);
|
||||
assert.doesConformToSchema('indexFilterValues', indexFilterValues, schemas.indexFilterValuesSchema);
|
||||
const logs = await this._getLogsAsync<ArgsType>(
|
||||
tokenAddress, eventName, subscriptionOpts, indexFilterValues, artifacts.TokenArtifact.abi,
|
||||
);
|
||||
return logs;
|
||||
}
|
||||
private _invalidateContractInstancesAsync(): void {
|
||||
this.unsubscribeAll();
|
||||
this._tokenContractsByAddress = {};
|
||||
}
|
||||
private async _getTokenContractAsync(tokenAddress: string): Promise<TokenContract> {
|
||||
@@ -268,20 +299,15 @@ export class TokenWrapper extends ContractWrapper {
|
||||
if (!_.isUndefined(tokenContract)) {
|
||||
return tokenContract;
|
||||
}
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync((TokenArtifacts as any), tokenAddress);
|
||||
const contractInstance = await this._instantiateContractIfExistsAsync<TokenContract>(
|
||||
artifacts.TokenArtifact, tokenAddress,
|
||||
);
|
||||
tokenContract = contractInstance as TokenContract;
|
||||
this._tokenContractsByAddress[tokenAddress] = tokenContract;
|
||||
return tokenContract;
|
||||
}
|
||||
private async _getProxyAddressAsync() {
|
||||
const networkIdIfExists = await this._web3Wrapper.getNetworkIdIfExistsAsync();
|
||||
const proxyNetworkConfigsIfExists = _.isUndefined(networkIdIfExists) ?
|
||||
undefined :
|
||||
(TokenTransferProxyArtifacts as any).networks[networkIdIfExists];
|
||||
if (_.isUndefined(proxyNetworkConfigsIfExists)) {
|
||||
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
|
||||
}
|
||||
const proxyAddress = proxyNetworkConfigsIfExists.address.toLowerCase();
|
||||
return proxyAddress;
|
||||
private async _getTokenTransferProxyAddressAsync(): Promise<string> {
|
||||
const tokenTransferProxyContractAddress = await this._tokenTransferProxyContractAddressFetcher();
|
||||
return tokenTransferProxyContractAddress;
|
||||
}
|
||||
}
|
||||
80
packages/0x.js/src/globals.d.ts
vendored
Normal file
80
packages/0x.js/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,80 @@
|
||||
/// <reference types='chai-typescript-typings' />
|
||||
/// <reference types='chai-as-promised-typescript-typings' />
|
||||
declare module 'web3_beta';
|
||||
declare module 'chai-bignumber';
|
||||
declare module 'dirty-chai';
|
||||
declare module 'request-promise-native';
|
||||
declare module 'web3-provider-engine';
|
||||
declare module 'web3-provider-engine/subproviders/rpc';
|
||||
|
||||
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
|
||||
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
|
||||
// disallow `namespace`, we disable tslint for the following.
|
||||
/* tslint:disable */
|
||||
declare namespace Chai {
|
||||
interface Assertion {
|
||||
bignumber: Assertion;
|
||||
// HACK: In order to comply with chai-as-promised we make eventually a `PromisedAssertion` not an `Assertion`
|
||||
eventually: PromisedAssertion;
|
||||
}
|
||||
}
|
||||
/* tslint:enable */
|
||||
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
||||
|
||||
// find-version declarations
|
||||
declare function findVersions(version: string): string[];
|
||||
declare module 'find-versions' {
|
||||
export = findVersions;
|
||||
}
|
||||
|
||||
// compare-version declarations
|
||||
declare function compareVersions(firstVersion: string, secondVersion: string): number;
|
||||
declare module 'compare-versions' {
|
||||
export = compareVersions;
|
||||
}
|
||||
|
||||
// es6-promisify declarations
|
||||
declare function promisify(original: any, settings?: any): ((...arg: any[]) => Promise<any>);
|
||||
declare module 'es6-promisify' {
|
||||
export = promisify;
|
||||
}
|
||||
|
||||
declare module 'ethereumjs-abi' {
|
||||
const soliditySHA3: (argTypes: string[], args: any[]) => Buffer;
|
||||
}
|
||||
|
||||
// truffle-hdwallet-provider declarations
|
||||
declare module 'truffle-hdwallet-provider' {
|
||||
import * as Web3 from 'web3';
|
||||
class HDWalletProvider implements Web3.Provider {
|
||||
constructor(mnemonic: string, rpcUrl: string);
|
||||
public sendAsync(
|
||||
payload: Web3.JSONRPCRequestPayload,
|
||||
callback: (err: Error, result: Web3.JSONRPCResponsePayload) => void,
|
||||
): void;
|
||||
}
|
||||
export = HDWalletProvider;
|
||||
}
|
||||
|
||||
// abi-decoder declarations
|
||||
interface DecodedLogArg {
|
||||
}
|
||||
interface DecodedLog {
|
||||
name: string;
|
||||
events: DecodedLogArg[];
|
||||
}
|
||||
declare module 'abi-decoder' {
|
||||
import * as Web3 from 'web3';
|
||||
const addABI: (abi: Web3.AbiDefinition) => void;
|
||||
const decodeLogs: (logs: Web3.LogEntry[]) => DecodedLog[];
|
||||
}
|
||||
|
||||
declare module 'web3/lib/solidity/coder' {
|
||||
const decodeParams: (types: string[], data: string) => any[];
|
||||
}
|
||||
23
packages/0x.js/src/globalsAugment.d.ts
vendored
Normal file
23
packages/0x.js/src/globalsAugment.d.ts
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
// HACK: This module overrides the Chai namespace so that we can use BigNumber types inside.
|
||||
// Source: https://github.com/Microsoft/TypeScript/issues/7352#issuecomment-191547232
|
||||
declare global {
|
||||
// HACK: In order to merge the bignumber declaration added by chai-bignumber to the chai Assertion
|
||||
// interface we must use `namespace` as the Chai definitelyTyped definition does. Since we otherwise
|
||||
// disallow `namespace`, we disable tslint for the following.
|
||||
/* tslint:disable */
|
||||
namespace Chai {
|
||||
interface NumberComparer {
|
||||
(value: number|BigNumber, message?: string): Assertion;
|
||||
}
|
||||
interface NumericComparison {
|
||||
greaterThan: NumberComparer;
|
||||
}
|
||||
}
|
||||
/* tslint:enable */
|
||||
interface DecodedLogArg {
|
||||
name: string;
|
||||
value: string|BigNumber;
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,6 @@ export {
|
||||
ECSignature,
|
||||
ZeroExError,
|
||||
EventCallback,
|
||||
EventCallbackAsync,
|
||||
EventCallbackSync,
|
||||
ExchangeContractErrs,
|
||||
ContractEvent,
|
||||
Token,
|
||||
@@ -16,10 +14,8 @@ export {
|
||||
IndexedFilterValues,
|
||||
SubscriptionOpts,
|
||||
BlockParam,
|
||||
OrderFillOrKillRequest,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
ContractEventEmitter,
|
||||
LogErrorContractEventArgs,
|
||||
LogCancelContractEventArgs,
|
||||
LogFillContractEventArgs,
|
||||
@@ -28,5 +24,20 @@ export {
|
||||
ApprovalContractEventArgs,
|
||||
TokenContractEventArgs,
|
||||
ContractEventArgs,
|
||||
ContractEventArg,
|
||||
Web3Provider,
|
||||
ZeroExConfig,
|
||||
TransactionReceipt,
|
||||
TransactionReceiptWithDecodedLogs,
|
||||
LogWithDecodedArgs,
|
||||
MethodOpts,
|
||||
OrderTransactionOpts,
|
||||
FilterObject,
|
||||
LogEvent,
|
||||
DecodedLogEvent,
|
||||
EventWatcherCallback,
|
||||
OnOrderStateChangeCallback,
|
||||
OrderStateValid,
|
||||
OrderStateInvalid,
|
||||
OrderState,
|
||||
} from './types';
|
||||
88
packages/0x.js/src/order_watcher/event_watcher.ts
Normal file
88
packages/0x.js/src/order_watcher/event_watcher.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as Web3 from 'web3';
|
||||
import * as _ from 'lodash';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {
|
||||
BlockParamLiteral,
|
||||
EventCallback,
|
||||
EventWatcherCallback,
|
||||
ZeroExError,
|
||||
} from '../types';
|
||||
import {AbiDecoder} from '../utils/abi_decoder';
|
||||
import {intervalUtils} from '../utils/interval_utils';
|
||||
import {assert} from '../utils/assert';
|
||||
import {utils} from '../utils/utils';
|
||||
|
||||
const DEFAULT_EVENT_POLLING_INTERVAL = 200;
|
||||
|
||||
enum LogEventState {
|
||||
Removed,
|
||||
Added,
|
||||
}
|
||||
|
||||
/*
|
||||
* The EventWatcher watches for blockchain events at the specified block confirmation
|
||||
* depth.
|
||||
*/
|
||||
export class EventWatcher {
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _pollingIntervalMs: number;
|
||||
private _intervalIdIfExists?: NodeJS.Timer;
|
||||
private _lastEvents: Web3.LogEntry[] = [];
|
||||
constructor(web3Wrapper: Web3Wrapper, pollingIntervalMs: undefined|number) {
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
this._pollingIntervalMs = _.isUndefined(pollingIntervalMs) ?
|
||||
DEFAULT_EVENT_POLLING_INTERVAL :
|
||||
pollingIntervalMs;
|
||||
}
|
||||
public subscribe(callback: EventWatcherCallback): void {
|
||||
assert.isFunction('callback', callback);
|
||||
if (!_.isUndefined(this._intervalIdIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
|
||||
}
|
||||
this._intervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
|
||||
this._pollForBlockchainEventsAsync.bind(this, callback), this._pollingIntervalMs,
|
||||
);
|
||||
}
|
||||
public unsubscribe(): void {
|
||||
this._lastEvents = [];
|
||||
if (!_.isUndefined(this._intervalIdIfExists)) {
|
||||
intervalUtils.clearAsyncExcludingInterval(this._intervalIdIfExists);
|
||||
delete this._intervalIdIfExists;
|
||||
}
|
||||
}
|
||||
private async _pollForBlockchainEventsAsync(callback: EventWatcherCallback): Promise<void> {
|
||||
const pendingEvents = await this._getEventsAsync();
|
||||
if (pendingEvents.length === 0) {
|
||||
// HACK: Sometimes when node rebuilds the pending block we get back the empty result.
|
||||
// We don't want to emit a lot of removal events and bring them back after a couple of miliseconds,
|
||||
// that's why we just ignore those cases.
|
||||
return;
|
||||
}
|
||||
const removedEvents = _.differenceBy(this._lastEvents, pendingEvents, JSON.stringify);
|
||||
const newEvents = _.differenceBy(pendingEvents, this._lastEvents, JSON.stringify);
|
||||
await this._emitDifferencesAsync(removedEvents, LogEventState.Removed, callback);
|
||||
await this._emitDifferencesAsync(newEvents, LogEventState.Added, callback);
|
||||
this._lastEvents = pendingEvents;
|
||||
}
|
||||
private async _getEventsAsync(): Promise<Web3.LogEntry[]> {
|
||||
const eventFilter = {
|
||||
fromBlock: BlockParamLiteral.Pending,
|
||||
toBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const events = await this._web3Wrapper.getLogsAsync(eventFilter);
|
||||
return events;
|
||||
}
|
||||
private async _emitDifferencesAsync(
|
||||
logs: Web3.LogEntry[], logEventState: LogEventState, callback: EventWatcherCallback,
|
||||
): Promise<void> {
|
||||
for (const log of logs) {
|
||||
const logEvent = {
|
||||
removed: logEventState === LogEventState.Removed,
|
||||
...log,
|
||||
};
|
||||
if (!_.isUndefined(this._intervalIdIfExists)) {
|
||||
callback(logEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
240
packages/0x.js/src/order_watcher/order_state_watcher.ts
Normal file
240
packages/0x.js/src/order_watcher/order_state_watcher.ts
Normal file
@@ -0,0 +1,240 @@
|
||||
import * as _ from 'lodash';
|
||||
import {schemas} from '@0xproject/json-schemas';
|
||||
import {ZeroEx} from '../0x';
|
||||
import {EventWatcher} from './event_watcher';
|
||||
import {assert} from '../utils/assert';
|
||||
import {utils} from '../utils/utils';
|
||||
import {artifacts} from '../artifacts';
|
||||
import {AbiDecoder} from '../utils/abi_decoder';
|
||||
import {OrderStateUtils} from '../utils/order_state_utils';
|
||||
import {
|
||||
LogEvent,
|
||||
OrderState,
|
||||
SignedOrder,
|
||||
Web3Provider,
|
||||
BlockParamLiteral,
|
||||
LogWithDecodedArgs,
|
||||
ContractEventArgs,
|
||||
OnOrderStateChangeCallback,
|
||||
OrderStateWatcherConfig,
|
||||
ApprovalContractEventArgs,
|
||||
TransferContractEventArgs,
|
||||
LogFillContractEventArgs,
|
||||
LogCancelContractEventArgs,
|
||||
ExchangeEvents,
|
||||
TokenEvents,
|
||||
ZeroExError,
|
||||
} from '../types';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
|
||||
import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store';
|
||||
import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';
|
||||
|
||||
const DEFAULT_NUM_CONFIRMATIONS = 0;
|
||||
|
||||
interface DependentOrderHashes {
|
||||
[makerAddress: string]: {
|
||||
[makerToken: string]: Set<string>,
|
||||
};
|
||||
}
|
||||
|
||||
interface OrderByOrderHash {
|
||||
[orderHash: string]: SignedOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to watching a set of orders
|
||||
* for potential changes in order validity/fillability. The orderWatcher notifies
|
||||
* the subscriber of these changes so that a final decison can be made on whether
|
||||
* the order should be deemed invalid.
|
||||
*/
|
||||
export class OrderStateWatcher {
|
||||
private _orderByOrderHash: OrderByOrderHash = {};
|
||||
private _dependentOrderHashes: DependentOrderHashes = {};
|
||||
private _callbackIfExists?: OnOrderStateChangeCallback;
|
||||
private _eventWatcher: EventWatcher;
|
||||
private _web3Wrapper: Web3Wrapper;
|
||||
private _abiDecoder: AbiDecoder;
|
||||
private _orderStateUtils: OrderStateUtils;
|
||||
private _orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
|
||||
private _balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
|
||||
constructor(
|
||||
web3Wrapper: Web3Wrapper, abiDecoder: AbiDecoder, token: TokenWrapper, exchange: ExchangeWrapper,
|
||||
config?: OrderStateWatcherConfig,
|
||||
) {
|
||||
this._abiDecoder = abiDecoder;
|
||||
this._web3Wrapper = web3Wrapper;
|
||||
const eventPollingIntervalMs = _.isUndefined(config) ? undefined : config.eventPollingIntervalMs;
|
||||
this._eventWatcher = new EventWatcher(web3Wrapper, eventPollingIntervalMs);
|
||||
this._balanceAndProxyAllowanceLazyStore = new BalanceAndProxyAllowanceLazyStore(token);
|
||||
this._orderFilledCancelledLazyStore = new OrderFilledCancelledLazyStore(exchange);
|
||||
this._orderStateUtils = new OrderStateUtils(
|
||||
this._balanceAndProxyAllowanceLazyStore, this._orderFilledCancelledLazyStore,
|
||||
);
|
||||
}
|
||||
/**
|
||||
* Add an order to the orderStateWatcher. Before the order is added, it's
|
||||
* signature is verified.
|
||||
* @param signedOrder The order you wish to start watching.
|
||||
*/
|
||||
public async addOrderAsync(signedOrder: SignedOrder): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
assert.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker);
|
||||
this._orderByOrderHash[orderHash] = signedOrder;
|
||||
await this.addToDependentOrderHashesAsync(signedOrder, orderHash);
|
||||
}
|
||||
/**
|
||||
* Removes an order from the orderStateWatcher
|
||||
* @param orderHash The orderHash of the order you wish to stop watching.
|
||||
*/
|
||||
public async removeOrderAsync(orderHash: string): Promise<void> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
const signedOrder = this._orderByOrderHash[orderHash];
|
||||
if (_.isUndefined(signedOrder)) {
|
||||
return; // noop
|
||||
}
|
||||
delete this._orderByOrderHash[orderHash];
|
||||
const exchange = (this._orderFilledCancelledLazyStore as any).exchange as ExchangeWrapper;
|
||||
const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();
|
||||
this.removeFromDependentOrderHashes(signedOrder.maker, zrxTokenAddress, orderHash);
|
||||
this.removeFromDependentOrderHashes(signedOrder.maker, signedOrder.makerTokenAddress, orderHash);
|
||||
}
|
||||
/**
|
||||
* Starts an orderStateWatcher subscription. The callback will be called every time a watched order's
|
||||
* backing blockchain state has changed. This is a call-to-action for the caller to re-validate the order.
|
||||
* @param callback Receives the orderHash of the order that should be re-validated, together
|
||||
* with all the order-relevant blockchain state needed to re-validate the order.
|
||||
*/
|
||||
public subscribe(callback: OnOrderStateChangeCallback): void {
|
||||
assert.isFunction('callback', callback);
|
||||
if (!_.isUndefined(this._callbackIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionAlreadyPresent);
|
||||
}
|
||||
this._callbackIfExists = callback;
|
||||
this._eventWatcher.subscribe(this._onEventWatcherCallbackAsync.bind(this));
|
||||
}
|
||||
/**
|
||||
* Ends an orderStateWatcher subscription.
|
||||
*/
|
||||
public unsubscribe(): void {
|
||||
if (_.isUndefined(this._callbackIfExists)) {
|
||||
throw new Error(ZeroExError.SubscriptionNotFound);
|
||||
}
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteAll();
|
||||
this._orderFilledCancelledLazyStore.deleteAll();
|
||||
delete this._callbackIfExists;
|
||||
this._eventWatcher.unsubscribe();
|
||||
}
|
||||
private async _onEventWatcherCallbackAsync(log: LogEvent): Promise<void> {
|
||||
const maybeDecodedLog = this._abiDecoder.tryToDecodeLogOrNoop(log);
|
||||
const isLogDecoded = !_.isUndefined((maybeDecodedLog as LogWithDecodedArgs<any>).event);
|
||||
if (!isLogDecoded) {
|
||||
return; // noop
|
||||
}
|
||||
const decodedLog = maybeDecodedLog as LogWithDecodedArgs<ContractEventArgs>;
|
||||
let makerToken: string;
|
||||
let makerAddress: string;
|
||||
switch (decodedLog.event) {
|
||||
case TokenEvents.Approval:
|
||||
{
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as ApprovalContractEventArgs;
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteProxyAllowance(decodedLog.address, args._owner);
|
||||
// Revalidate orders
|
||||
makerToken = decodedLog.address;
|
||||
makerAddress = args._owner;
|
||||
if (!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])) {
|
||||
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
|
||||
await this._emitRevalidateOrdersAsync(orderHashes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TokenEvents.Transfer:
|
||||
{
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as TransferContractEventArgs;
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._from);
|
||||
this._balanceAndProxyAllowanceLazyStore.deleteBalance(decodedLog.address, args._to);
|
||||
// Revalidate orders
|
||||
makerToken = decodedLog.address;
|
||||
makerAddress = args._from;
|
||||
if (!_.isUndefined(this._dependentOrderHashes[makerAddress]) &&
|
||||
!_.isUndefined(this._dependentOrderHashes[makerAddress][makerToken])) {
|
||||
const orderHashes = Array.from(this._dependentOrderHashes[makerAddress][makerToken]);
|
||||
await this._emitRevalidateOrdersAsync(orderHashes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExchangeEvents.LogFill:
|
||||
{
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as LogFillContractEventArgs;
|
||||
this._orderFilledCancelledLazyStore.deleteFilledTakerAmount(args.orderHash);
|
||||
// Revalidate orders
|
||||
const orderHash = args.orderHash;
|
||||
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
|
||||
if (isOrderWatched) {
|
||||
await this._emitRevalidateOrdersAsync([orderHash]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExchangeEvents.LogCancel:
|
||||
{
|
||||
// Invalidate cache
|
||||
const args = decodedLog.args as LogCancelContractEventArgs;
|
||||
this._orderFilledCancelledLazyStore.deleteCancelledTakerAmount(args.orderHash);
|
||||
// Revalidate orders
|
||||
const orderHash = args.orderHash;
|
||||
const isOrderWatched = !_.isUndefined(this._orderByOrderHash[orderHash]);
|
||||
if (isOrderWatched) {
|
||||
await this._emitRevalidateOrdersAsync([orderHash]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ExchangeEvents.LogError:
|
||||
return; // noop
|
||||
|
||||
default:
|
||||
throw utils.spawnSwitchErr('decodedLog.event', decodedLog.event);
|
||||
}
|
||||
}
|
||||
private async _emitRevalidateOrdersAsync(orderHashes: string[]): Promise<void> {
|
||||
for (const orderHash of orderHashes) {
|
||||
const signedOrder = this._orderByOrderHash[orderHash] as SignedOrder;
|
||||
// Most of these calls will never reach the network because the data is fetched from stores
|
||||
// and only updated when cache is invalidated
|
||||
const orderState = await this._orderStateUtils.getOrderStateAsync(signedOrder);
|
||||
if (_.isUndefined(this._callbackIfExists)) {
|
||||
break; // Unsubscribe was called
|
||||
}
|
||||
this._callbackIfExists(orderState);
|
||||
}
|
||||
}
|
||||
private async addToDependentOrderHashesAsync(signedOrder: SignedOrder, orderHash: string): Promise<void> {
|
||||
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker])) {
|
||||
this._dependentOrderHashes[signedOrder.maker] = {};
|
||||
}
|
||||
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress])) {
|
||||
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress] = new Set();
|
||||
}
|
||||
this._dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress].add(orderHash);
|
||||
const exchange = (this._orderFilledCancelledLazyStore as any).exchange as ExchangeWrapper;
|
||||
const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();
|
||||
if (_.isUndefined(this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress])) {
|
||||
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress] = new Set();
|
||||
}
|
||||
this._dependentOrderHashes[signedOrder.maker][zrxTokenAddress].add(orderHash);
|
||||
}
|
||||
private removeFromDependentOrderHashes(makerAddress: string, tokenAddress: string, orderHash: string) {
|
||||
this._dependentOrderHashes[makerAddress][tokenAddress].delete(orderHash);
|
||||
if (this._dependentOrderHashes[makerAddress][tokenAddress].size === 0) {
|
||||
delete this._dependentOrderHashes[makerAddress][tokenAddress];
|
||||
}
|
||||
if (_.isEmpty(this._dependentOrderHashes[makerAddress])) {
|
||||
delete this._dependentOrderHashes[makerAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
23
packages/0x.js/src/schemas/zero_ex_config_schema.ts
Normal file
23
packages/0x.js/src/schemas/zero_ex_config_schema.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export const zeroExConfigSchema = {
|
||||
id: '/ZeroExConfig',
|
||||
properties: {
|
||||
gasPrice: {$ref: '/Number'},
|
||||
exchangeContractAddress: {$ref: '/Address'},
|
||||
tokenRegistryContractAddress: {$ref: '/Address'},
|
||||
etherTokenContractAddress: {$ref: '/Address'},
|
||||
orderWatcherConfig: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
pollingIntervalMs: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
},
|
||||
numConfirmations: {
|
||||
type: 'number',
|
||||
minimum: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
type: 'object',
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import {BigNumber} from 'bignumber.js';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
import {BlockParamLiteral} from '../types';
|
||||
|
||||
/**
|
||||
* Copy on read store for balances/proxyAllowances of tokens/accounts
|
||||
*/
|
||||
export class BalanceAndProxyAllowanceLazyStore {
|
||||
private token: TokenWrapper;
|
||||
private balance: {
|
||||
[tokenAddress: string]: {
|
||||
[userAddress: string]: BigNumber,
|
||||
},
|
||||
};
|
||||
private proxyAllowance: {
|
||||
[tokenAddress: string]: {
|
||||
[userAddress: string]: BigNumber,
|
||||
},
|
||||
};
|
||||
constructor(token: TokenWrapper) {
|
||||
this.token = token;
|
||||
this.balance = {};
|
||||
this.proxyAllowance = {};
|
||||
}
|
||||
public async getBalanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this.balance[tokenAddress]) || _.isUndefined(this.balance[tokenAddress][userAddress])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const balance = await this.token.getBalanceAsync(tokenAddress, userAddress, methodOpts);
|
||||
this.setBalance(tokenAddress, userAddress, balance);
|
||||
}
|
||||
const cachedBalance = this.balance[tokenAddress][userAddress];
|
||||
return cachedBalance;
|
||||
}
|
||||
public setBalance(tokenAddress: string, userAddress: string, balance: BigNumber): void {
|
||||
if (_.isUndefined(this.balance[tokenAddress])) {
|
||||
this.balance[tokenAddress] = {};
|
||||
}
|
||||
this.balance[tokenAddress][userAddress] = balance;
|
||||
}
|
||||
public deleteBalance(tokenAddress: string, userAddress: string): void {
|
||||
if (!_.isUndefined(this.balance[tokenAddress])) {
|
||||
delete this.balance[tokenAddress][userAddress];
|
||||
if (_.isEmpty(this.balance[tokenAddress])) {
|
||||
delete this.balance[tokenAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
public async getProxyAllowanceAsync(tokenAddress: string, userAddress: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this.proxyAllowance[tokenAddress]) ||
|
||||
_.isUndefined(this.proxyAllowance[tokenAddress][userAddress])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const proxyAllowance = await this.token.getProxyAllowanceAsync(tokenAddress, userAddress, methodOpts);
|
||||
this.setProxyAllowance(tokenAddress, userAddress, proxyAllowance);
|
||||
}
|
||||
const cachedProxyAllowance = this.proxyAllowance[tokenAddress][userAddress];
|
||||
return cachedProxyAllowance;
|
||||
}
|
||||
public setProxyAllowance(tokenAddress: string, userAddress: string, proxyAllowance: BigNumber): void {
|
||||
if (_.isUndefined(this.proxyAllowance[tokenAddress])) {
|
||||
this.proxyAllowance[tokenAddress] = {};
|
||||
}
|
||||
this.proxyAllowance[tokenAddress][userAddress] = proxyAllowance;
|
||||
}
|
||||
public deleteProxyAllowance(tokenAddress: string, userAddress: string): void {
|
||||
if (!_.isUndefined(this.proxyAllowance[tokenAddress])) {
|
||||
delete this.proxyAllowance[tokenAddress][userAddress];
|
||||
if (_.isEmpty(this.proxyAllowance[tokenAddress])) {
|
||||
delete this.proxyAllowance[tokenAddress];
|
||||
}
|
||||
}
|
||||
}
|
||||
public deleteAll(): void {
|
||||
this.balance = {};
|
||||
this.proxyAllowance = {};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import {BigNumber} from 'bignumber.js';
|
||||
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
|
||||
import {BlockParamLiteral} from '../types';
|
||||
|
||||
/**
|
||||
* Copy on read store for filled/cancelled taker amounts
|
||||
*/
|
||||
export class OrderFilledCancelledLazyStore {
|
||||
private exchange: ExchangeWrapper;
|
||||
private filledTakerAmount: {
|
||||
[orderHash: string]: BigNumber,
|
||||
};
|
||||
private cancelledTakerAmount: {
|
||||
[orderHash: string]: BigNumber,
|
||||
};
|
||||
constructor(exchange: ExchangeWrapper) {
|
||||
this.exchange = exchange;
|
||||
this.filledTakerAmount = {};
|
||||
this.cancelledTakerAmount = {};
|
||||
}
|
||||
public async getFilledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this.filledTakerAmount[orderHash])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const filledTakerAmount = await this.exchange.getFilledTakerAmountAsync(orderHash, methodOpts);
|
||||
this.setFilledTakerAmount(orderHash, filledTakerAmount);
|
||||
}
|
||||
const cachedFilled = this.filledTakerAmount[orderHash];
|
||||
return cachedFilled;
|
||||
}
|
||||
public setFilledTakerAmount(orderHash: string, filledTakerAmount: BigNumber): void {
|
||||
this.filledTakerAmount[orderHash] = filledTakerAmount;
|
||||
}
|
||||
public deleteFilledTakerAmount(orderHash: string): void {
|
||||
delete this.filledTakerAmount[orderHash];
|
||||
}
|
||||
public async getCancelledTakerAmountAsync(orderHash: string): Promise<BigNumber> {
|
||||
if (_.isUndefined(this.cancelledTakerAmount[orderHash])) {
|
||||
const methodOpts = {
|
||||
defaultBlock: BlockParamLiteral.Pending,
|
||||
};
|
||||
const cancelledTakerAmount = await this.exchange.getCancelledTakerAmountAsync(orderHash, methodOpts);
|
||||
this.setCancelledTakerAmount(orderHash, cancelledTakerAmount);
|
||||
}
|
||||
const cachedCancelled = this.cancelledTakerAmount[orderHash];
|
||||
return cachedCancelled;
|
||||
}
|
||||
public setCancelledTakerAmount(orderHash: string, cancelledTakerAmount: BigNumber): void {
|
||||
this.cancelledTakerAmount[orderHash] = cancelledTakerAmount;
|
||||
}
|
||||
public deleteCancelledTakerAmount(orderHash: string): void {
|
||||
delete this.cancelledTakerAmount[orderHash];
|
||||
}
|
||||
public deleteAll(): void {
|
||||
this.filledTakerAmount = {};
|
||||
this.cancelledTakerAmount = {};
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
import * as Web3 from 'web3';
|
||||
import {JSONRPCPayload} from '../types';
|
||||
|
||||
/*
|
||||
519
packages/0x.js/src/types.ts
Normal file
519
packages/0x.js/src/types.ts
Normal file
@@ -0,0 +1,519 @@
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export enum ZeroExError {
|
||||
ContractDoesNotExist = 'CONTRACT_DOES_NOT_EXIST',
|
||||
ExchangeContractDoesNotExist = 'EXCHANGE_CONTRACT_DOES_NOT_EXIST',
|
||||
UnhandledError = 'UNHANDLED_ERROR',
|
||||
UserHasNoAssociatedAddress = 'USER_HAS_NO_ASSOCIATED_ADDRESSES',
|
||||
InvalidSignature = 'INVALID_SIGNATURE',
|
||||
ContractNotDeployedOnNetwork = 'CONTRACT_NOT_DEPLOYED_ON_NETWORK',
|
||||
InsufficientAllowanceForTransfer = 'INSUFFICIENT_ALLOWANCE_FOR_TRANSFER',
|
||||
InsufficientBalanceForTransfer = 'INSUFFICIENT_BALANCE_FOR_TRANSFER',
|
||||
InsufficientEthBalanceForDeposit = 'INSUFFICIENT_ETH_BALANCE_FOR_DEPOSIT',
|
||||
InsufficientWEthBalanceForWithdrawal = 'INSUFFICIENT_WETH_BALANCE_FOR_WITHDRAWAL',
|
||||
InvalidJump = 'INVALID_JUMP',
|
||||
OutOfGas = 'OUT_OF_GAS',
|
||||
NoNetworkId = 'NO_NETWORK_ID',
|
||||
SubscriptionNotFound = 'SUBSCRIPTION_NOT_FOUND',
|
||||
SubscriptionAlreadyPresent = 'SUBSCRIPTION_ALREADY_PRESENT',
|
||||
TransactionMiningTimeout = 'TRANSACTION_MINING_TIMEOUT',
|
||||
}
|
||||
|
||||
export enum InternalZeroExError {
|
||||
NoAbiDecoder = 'NO_ABI_DECODER',
|
||||
ZrxNotInTokenRegistry = 'ZRX_NOT_IN_TOKEN_REGISTRY',
|
||||
}
|
||||
|
||||
/**
|
||||
* Elliptic Curve signature
|
||||
*/
|
||||
export interface ECSignature {
|
||||
v: number;
|
||||
r: string;
|
||||
s: string;
|
||||
}
|
||||
|
||||
export type OrderAddresses = [string, string, string, string, string];
|
||||
|
||||
export type OrderValues = [BigNumber, BigNumber, BigNumber,
|
||||
BigNumber, BigNumber, BigNumber];
|
||||
|
||||
export type LogEvent = Web3.LogEntryEvent;
|
||||
export type DecodedLogEvent<ArgsType> = Web3.DecodedLogEntryEvent<ArgsType>;
|
||||
|
||||
export type EventCallback<ArgsType> = (err: null|Error, log?: DecodedLogEvent<ArgsType>) => void;
|
||||
export type EventWatcherCallback = (log: LogEvent) => void;
|
||||
|
||||
export interface ExchangeContract extends Web3.ContractInstance {
|
||||
isValidSignature: {
|
||||
callAsync: (signerAddressHex: string, dataHex: string, v: number, r: string, s: string,
|
||||
txOpts?: TxOpts) => Promise<boolean>;
|
||||
};
|
||||
ZRX_TOKEN_CONTRACT: {
|
||||
callAsync: () => Promise<string>;
|
||||
};
|
||||
TOKEN_TRANSFER_PROXY_CONTRACT: {
|
||||
callAsync: () => Promise<string>;
|
||||
};
|
||||
getUnavailableTakerTokenAmount: {
|
||||
callAsync: (orderHash: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber>;
|
||||
};
|
||||
isRoundingError: {
|
||||
callAsync: (takerTokenFillAmount: BigNumber, takerTokenAmount: BigNumber,
|
||||
makerTokenAmount: BigNumber, txOpts?: TxOpts) => Promise<boolean>;
|
||||
};
|
||||
fillOrder: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
batchFillOrders: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
fillTakerTokenAmounts: BigNumber[],
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
fillTakerTokenAmounts: BigNumber[],
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
fillOrdersUpTo: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
shouldThrowOnInsufficientBalanceOrAllowance: boolean,
|
||||
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
cancelOrder: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
|
||||
cancelTakerTokenAmount: BigNumber, txOpts?: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
|
||||
cancelTakerTokenAmount: BigNumber,
|
||||
txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
batchCancelOrders: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
cancelTakerTokenAmounts: BigNumber[], txOpts?: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
cancelTakerTokenAmounts: BigNumber[],
|
||||
txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
fillOrKillOrder: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues,
|
||||
fillTakerTokenAmount: BigNumber,
|
||||
v: number, r: string, s: string, txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
batchFillOrKillOrders: {
|
||||
sendTransactionAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
fillTakerTokenAmounts: BigNumber[],
|
||||
v: number[], r: string[], s: string[], txOpts: TxOpts) => Promise<string>;
|
||||
estimateGasAsync: (orderAddresses: OrderAddresses[], orderValues: OrderValues[],
|
||||
fillTakerTokenAmounts: BigNumber[],
|
||||
v: number[], r: string[], s: string[], txOpts?: TxOpts) => Promise<number>;
|
||||
};
|
||||
filled: {
|
||||
callAsync: (orderHash: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber>;
|
||||
};
|
||||
cancelled: {
|
||||
callAsync: (orderHash: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber>;
|
||||
};
|
||||
getOrderHash: {
|
||||
callAsync: (orderAddresses: OrderAddresses, orderValues: OrderValues) => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TokenContract extends Web3.ContractInstance {
|
||||
balanceOf: {
|
||||
callAsync: (address: string, defaultBlock?: Web3.BlockParam) => Promise<BigNumber>;
|
||||
};
|
||||
allowance: {
|
||||
callAsync: (ownerAddress: string, allowedAddress: string,
|
||||
defaultBlock?: Web3.BlockParam) => Promise<BigNumber>;
|
||||
};
|
||||
transfer: {
|
||||
sendTransactionAsync: (toAddress: string, amountInBaseUnits: BigNumber,
|
||||
txOpts?: TxOpts) => Promise<string>;
|
||||
};
|
||||
transferFrom: {
|
||||
sendTransactionAsync: (fromAddress: string, toAddress: string, amountInBaseUnits: BigNumber,
|
||||
txOpts?: TxOpts) => Promise<string>;
|
||||
};
|
||||
approve: {
|
||||
sendTransactionAsync: (proxyAddress: string, amountInBaseUnits: BigNumber,
|
||||
txOpts?: TxOpts) => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TokenRegistryContract extends Web3.ContractInstance {
|
||||
getTokenMetaData: {
|
||||
callAsync: (address: string) => Promise<TokenMetadata>;
|
||||
};
|
||||
getTokenAddresses: {
|
||||
callAsync: () => Promise<string[]>;
|
||||
};
|
||||
getTokenAddressBySymbol: {
|
||||
callAsync: (symbol: string) => Promise<string>;
|
||||
};
|
||||
getTokenAddressByName: {
|
||||
callAsync: (name: string) => Promise<string>;
|
||||
};
|
||||
getTokenBySymbol: {
|
||||
callAsync: (symbol: string) => Promise<TokenMetadata>;
|
||||
};
|
||||
getTokenByName: {
|
||||
callAsync: (name: string) => Promise<TokenMetadata>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface EtherTokenContract extends Web3.ContractInstance {
|
||||
deposit: {
|
||||
sendTransactionAsync: (txOpts: TxOpts) => Promise<string>;
|
||||
};
|
||||
withdraw: {
|
||||
sendTransactionAsync: (amount: BigNumber, txOpts: TxOpts) => Promise<string>;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TokenTransferProxyContract extends Web3.ContractInstance {
|
||||
getAuthorizedAddresses: {
|
||||
callAsync: () => Promise<string[]>;
|
||||
};
|
||||
authorized: {
|
||||
callAsync: (address: string) => Promise<boolean>;
|
||||
};
|
||||
}
|
||||
|
||||
export enum SolidityTypes {
|
||||
Address = 'address',
|
||||
Uint256 = 'uint256',
|
||||
Uint8 = 'uint8',
|
||||
Uint = 'uint',
|
||||
}
|
||||
|
||||
export enum ExchangeContractErrCodes {
|
||||
ERROR_FILL_EXPIRED, // Order has already expired
|
||||
ERROR_FILL_NO_VALUE, // Order has already been fully filled or cancelled
|
||||
ERROR_FILL_TRUNCATION, // Rounding error too large
|
||||
ERROR_FILL_BALANCE_ALLOWANCE, // Insufficient balance or allowance for token transfer
|
||||
ERROR_CANCEL_EXPIRED, // Order has already expired
|
||||
ERROR_CANCEL_NO_VALUE, // Order has already been fully filled or cancelled
|
||||
}
|
||||
|
||||
export enum ExchangeContractErrs {
|
||||
OrderFillExpired = 'ORDER_FILL_EXPIRED',
|
||||
OrderCancelExpired = 'ORDER_CANCEL_EXPIRED',
|
||||
OrderCancelAmountZero = 'ORDER_CANCEL_AMOUNT_ZERO',
|
||||
OrderAlreadyCancelledOrFilled = 'ORDER_ALREADY_CANCELLED_OR_FILLED',
|
||||
OrderFillAmountZero = 'ORDER_FILL_AMOUNT_ZERO',
|
||||
OrderRemainingFillAmountZero = 'ORDER_REMAINING_FILL_AMOUNT_ZERO',
|
||||
OrderFillRoundingError = 'ORDER_FILL_ROUNDING_ERROR',
|
||||
FillBalanceAllowanceError = 'FILL_BALANCE_ALLOWANCE_ERROR',
|
||||
InsufficientTakerBalance = 'INSUFFICIENT_TAKER_BALANCE',
|
||||
InsufficientTakerAllowance = 'INSUFFICIENT_TAKER_ALLOWANCE',
|
||||
InsufficientMakerBalance = 'INSUFFICIENT_MAKER_BALANCE',
|
||||
InsufficientMakerAllowance = 'INSUFFICIENT_MAKER_ALLOWANCE',
|
||||
InsufficientTakerFeeBalance = 'INSUFFICIENT_TAKER_FEE_BALANCE',
|
||||
InsufficientTakerFeeAllowance = 'INSUFFICIENT_TAKER_FEE_ALLOWANCE',
|
||||
InsufficientMakerFeeBalance = 'INSUFFICIENT_MAKER_FEE_BALANCE',
|
||||
InsufficientMakerFeeAllowance = 'INSUFFICIENT_MAKER_FEE_ALLOWANCE',
|
||||
TransactionSenderIsNotFillOrderTaker = 'TRANSACTION_SENDER_IS_NOT_FILL_ORDER_TAKER',
|
||||
MultipleMakersInSingleCancelBatchDisallowed = 'MULTIPLE_MAKERS_IN_SINGLE_CANCEL_BATCH_DISALLOWED',
|
||||
InsufficientRemainingFillAmount = 'INSUFFICIENT_REMAINING_FILL_AMOUNT',
|
||||
MultipleTakerTokensInFillUpToDisallowed = 'MULTIPLE_TAKER_TOKENS_IN_FILL_UP_TO_DISALLOWED',
|
||||
BatchOrdersMustHaveSameExchangeAddress = 'BATCH_ORDERS_MUST_HAVE_SAME_EXCHANGE_ADDRESS',
|
||||
BatchOrdersMustHaveAtLeastOneItem = 'BATCH_ORDERS_MUST_HAVE_AT_LEAST_ONE_ITEM',
|
||||
}
|
||||
|
||||
export type RawLog = Web3.LogEntry;
|
||||
|
||||
export interface ContractEvent {
|
||||
logIndex: number;
|
||||
transactionIndex: number;
|
||||
transactionHash: string;
|
||||
blockHash: string;
|
||||
blockNumber: number;
|
||||
address: string;
|
||||
type: string;
|
||||
event: string;
|
||||
args: ContractEventArgs;
|
||||
}
|
||||
|
||||
export interface LogFillContractEventArgs {
|
||||
maker: string;
|
||||
taker: string;
|
||||
feeRecipient: string;
|
||||
makerToken: string;
|
||||
takerToken: string;
|
||||
filledMakerTokenAmount: BigNumber;
|
||||
filledTakerTokenAmount: BigNumber;
|
||||
paidMakerFee: BigNumber;
|
||||
paidTakerFee: BigNumber;
|
||||
tokens: string;
|
||||
orderHash: string;
|
||||
}
|
||||
export interface LogCancelContractEventArgs {
|
||||
maker: string;
|
||||
feeRecipient: string;
|
||||
makerToken: string;
|
||||
takerToken: string;
|
||||
cancelledMakerTokenAmount: BigNumber;
|
||||
cancelledTakerTokenAmount: BigNumber;
|
||||
tokens: string;
|
||||
orderHash: string;
|
||||
}
|
||||
export interface LogErrorContractEventArgs {
|
||||
errorId: BigNumber;
|
||||
orderHash: string;
|
||||
}
|
||||
export type ExchangeContractEventArgs = LogFillContractEventArgs|LogCancelContractEventArgs|LogErrorContractEventArgs;
|
||||
export interface TransferContractEventArgs {
|
||||
_from: string;
|
||||
_to: string;
|
||||
_value: BigNumber;
|
||||
}
|
||||
export interface ApprovalContractEventArgs {
|
||||
_owner: string;
|
||||
_spender: string;
|
||||
_value: BigNumber;
|
||||
}
|
||||
export type TokenContractEventArgs = TransferContractEventArgs|ApprovalContractEventArgs;
|
||||
export type ContractEventArgs = ExchangeContractEventArgs|TokenContractEventArgs;
|
||||
export type ContractEventArg = string|BigNumber;
|
||||
|
||||
export interface Order {
|
||||
maker: string;
|
||||
taker: string;
|
||||
makerFee: BigNumber;
|
||||
takerFee: BigNumber;
|
||||
makerTokenAmount: BigNumber;
|
||||
takerTokenAmount: BigNumber;
|
||||
makerTokenAddress: string;
|
||||
takerTokenAddress: string;
|
||||
salt: BigNumber;
|
||||
exchangeContractAddress: string;
|
||||
feeRecipient: string;
|
||||
expirationUnixTimestampSec: BigNumber;
|
||||
}
|
||||
|
||||
export interface SignedOrder extends Order {
|
||||
ecSignature: ECSignature;
|
||||
}
|
||||
|
||||
// [address, name, symbol, decimals, ipfsHash, swarmHash]
|
||||
export type TokenMetadata = [string, string, string, BigNumber, string, string];
|
||||
|
||||
export interface Token {
|
||||
name: string;
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
}
|
||||
|
||||
export interface TxOpts {
|
||||
from: string;
|
||||
gas?: number;
|
||||
value?: BigNumber;
|
||||
}
|
||||
|
||||
export interface TokenAddressBySymbol {
|
||||
[symbol: string]: string;
|
||||
}
|
||||
|
||||
export enum ExchangeEvents {
|
||||
LogFill = 'LogFill',
|
||||
LogCancel = 'LogCancel',
|
||||
LogError = 'LogError',
|
||||
}
|
||||
|
||||
export enum TokenEvents {
|
||||
Transfer = 'Transfer',
|
||||
Approval = 'Approval',
|
||||
}
|
||||
|
||||
export type ContractEvents = TokenEvents|ExchangeEvents;
|
||||
|
||||
export interface IndexedFilterValues {
|
||||
[index: string]: ContractEventArg;
|
||||
}
|
||||
|
||||
export enum BlockParamLiteral {
|
||||
Latest = 'latest',
|
||||
Earliest = 'earliest',
|
||||
Pending = 'pending',
|
||||
}
|
||||
|
||||
export type BlockParam = BlockParamLiteral|number;
|
||||
|
||||
export interface SubscriptionOpts {
|
||||
fromBlock: BlockParam;
|
||||
toBlock: BlockParam;
|
||||
}
|
||||
|
||||
export type DoneCallback = (err?: Error) => void;
|
||||
|
||||
export interface OrderCancellationRequest {
|
||||
order: Order|SignedOrder;
|
||||
takerTokenCancelAmount: BigNumber;
|
||||
}
|
||||
|
||||
export interface OrderFillRequest {
|
||||
signedOrder: SignedOrder;
|
||||
takerTokenFillAmount: BigNumber;
|
||||
}
|
||||
|
||||
export type AsyncMethod = (...args: any[]) => Promise<any>;
|
||||
|
||||
/**
|
||||
* We re-export the `Web3.Provider` type specified in the Web3 Typescript typings
|
||||
* since it is the type of the `provider` argument to the `ZeroEx` constructor.
|
||||
* It is however a `Web3` library type, not a native `0x.js` type.
|
||||
*/
|
||||
export type Web3Provider = Web3.Provider;
|
||||
|
||||
export interface ExchangeContractByAddress {
|
||||
[address: string]: ExchangeContract;
|
||||
}
|
||||
|
||||
export interface JSONRPCPayload {
|
||||
params: any[];
|
||||
method: string;
|
||||
}
|
||||
|
||||
/*
|
||||
* eventPollingIntervalMs: How often to poll the Ethereum node for new events
|
||||
*/
|
||||
export interface OrderStateWatcherConfig {
|
||||
eventPollingIntervalMs?: number;
|
||||
}
|
||||
|
||||
/*
|
||||
* gasPrice: Gas price to use with every transaction
|
||||
* exchangeContractAddress: The address of an exchange contract to use
|
||||
* tokenRegistryContractAddress: The address of a token registry contract to use
|
||||
* etherTokenContractAddress: The address of an ether token contract to use
|
||||
* orderWatcherConfig: All the configs related to the orderWatcher
|
||||
*/
|
||||
export interface ZeroExConfig {
|
||||
gasPrice?: BigNumber; // Gas price to use with every transaction
|
||||
exchangeContractAddress?: string;
|
||||
tokenRegistryContractAddress?: string;
|
||||
etherTokenContractAddress?: string;
|
||||
orderWatcherConfig?: OrderStateWatcherConfig;
|
||||
}
|
||||
|
||||
export enum AbiType {
|
||||
Function = 'function',
|
||||
Constructor = 'constructor',
|
||||
Event = 'event',
|
||||
Fallback = 'fallback',
|
||||
}
|
||||
|
||||
export interface DecodedLogArgs {
|
||||
[argName: string]: ContractEventArg;
|
||||
}
|
||||
|
||||
export interface LogWithDecodedArgs<ArgsType> extends Web3.DecodedLogEntry<ArgsType> {}
|
||||
|
||||
export interface TransactionReceiptWithDecodedLogs extends TransactionReceipt {
|
||||
logs: Array<LogWithDecodedArgs<DecodedLogArgs>|Web3.LogEntry>;
|
||||
}
|
||||
|
||||
export interface Artifact {
|
||||
abi: any;
|
||||
networks: {[networkId: number]: {
|
||||
address: string;
|
||||
}};
|
||||
}
|
||||
|
||||
/*
|
||||
* expectedFillTakerTokenAmount: If specified, the validation method will ensure that the
|
||||
* supplied order maker has a sufficient allowance/balance to fill this amount of the order's
|
||||
* takerTokenAmount. If not specified, the validation method ensures that the maker has a sufficient
|
||||
* allowance/balance to fill the entire remaining order amount.
|
||||
*/
|
||||
export interface ValidateOrderFillableOpts {
|
||||
expectedFillTakerTokenAmount?: BigNumber;
|
||||
}
|
||||
|
||||
/*
|
||||
* defaultBlock: The block up to which to query the blockchain state. Setting this to a historical block number
|
||||
* let's the user query the blockchain's state at an arbitrary point in time. In order for this to work, the
|
||||
* backing Ethereum node must keep the entire historical state of the chain (e.g setting `--pruning=archive`
|
||||
* flag when running Parity).
|
||||
*/
|
||||
export interface MethodOpts {
|
||||
defaultBlock?: Web3.BlockParam;
|
||||
}
|
||||
|
||||
/*
|
||||
* shouldValidate: Flag indicating whether the library should make attempts to validate a transaction before
|
||||
* broadcasting it. For example, order has a valid signature, maker has sufficient funds, etc.
|
||||
*/
|
||||
export interface OrderTransactionOpts {
|
||||
shouldValidate: boolean;
|
||||
}
|
||||
|
||||
export type FilterObject = Web3.FilterObject;
|
||||
|
||||
export enum TradeSide {
|
||||
Maker = 'maker',
|
||||
Taker = 'taker',
|
||||
}
|
||||
|
||||
export enum TransferType {
|
||||
Trade = 'trade',
|
||||
Fee = 'fee',
|
||||
}
|
||||
|
||||
export interface OrderRelevantState {
|
||||
makerBalance: BigNumber;
|
||||
makerProxyAllowance: BigNumber;
|
||||
makerFeeBalance: BigNumber;
|
||||
makerFeeProxyAllowance: BigNumber;
|
||||
filledTakerTokenAmount: BigNumber;
|
||||
cancelledTakerTokenAmount: BigNumber;
|
||||
remainingFillableMakerTokenAmount: BigNumber;
|
||||
remainingFillableTakerTokenAmount: BigNumber;
|
||||
}
|
||||
|
||||
export interface OrderStateValid {
|
||||
isValid: true;
|
||||
orderHash: string;
|
||||
orderRelevantState: OrderRelevantState;
|
||||
}
|
||||
|
||||
export interface OrderStateInvalid {
|
||||
isValid: false;
|
||||
orderHash: string;
|
||||
error: ExchangeContractErrs;
|
||||
}
|
||||
|
||||
export type OrderState = OrderStateValid|OrderStateInvalid;
|
||||
|
||||
export type OnOrderStateChangeCallback = (orderState: OrderState) => void;
|
||||
|
||||
export interface TransactionReceipt {
|
||||
blockHash: string;
|
||||
blockNumber: number;
|
||||
transactionHash: string;
|
||||
transactionIndex: number;
|
||||
from: string;
|
||||
to: string;
|
||||
status: null|0|1;
|
||||
cumulativeGasUsed: number;
|
||||
gasUsed: number;
|
||||
contractAddress: string|null;
|
||||
logs: Web3.LogEntry[];
|
||||
}
|
||||
68
packages/0x.js/src/utils/abi_decoder.ts
Normal file
68
packages/0x.js/src/utils/abi_decoder.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as Web3 from 'web3';
|
||||
import * as _ from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {AbiType, DecodedLogArgs, LogWithDecodedArgs, RawLog, SolidityTypes, ContractEventArgs} from '../types';
|
||||
import * as SolidityCoder from 'web3/lib/solidity/coder';
|
||||
|
||||
export class AbiDecoder {
|
||||
private savedABIs: Web3.AbiDefinition[] = [];
|
||||
private methodIds: {[signatureHash: string]: Web3.EventAbi} = {};
|
||||
constructor(abiArrays: Web3.AbiDefinition[][]) {
|
||||
_.map(abiArrays, this.addABI.bind(this));
|
||||
}
|
||||
// This method can only decode logs from the 0x & ERC20 smart contracts
|
||||
public tryToDecodeLogOrNoop<ArgsType extends ContractEventArgs>(
|
||||
log: Web3.LogEntry): LogWithDecodedArgs<ArgsType>|RawLog {
|
||||
const methodId = log.topics[0];
|
||||
const event = this.methodIds[methodId];
|
||||
if (_.isUndefined(event)) {
|
||||
return log;
|
||||
}
|
||||
const logData = log.data;
|
||||
const decodedParams: DecodedLogArgs = {};
|
||||
let dataIndex = 0;
|
||||
let topicsIndex = 1;
|
||||
|
||||
const nonIndexedInputs = _.filter(event.inputs, input => !input.indexed);
|
||||
const dataTypes = _.map(nonIndexedInputs, input => input.type);
|
||||
const decodedData = SolidityCoder.decodeParams(dataTypes, logData.slice('0x'.length));
|
||||
|
||||
_.map(event.inputs, (param: Web3.EventParameter) => {
|
||||
// Indexed parameters are stored in topics. Non-indexed ones in decodedData
|
||||
let value = param.indexed ? log.topics[topicsIndex++] : decodedData[dataIndex++];
|
||||
if (param.type === SolidityTypes.Address) {
|
||||
value = this.padZeros(new BigNumber(value).toString(16));
|
||||
} else if (param.type === SolidityTypes.Uint256 ||
|
||||
param.type === SolidityTypes.Uint8 ||
|
||||
param.type === SolidityTypes.Uint) {
|
||||
value = new BigNumber(value);
|
||||
}
|
||||
decodedParams[param.name] = value;
|
||||
});
|
||||
|
||||
return {
|
||||
...log,
|
||||
event: event.name,
|
||||
args: decodedParams,
|
||||
};
|
||||
}
|
||||
private addABI(abiArray: Web3.AbiDefinition[]): void {
|
||||
_.map(abiArray, (abi: Web3.AbiDefinition) => {
|
||||
if (abi.type === AbiType.Event) {
|
||||
const signature = `${abi.name}(${_.map(abi.inputs, input => input.type).join(',')})`;
|
||||
const signatureHash = new Web3().sha3(signature);
|
||||
this.methodIds[signatureHash] = abi;
|
||||
}
|
||||
});
|
||||
this.savedABIs = this.savedABIs.concat(abiArray);
|
||||
}
|
||||
private padZeros(address: string) {
|
||||
let formatted = address;
|
||||
if (_.startsWith(formatted, '0x')) {
|
||||
formatted = formatted.slice(2);
|
||||
}
|
||||
|
||||
formatted = _.padStart(formatted, 40, '0');
|
||||
return `0x${formatted}`;
|
||||
}
|
||||
}
|
||||
30
packages/0x.js/src/utils/assert.ts
Normal file
30
packages/0x.js/src/utils/assert.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {SchemaValidator, Schema} from '@0xproject/json-schemas';
|
||||
import {assert as sharedAssert} from '@0xproject/assert';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {signatureUtils} from '../utils/signature_utils';
|
||||
import {ECSignature} from '../types';
|
||||
|
||||
const HEX_REGEX = /^0x[0-9A-F]*$/i;
|
||||
|
||||
export const assert = {
|
||||
...sharedAssert,
|
||||
isValidSignature(orderHash: string, ecSignature: ECSignature, signerAddress: string) {
|
||||
const isValidSignature = signatureUtils.isValidSignature(orderHash, ecSignature, signerAddress);
|
||||
this.assert(isValidSignature, `Expected order with hash '${orderHash}' to have a valid signature`);
|
||||
},
|
||||
async isSenderAddressAsync(variableName: string, senderAddressHex: string,
|
||||
web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
sharedAssert.isETHAddressHex(variableName, senderAddressHex);
|
||||
const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
|
||||
sharedAssert.assert(isSenderAddressAvailable,
|
||||
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
|
||||
);
|
||||
},
|
||||
async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider');
|
||||
},
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
|
||||
export const constants = {
|
||||
NULL_ADDRESS: '0x0000000000000000000000000000000000000000',
|
||||
@@ -7,4 +7,5 @@ export const constants = {
|
||||
INVALID_JUMP_PATTERN: 'invalid JUMP at',
|
||||
OUT_OF_GAS_PATTERN: 'out of gas',
|
||||
UNLIMITED_ALLOWANCE_IN_BASE_UNITS: new BigNumber(2).pow(256).minus(1),
|
||||
DEFAULT_BLOCK_POLLING_INTERVAL: 1000,
|
||||
};
|
||||
88
packages/0x.js/src/utils/exchange_transfer_simulator.ts
Normal file
88
packages/0x.js/src/utils/exchange_transfer_simulator.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import * as _ from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {ExchangeContractErrs, TradeSide, TransferType, BlockParamLiteral} from '../types';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';
|
||||
|
||||
enum FailureReason {
|
||||
Balance = 'balance',
|
||||
ProxyAllowance = 'proxyAllowance',
|
||||
}
|
||||
|
||||
const ERR_MSG_MAPPING = {
|
||||
[FailureReason.Balance]: {
|
||||
[TradeSide.Maker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerBalance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeBalance,
|
||||
},
|
||||
[TradeSide.Taker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerBalance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeBalance,
|
||||
},
|
||||
},
|
||||
[FailureReason.ProxyAllowance]: {
|
||||
[TradeSide.Maker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientMakerAllowance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientMakerFeeAllowance,
|
||||
},
|
||||
[TradeSide.Taker]: {
|
||||
[TransferType.Trade]: ExchangeContractErrs.InsufficientTakerAllowance,
|
||||
[TransferType.Fee]: ExchangeContractErrs.InsufficientTakerFeeAllowance,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export class ExchangeTransferSimulator {
|
||||
private store: BalanceAndProxyAllowanceLazyStore;
|
||||
private UNLIMITED_ALLOWANCE_IN_BASE_UNITS: BigNumber;
|
||||
constructor(token: TokenWrapper) {
|
||||
this.store = new BalanceAndProxyAllowanceLazyStore(token);
|
||||
this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS = token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS;
|
||||
}
|
||||
/**
|
||||
* Simulates transferFrom call performed by a proxy
|
||||
* @param tokenAddress Address of the token to be transferred
|
||||
* @param from Owner of the transferred tokens
|
||||
* @param to Recipient of the transferred tokens
|
||||
* @param amountInBaseUnits The amount of tokens being transferred
|
||||
* @param tradeSide Is Maker/Taker transferring
|
||||
* @param transferType Is it a fee payment or a value transfer
|
||||
*/
|
||||
public async transferFromAsync(tokenAddress: string, from: string, to: string,
|
||||
amountInBaseUnits: BigNumber, tradeSide: TradeSide,
|
||||
transferType: TransferType): Promise<void> {
|
||||
const balance = await this.store.getBalanceAsync(tokenAddress, from);
|
||||
const proxyAllowance = await this.store.getProxyAllowanceAsync(tokenAddress, from);
|
||||
if (proxyAllowance.lessThan(amountInBaseUnits)) {
|
||||
this.throwValidationError(FailureReason.ProxyAllowance, tradeSide, transferType);
|
||||
}
|
||||
if (balance.lessThan(amountInBaseUnits)) {
|
||||
this.throwValidationError(FailureReason.Balance, tradeSide, transferType);
|
||||
}
|
||||
await this.decreaseProxyAllowanceAsync(tokenAddress, from, amountInBaseUnits);
|
||||
await this.decreaseBalanceAsync(tokenAddress, from, amountInBaseUnits);
|
||||
await this.increaseBalanceAsync(tokenAddress, to, amountInBaseUnits);
|
||||
}
|
||||
private async decreaseProxyAllowanceAsync(tokenAddress: string, userAddress: string,
|
||||
amountInBaseUnits: BigNumber): Promise<void> {
|
||||
const proxyAllowance = await this.store.getProxyAllowanceAsync(tokenAddress, userAddress);
|
||||
if (!proxyAllowance.eq(this.UNLIMITED_ALLOWANCE_IN_BASE_UNITS)) {
|
||||
this.store.setProxyAllowance(tokenAddress, userAddress, proxyAllowance.minus(amountInBaseUnits));
|
||||
}
|
||||
}
|
||||
private async increaseBalanceAsync(tokenAddress: string, userAddress: string,
|
||||
amountInBaseUnits: BigNumber): Promise<void> {
|
||||
const balance = await this.store.getBalanceAsync(tokenAddress, userAddress);
|
||||
this.store.setBalance(tokenAddress, userAddress, balance.plus(amountInBaseUnits));
|
||||
}
|
||||
private async decreaseBalanceAsync(tokenAddress: string, userAddress: string,
|
||||
amountInBaseUnits: BigNumber): Promise<void> {
|
||||
const balance = await this.store.getBalanceAsync(tokenAddress, userAddress);
|
||||
this.store.setBalance(tokenAddress, userAddress, balance.minus(amountInBaseUnits));
|
||||
}
|
||||
private throwValidationError(failureReason: FailureReason, tradeSide: TradeSide,
|
||||
transferType: TransferType): Promise<never> {
|
||||
const errMsg = ERR_MSG_MAPPING[failureReason][tradeSide][transferType];
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
}
|
||||
82
packages/0x.js/src/utils/filter_utils.ts
Normal file
82
packages/0x.js/src/utils/filter_utils.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import * as uuid from 'uuid/v4';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import * as jsSHA3 from 'js-sha3';
|
||||
import {ContractEvents, IndexedFilterValues, SubscriptionOpts} from '../types';
|
||||
|
||||
const TOPIC_LENGTH = 32;
|
||||
|
||||
export const filterUtils = {
|
||||
generateUUID(): string {
|
||||
return uuid();
|
||||
},
|
||||
getFilter(address: string, eventName: ContractEvents,
|
||||
indexFilterValues: IndexedFilterValues, abi: Web3.ContractAbi,
|
||||
subscriptionOpts?: SubscriptionOpts): Web3.FilterObject {
|
||||
const eventAbi = _.find(abi, {name: eventName}) as Web3.EventAbi;
|
||||
const eventSignature = filterUtils.getEventSignatureFromAbiByName(eventAbi, eventName);
|
||||
const topicForEventSignature = ethUtil.addHexPrefix(jsSHA3.keccak256(eventSignature));
|
||||
const topicsForIndexedArgs = filterUtils.getTopicsForIndexedArgs(eventAbi, indexFilterValues);
|
||||
const topics = [topicForEventSignature, ...topicsForIndexedArgs];
|
||||
let filter: Web3.FilterObject = {
|
||||
address,
|
||||
topics,
|
||||
};
|
||||
if (!_.isUndefined(subscriptionOpts)) {
|
||||
filter = {
|
||||
...subscriptionOpts,
|
||||
...filter,
|
||||
};
|
||||
}
|
||||
return filter;
|
||||
},
|
||||
getEventSignatureFromAbiByName(eventAbi: Web3.EventAbi, eventName: ContractEvents): string {
|
||||
const types = _.map(eventAbi.inputs, 'type');
|
||||
const signature = `${eventAbi.name}(${types.join(',')})`;
|
||||
return signature;
|
||||
},
|
||||
getTopicsForIndexedArgs(abi: Web3.EventAbi, indexFilterValues: IndexedFilterValues): Array<string|null> {
|
||||
const topics: Array<string|null> = [];
|
||||
for (const eventInput of abi.inputs) {
|
||||
if (!eventInput.indexed) {
|
||||
continue;
|
||||
}
|
||||
if (_.isUndefined(indexFilterValues[eventInput.name])) {
|
||||
// Null is a wildcard topic in a JSON-RPC call
|
||||
topics.push(null);
|
||||
} else {
|
||||
const value = indexFilterValues[eventInput.name] as string;
|
||||
const buffer = ethUtil.toBuffer(value);
|
||||
const paddedBuffer = ethUtil.setLengthLeft(buffer, TOPIC_LENGTH);
|
||||
const topic = ethUtil.bufferToHex(paddedBuffer);
|
||||
topics.push(topic);
|
||||
}
|
||||
}
|
||||
return topics;
|
||||
},
|
||||
matchesFilter(log: Web3.LogEntry, filter: Web3.FilterObject): boolean {
|
||||
if (!_.isUndefined(filter.address) && log.address !== filter.address) {
|
||||
return false;
|
||||
}
|
||||
if (!_.isUndefined(filter.topics)) {
|
||||
return filterUtils.matchesTopics(log.topics, filter.topics);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
matchesTopics(logTopics: string[], filterTopics: Array<string[]|string|null>): boolean {
|
||||
const matchesTopic = _.zipWith(logTopics, filterTopics, filterUtils.matchesTopic.bind(filterUtils));
|
||||
const matchesTopics = _.every(matchesTopic);
|
||||
return matchesTopics;
|
||||
},
|
||||
matchesTopic(logTopic: string, filterTopic: string[]|string|null): boolean {
|
||||
if (_.isArray(filterTopic)) {
|
||||
return _.includes(filterTopic, logTopic);
|
||||
}
|
||||
if (_.isString(filterTopic)) {
|
||||
return filterTopic === logTopic;
|
||||
}
|
||||
// null topic is a wildcard
|
||||
return true;
|
||||
},
|
||||
};
|
||||
20
packages/0x.js/src/utils/interval_utils.ts
Normal file
20
packages/0x.js/src/utils/interval_utils.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import * as _ from 'lodash';
|
||||
|
||||
export const intervalUtils = {
|
||||
setAsyncExcludingInterval(fn: () => Promise<void>, intervalMs: number) {
|
||||
let locked = false;
|
||||
const intervalId = setInterval(async () => {
|
||||
if (locked) {
|
||||
return;
|
||||
} else {
|
||||
locked = true;
|
||||
await fn();
|
||||
locked = false;
|
||||
}
|
||||
}, intervalMs);
|
||||
return intervalId;
|
||||
},
|
||||
clearAsyncExcludingInterval(intervalId: NodeJS.Timer): void {
|
||||
clearInterval(intervalId);
|
||||
},
|
||||
};
|
||||
132
packages/0x.js/src/utils/order_state_utils.ts
Normal file
132
packages/0x.js/src/utils/order_state_utils.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {
|
||||
ExchangeContractErrs,
|
||||
SignedOrder,
|
||||
OrderRelevantState,
|
||||
MethodOpts,
|
||||
OrderState,
|
||||
OrderStateValid,
|
||||
OrderStateInvalid,
|
||||
} from '../types';
|
||||
import {ZeroEx} from '../0x';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
|
||||
import {utils} from '../utils/utils';
|
||||
import {constants} from '../utils/constants';
|
||||
import {OrderFilledCancelledLazyStore} from '../stores/order_filled_cancelled_lazy_store';
|
||||
import {BalanceAndProxyAllowanceLazyStore} from '../stores/balance_proxy_allowance_lazy_store';
|
||||
|
||||
const ACCEPTABLE_RELATIVE_ROUNDING_ERROR = 0.0001;
|
||||
|
||||
export class OrderStateUtils {
|
||||
private balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore;
|
||||
private orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore;
|
||||
constructor(balanceAndProxyAllowanceLazyStore: BalanceAndProxyAllowanceLazyStore,
|
||||
orderFilledCancelledLazyStore: OrderFilledCancelledLazyStore) {
|
||||
this.balanceAndProxyAllowanceLazyStore = balanceAndProxyAllowanceLazyStore;
|
||||
this.orderFilledCancelledLazyStore = orderFilledCancelledLazyStore;
|
||||
}
|
||||
public async getOrderStateAsync(signedOrder: SignedOrder): Promise<OrderState> {
|
||||
const orderRelevantState = await this.getOrderRelevantStateAsync(signedOrder);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
try {
|
||||
this.validateIfOrderIsValid(signedOrder, orderRelevantState);
|
||||
const orderState: OrderStateValid = {
|
||||
isValid: true,
|
||||
orderHash,
|
||||
orderRelevantState,
|
||||
};
|
||||
return orderState;
|
||||
} catch (err) {
|
||||
const orderState: OrderStateInvalid = {
|
||||
isValid: false,
|
||||
orderHash,
|
||||
error: err.message,
|
||||
};
|
||||
return orderState;
|
||||
}
|
||||
}
|
||||
public async getOrderRelevantStateAsync(signedOrder: SignedOrder): Promise<OrderRelevantState> {
|
||||
// HACK: We access the private property here but otherwise the interface will be less nice.
|
||||
// If we pass it from the instantiator - there is no opportunity to get it there
|
||||
// because JS doesn't support async constructors.
|
||||
// Moreover - it's cached under the hood so it's equivalent to an async constructor.
|
||||
const exchange = (this.orderFilledCancelledLazyStore as any).exchange as ExchangeWrapper;
|
||||
const zrxTokenAddress = await exchange.getZRXTokenAddressAsync();
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
const makerBalance = await this.balanceAndProxyAllowanceLazyStore.getBalanceAsync(
|
||||
signedOrder.makerTokenAddress, signedOrder.maker,
|
||||
);
|
||||
const makerProxyAllowance = await this.balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
|
||||
signedOrder.makerTokenAddress, signedOrder.maker,
|
||||
);
|
||||
const makerFeeBalance = await this.balanceAndProxyAllowanceLazyStore.getBalanceAsync(
|
||||
zrxTokenAddress, signedOrder.maker,
|
||||
);
|
||||
const makerFeeProxyAllowance = await this.balanceAndProxyAllowanceLazyStore.getProxyAllowanceAsync(
|
||||
zrxTokenAddress, signedOrder.maker,
|
||||
);
|
||||
const filledTakerTokenAmount = await this.orderFilledCancelledLazyStore.getFilledTakerAmountAsync(orderHash);
|
||||
const cancelledTakerTokenAmount = await this.orderFilledCancelledLazyStore.getCancelledTakerAmountAsync(
|
||||
orderHash,
|
||||
);
|
||||
const unavailableTakerTokenAmount = await exchange.getUnavailableTakerAmountAsync(orderHash);
|
||||
const totalMakerTokenAmount = signedOrder.makerTokenAmount;
|
||||
const totalTakerTokenAmount = signedOrder.takerTokenAmount;
|
||||
const remainingTakerTokenAmount = totalTakerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
const remainingMakerTokenAmount = remainingTakerTokenAmount.times(totalMakerTokenAmount)
|
||||
.dividedToIntegerBy(totalTakerTokenAmount);
|
||||
const fillableMakerTokenAmount = BigNumber.min([makerProxyAllowance, makerBalance]);
|
||||
const remainingFillableMakerTokenAmount = BigNumber.min(fillableMakerTokenAmount, remainingMakerTokenAmount);
|
||||
const remainingFillableTakerTokenAmount = remainingFillableMakerTokenAmount
|
||||
.times(totalTakerTokenAmount)
|
||||
.dividedToIntegerBy(totalMakerTokenAmount);
|
||||
// TODO: Handle edge case where maker token is ZRX with fee
|
||||
const orderRelevantState = {
|
||||
makerBalance,
|
||||
makerProxyAllowance,
|
||||
makerFeeBalance,
|
||||
makerFeeProxyAllowance,
|
||||
filledTakerTokenAmount,
|
||||
cancelledTakerTokenAmount,
|
||||
remainingFillableMakerTokenAmount,
|
||||
remainingFillableTakerTokenAmount,
|
||||
};
|
||||
return orderRelevantState;
|
||||
}
|
||||
private validateIfOrderIsValid(signedOrder: SignedOrder, orderRelevantState: OrderRelevantState): void {
|
||||
const unavailableTakerTokenAmount = orderRelevantState.cancelledTakerTokenAmount.add(
|
||||
orderRelevantState.filledTakerTokenAmount,
|
||||
);
|
||||
const availableTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
if (availableTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
|
||||
if (orderRelevantState.makerBalance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
}
|
||||
if (orderRelevantState.makerProxyAllowance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerAllowance);
|
||||
}
|
||||
if (!signedOrder.makerFee.eq(0)) {
|
||||
if (orderRelevantState.makerFeeBalance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerFeeBalance);
|
||||
}
|
||||
if (orderRelevantState.makerFeeProxyAllowance.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientMakerFeeAllowance);
|
||||
}
|
||||
}
|
||||
const minFillableTakerTokenAmountWithinNoRoundingErrorRange = signedOrder.takerTokenAmount
|
||||
.dividedBy(ACCEPTABLE_RELATIVE_ROUNDING_ERROR)
|
||||
.dividedBy(signedOrder.makerTokenAmount);
|
||||
if (orderRelevantState.remainingFillableTakerTokenAmount
|
||||
.lessThan(minFillableTakerTokenAmountWithinNoRoundingErrorRange)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
|
||||
}
|
||||
// TODO Add linear function solver when maker token is ZRX #badass
|
||||
// Return the max amount that's fillable
|
||||
}
|
||||
}
|
||||
166
packages/0x.js/src/utils/order_validation_utils.ts
Normal file
166
packages/0x.js/src/utils/order_validation_utils.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import * as _ from 'lodash';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {ExchangeContractErrs, SignedOrder, Order, ZeroExError, TradeSide, TransferType} from '../types';
|
||||
import {ZeroEx} from '../0x';
|
||||
import {TokenWrapper} from '../contract_wrappers/token_wrapper';
|
||||
import {ExchangeWrapper} from '../contract_wrappers/exchange_wrapper';
|
||||
import {utils} from '../utils/utils';
|
||||
import {constants} from '../utils/constants';
|
||||
import {ExchangeTransferSimulator} from './exchange_transfer_simulator';
|
||||
|
||||
export class OrderValidationUtils {
|
||||
private tokenWrapper: TokenWrapper;
|
||||
private exchangeWrapper: ExchangeWrapper;
|
||||
constructor(tokenWrapper: TokenWrapper, exchangeWrapper: ExchangeWrapper) {
|
||||
this.tokenWrapper = tokenWrapper;
|
||||
this.exchangeWrapper = exchangeWrapper;
|
||||
}
|
||||
public async validateOrderFillableOrThrowAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder, zrxTokenAddress: string,
|
||||
expectedFillTakerTokenAmount?: BigNumber): Promise<void> {
|
||||
const orderHash = utils.getOrderHashHex(signedOrder);
|
||||
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
|
||||
this.validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
|
||||
);
|
||||
this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
|
||||
let fillTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
if (!_.isUndefined(expectedFillTakerTokenAmount)) {
|
||||
fillTakerTokenAmount = expectedFillTakerTokenAmount;
|
||||
}
|
||||
const fillMakerTokenAmount = this.getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerTokenAmount,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.makerTokenAddress, signedOrder.maker, signedOrder.taker, fillMakerTokenAmount,
|
||||
TradeSide.Maker, TransferType.Trade,
|
||||
);
|
||||
const makerFeeAmount = this.getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress, signedOrder.maker, signedOrder.feeRecipient, makerFeeAmount,
|
||||
TradeSide.Maker, TransferType.Fee,
|
||||
);
|
||||
}
|
||||
public async validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber, takerAddress: string,
|
||||
zrxTokenAddress: string): Promise<BigNumber> {
|
||||
if (fillTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillAmountZero);
|
||||
}
|
||||
const orderHash = utils.getOrderHashHex(signedOrder);
|
||||
if (!ZeroEx.isValidSignature(orderHash, signedOrder.ecSignature, signedOrder.maker)) {
|
||||
throw new Error(ZeroExError.InvalidSignature);
|
||||
}
|
||||
const unavailableTakerTokenAmount = await this.exchangeWrapper.getUnavailableTakerAmountAsync(orderHash);
|
||||
this.validateRemainingFillAmountNotZeroOrThrow(
|
||||
signedOrder.takerTokenAmount, unavailableTakerTokenAmount,
|
||||
);
|
||||
if (signedOrder.taker !== constants.NULL_ADDRESS && signedOrder.taker !== takerAddress) {
|
||||
throw new Error(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
}
|
||||
this.validateOrderNotExpiredOrThrow(signedOrder.expirationUnixTimestampSec);
|
||||
const remainingTakerTokenAmount = signedOrder.takerTokenAmount.minus(unavailableTakerTokenAmount);
|
||||
const filledTakerTokenAmount = remainingTakerTokenAmount.lessThan(fillTakerTokenAmount) ?
|
||||
remainingTakerTokenAmount :
|
||||
fillTakerTokenAmount;
|
||||
await this.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, signedOrder, filledTakerTokenAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
|
||||
const wouldRoundingErrorOccur = await this.exchangeWrapper.isRoundingErrorAsync(
|
||||
filledTakerTokenAmount, signedOrder.takerTokenAmount, signedOrder.makerTokenAmount,
|
||||
);
|
||||
if (wouldRoundingErrorOccur) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillRoundingError);
|
||||
}
|
||||
return filledTakerTokenAmount;
|
||||
}
|
||||
public async validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber, takerAddress: string, zrxTokenAddress: string): Promise<void> {
|
||||
const filledTakerTokenAmount = await this.validateFillOrderThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
if (filledTakerTokenAmount !== fillTakerTokenAmount) {
|
||||
throw new Error(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
}
|
||||
}
|
||||
public async validateCancelOrderThrowIfInvalidAsync(order: Order,
|
||||
cancelTakerTokenAmount: BigNumber,
|
||||
unavailableTakerTokenAmount: BigNumber,
|
||||
): Promise<void> {
|
||||
if (cancelTakerTokenAmount.eq(0)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
}
|
||||
if (order.takerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
|
||||
}
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (order.expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderCancelExpired);
|
||||
}
|
||||
}
|
||||
public async validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTradeEmulator: ExchangeTransferSimulator, signedOrder: SignedOrder,
|
||||
fillTakerTokenAmount: BigNumber, senderAddress: string, zrxTokenAddress: string): Promise<void> {
|
||||
const fillMakerTokenAmount = this.getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerTokenAmount,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.makerTokenAddress, signedOrder.maker, senderAddress, fillMakerTokenAmount,
|
||||
TradeSide.Maker, TransferType.Trade,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
signedOrder.takerTokenAddress, senderAddress, signedOrder.maker, fillTakerTokenAmount,
|
||||
TradeSide.Taker, TransferType.Trade,
|
||||
);
|
||||
const makerFeeAmount = this.getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.makerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress, signedOrder.maker, signedOrder.feeRecipient, makerFeeAmount, TradeSide.Maker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
const takerFeeAmount = this.getPartialAmount(
|
||||
fillTakerTokenAmount,
|
||||
signedOrder.takerTokenAmount,
|
||||
signedOrder.takerFee,
|
||||
);
|
||||
await exchangeTradeEmulator.transferFromAsync(
|
||||
zrxTokenAddress, senderAddress, signedOrder.feeRecipient, takerFeeAmount, TradeSide.Taker,
|
||||
TransferType.Fee,
|
||||
);
|
||||
}
|
||||
private validateRemainingFillAmountNotZeroOrThrow(
|
||||
takerTokenAmount: BigNumber, unavailableTakerTokenAmount: BigNumber,
|
||||
) {
|
||||
if (takerTokenAmount.eq(unavailableTakerTokenAmount)) {
|
||||
throw new Error(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
}
|
||||
}
|
||||
private validateOrderNotExpiredOrThrow(expirationUnixTimestampSec: BigNumber) {
|
||||
const currentUnixTimestampSec = utils.getCurrentUnixTimestamp();
|
||||
if (expirationUnixTimestampSec.lessThan(currentUnixTimestampSec)) {
|
||||
throw new Error(ExchangeContractErrs.OrderFillExpired);
|
||||
}
|
||||
}
|
||||
private getPartialAmount(numerator: BigNumber, denominator: BigNumber,
|
||||
target: BigNumber): BigNumber {
|
||||
const fillMakerTokenAmount = numerator
|
||||
.mul(target)
|
||||
.div(denominator)
|
||||
.round(0);
|
||||
return fillMakerTokenAmount;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,21 @@ import * as ethUtil from 'ethereumjs-util';
|
||||
import {ECSignature} from '../types';
|
||||
|
||||
export const signatureUtils = {
|
||||
isValidSignature(data: string, signature: ECSignature, signerAddress: string): boolean {
|
||||
const dataBuff = ethUtil.toBuffer(data);
|
||||
const msgHashBuff = ethUtil.hashPersonalMessage(dataBuff);
|
||||
try {
|
||||
const pubKey = ethUtil.ecrecover(
|
||||
msgHashBuff,
|
||||
signature.v,
|
||||
ethUtil.toBuffer(signature.r),
|
||||
ethUtil.toBuffer(signature.s));
|
||||
const retrievedAddress = ethUtil.bufferToHex(ethUtil.pubToAddress(pubKey));
|
||||
return retrievedAddress === signerAddress;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
parseSignatureHexAsVRS(signatureHex: string): ECSignature {
|
||||
const signatureBuffer = ethUtil.toBuffer(signatureHex);
|
||||
let v = signatureBuffer[0];
|
||||
@@ -2,7 +2,7 @@ import * as _ from 'lodash';
|
||||
import * as ethABI from 'ethereumjs-abi';
|
||||
import * as ethUtil from 'ethereumjs-util';
|
||||
import {Order, SignedOrder, SolidityTypes} from '../types';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import BN = require('bn.js');
|
||||
|
||||
export const utils = {
|
||||
@@ -12,7 +12,7 @@ export const utils = {
|
||||
* expects values of Solidity type `uint` to be passed as type `BN`.
|
||||
* We do not use BN anywhere else in the codebase.
|
||||
*/
|
||||
bigNumberToBN(value: BigNumber.BigNumber) {
|
||||
bigNumberToBN(value: BigNumber) {
|
||||
return new BN(value.toString(), 10);
|
||||
},
|
||||
consoleLog(message: string): void {
|
||||
@@ -49,7 +49,7 @@ export const utils = {
|
||||
const hashHex = ethUtil.bufferToHex(hashBuff);
|
||||
return hashHex;
|
||||
},
|
||||
getCurrentUnixTimestamp(): BigNumber.BigNumber {
|
||||
getCurrentUnixTimestamp(): BigNumber {
|
||||
return new BigNumber(Date.now() / 1000);
|
||||
},
|
||||
};
|
||||
200
packages/0x.js/src/web3_wrapper.ts
Normal file
200
packages/0x.js/src/web3_wrapper.ts
Normal file
@@ -0,0 +1,200 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {ZeroExError, Artifact, TransactionReceipt} from './types';
|
||||
import {Contract} from './contract';
|
||||
|
||||
interface RawLogEntry {
|
||||
logIndex: string|null;
|
||||
transactionIndex: string|null;
|
||||
transactionHash: string;
|
||||
blockHash: string|null;
|
||||
blockNumber: string|null;
|
||||
address: string;
|
||||
data: string;
|
||||
topics: string[];
|
||||
}
|
||||
|
||||
export class Web3Wrapper {
|
||||
private web3: Web3;
|
||||
private defaults: Partial<Web3.TxData>;
|
||||
private networkIdIfExists?: number;
|
||||
private jsonRpcRequestId: number;
|
||||
constructor(provider: Web3.Provider, defaults?: Partial<Web3.TxData>) {
|
||||
if (_.isUndefined((provider as any).sendAsync)) {
|
||||
// Web3@1.0 provider doesn't support synchronous http requests,
|
||||
// so it only has an async `send` method, instead of a `send` and `sendAsync` in web3@0.x.x`
|
||||
// We re-assign the send method so that Web3@1.0 providers work with 0x.js
|
||||
(provider as any).sendAsync = (provider as any).send;
|
||||
}
|
||||
this.web3 = new Web3();
|
||||
this.web3.setProvider(provider);
|
||||
this.defaults = defaults || {};
|
||||
this.jsonRpcRequestId = 0;
|
||||
}
|
||||
public setProvider(provider: Web3.Provider) {
|
||||
delete this.networkIdIfExists;
|
||||
this.web3.setProvider(provider);
|
||||
}
|
||||
public isAddress(address: string): boolean {
|
||||
return this.web3.isAddress(address);
|
||||
}
|
||||
public async isSenderAddressAvailableAsync(senderAddress: string): Promise<boolean> {
|
||||
const addresses = await this.getAvailableAddressesAsync();
|
||||
return _.includes(addresses, senderAddress);
|
||||
}
|
||||
public async getNodeVersionAsync(): Promise<string> {
|
||||
const nodeVersion = await promisify(this.web3.version.getNode)();
|
||||
return nodeVersion;
|
||||
}
|
||||
public async getTransactionReceiptAsync(txHash: string): Promise<TransactionReceipt> {
|
||||
const transactionReceipt = await promisify(this.web3.eth.getTransactionReceipt)(txHash);
|
||||
transactionReceipt.status = this.normalizeTxReceiptStatus(transactionReceipt.status);
|
||||
return transactionReceipt;
|
||||
}
|
||||
public getCurrentProvider(): Web3.Provider {
|
||||
return this.web3.currentProvider;
|
||||
}
|
||||
public async getNetworkIdIfExistsAsync(): Promise<number|undefined> {
|
||||
if (!_.isUndefined(this.networkIdIfExists)) {
|
||||
return this.networkIdIfExists;
|
||||
}
|
||||
|
||||
try {
|
||||
const networkId = await this.getNetworkAsync();
|
||||
this.networkIdIfExists = Number(networkId);
|
||||
return this.networkIdIfExists;
|
||||
} catch (err) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
public async getContractInstanceFromArtifactAsync<A extends Web3.ContractInstance>(artifact: Artifact,
|
||||
address?: string): Promise<A> {
|
||||
let contractAddress: string;
|
||||
if (_.isUndefined(address)) {
|
||||
const networkIdIfExists = await this.getNetworkIdIfExistsAsync();
|
||||
if (_.isUndefined(networkIdIfExists)) {
|
||||
throw new Error(ZeroExError.NoNetworkId);
|
||||
}
|
||||
if (_.isUndefined(artifact.networks[networkIdIfExists])) {
|
||||
throw new Error(ZeroExError.ContractNotDeployedOnNetwork);
|
||||
}
|
||||
contractAddress = artifact.networks[networkIdIfExists].address.toLowerCase();
|
||||
} else {
|
||||
contractAddress = address;
|
||||
}
|
||||
const doesContractExist = await this.doesContractExistAtAddressAsync(contractAddress);
|
||||
if (!doesContractExist) {
|
||||
throw new Error(ZeroExError.ContractDoesNotExist);
|
||||
}
|
||||
const contractInstance = this.getContractInstance<A>(
|
||||
artifact.abi, contractAddress,
|
||||
);
|
||||
return contractInstance;
|
||||
}
|
||||
public toWei(ethAmount: BigNumber): BigNumber {
|
||||
const balanceWei = this.web3.toWei(ethAmount, 'ether');
|
||||
return balanceWei;
|
||||
}
|
||||
public async getBalanceInWeiAsync(owner: string): Promise<BigNumber> {
|
||||
let balanceInWei = await promisify(this.web3.eth.getBalance)(owner);
|
||||
balanceInWei = new BigNumber(balanceInWei);
|
||||
return balanceInWei;
|
||||
}
|
||||
public async doesContractExistAtAddressAsync(address: string): Promise<boolean> {
|
||||
const code = await promisify(this.web3.eth.getCode)(address);
|
||||
// Regex matches 0x0, 0x00, 0x in order to accommodate poorly implemented clients
|
||||
const codeIsEmpty = /^0x0{0,40}$/i.test(code);
|
||||
return !codeIsEmpty;
|
||||
}
|
||||
public async signTransactionAsync(address: string, message: string): Promise<string> {
|
||||
const signData = await promisify(this.web3.eth.sign)(address, message);
|
||||
return signData;
|
||||
}
|
||||
public async getBlockNumberAsync(): Promise<number> {
|
||||
const blockNumber = await promisify(this.web3.eth.getBlockNumber)();
|
||||
return blockNumber;
|
||||
}
|
||||
public async getBlockAsync(blockParam: string|Web3.BlockParam): Promise<Web3.BlockWithoutTransactionData> {
|
||||
const block = await promisify(this.web3.eth.getBlock)(blockParam);
|
||||
return block;
|
||||
}
|
||||
public async getBlockTimestampAsync(blockParam: string|Web3.BlockParam): Promise<number> {
|
||||
const {timestamp} = await this.getBlockAsync(blockParam);
|
||||
return timestamp;
|
||||
}
|
||||
public async getAvailableAddressesAsync(): Promise<string[]> {
|
||||
const addresses: string[] = await promisify(this.web3.eth.getAccounts)();
|
||||
return addresses;
|
||||
}
|
||||
public async getLogsAsync(filter: Web3.FilterObject): Promise<Web3.LogEntry[]> {
|
||||
let fromBlock = filter.fromBlock;
|
||||
if (_.isNumber(fromBlock)) {
|
||||
fromBlock = this.web3.toHex(fromBlock);
|
||||
}
|
||||
let toBlock = filter.toBlock;
|
||||
if (_.isNumber(toBlock)) {
|
||||
toBlock = this.web3.toHex(toBlock);
|
||||
}
|
||||
const serializedFilter = {
|
||||
...filter,
|
||||
fromBlock,
|
||||
toBlock,
|
||||
};
|
||||
const payload = {
|
||||
jsonrpc: '2.0',
|
||||
id: this.jsonRpcRequestId++,
|
||||
method: 'eth_getLogs',
|
||||
params: [serializedFilter],
|
||||
};
|
||||
const rawLogs = await this.sendRawPayloadAsync<RawLogEntry[]>(payload);
|
||||
const formattedLogs = _.map(rawLogs, this.formatLog.bind(this));
|
||||
return formattedLogs;
|
||||
}
|
||||
private getContractInstance<A extends Web3.ContractInstance>(abi: Web3.ContractAbi, address: string): A {
|
||||
const web3ContractInstance = this.web3.eth.contract(abi).at(address);
|
||||
const contractInstance = new Contract(web3ContractInstance, this.defaults) as any as A;
|
||||
return contractInstance;
|
||||
}
|
||||
private async getNetworkAsync(): Promise<number> {
|
||||
const networkId = await promisify(this.web3.version.getNetwork)();
|
||||
return networkId;
|
||||
}
|
||||
private async sendRawPayloadAsync<A>(payload: Web3.JSONRPCRequestPayload): Promise<A> {
|
||||
const sendAsync = this.web3.currentProvider.sendAsync.bind(this.web3.currentProvider);
|
||||
const response = await promisify(sendAsync)(payload);
|
||||
const result = response.result;
|
||||
return result;
|
||||
}
|
||||
private normalizeTxReceiptStatus(status: undefined|null|string|0|1): null|0|1 {
|
||||
// Transaction status might have four values
|
||||
// undefined - Testrpc and other old clients
|
||||
// null - New clients on old transactions
|
||||
// number - Parity
|
||||
// hex - Geth
|
||||
if (_.isString(status)) {
|
||||
return this.web3.toDecimal(status) as 0|1;
|
||||
} else if (_.isUndefined(status)) {
|
||||
return null;
|
||||
} else {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
private formatLog(rawLog: RawLogEntry): Web3.LogEntry {
|
||||
const formattedLog = {
|
||||
...rawLog,
|
||||
logIndex: this.hexToDecimal(rawLog.logIndex),
|
||||
blockNumber: this.hexToDecimal(rawLog.blockNumber),
|
||||
transactionIndex: this.hexToDecimal(rawLog.transactionIndex),
|
||||
};
|
||||
return formattedLog;
|
||||
}
|
||||
private hexToDecimal(hex: string|null): number|null {
|
||||
if (_.isNull(hex)) {
|
||||
return null;
|
||||
}
|
||||
const decimal = this.web3.toDecimal(hex);
|
||||
return decimal;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@ import * as _ from 'lodash';
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import 'mocha';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as Sinon from 'sinon';
|
||||
import {ZeroEx, Order} from '../src';
|
||||
import {ZeroEx, Order, ZeroExError, LogWithDecodedArgs, ApprovalContractEventArgs, TokenEvents} from '../src';
|
||||
import {constants} from './utils/constants';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
@@ -204,4 +207,53 @@ describe('ZeroEx library', () => {
|
||||
expect(ecSignature).to.deep.equal(expectedECSignature);
|
||||
});
|
||||
});
|
||||
describe('#awaitTransactionMinedAsync', () => {
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
it('returns transaction receipt with decoded logs', async () => {
|
||||
const availableAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
const coinbase = availableAddresses[0];
|
||||
const tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
const tokenUtils = new TokenUtils(tokens);
|
||||
const zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
const proxyAddress = await zeroEx.proxy.getContractAddressAsync();
|
||||
const txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(zrxTokenAddress, coinbase);
|
||||
const txReceiptWithDecodedLogs = await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const log = txReceiptWithDecodedLogs.logs[0] as LogWithDecodedArgs<ApprovalContractEventArgs>;
|
||||
expect(log.event).to.be.equal(TokenEvents.Approval);
|
||||
expect(log.args._owner).to.be.equal(coinbase);
|
||||
expect(log.args._spender).to.be.equal(proxyAddress);
|
||||
expect(log.args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
describe('#config', () => {
|
||||
it('allows to specify exchange contract address', async () => {
|
||||
const config = {
|
||||
exchangeContractAddress: ZeroEx.NULL_ADDRESS,
|
||||
};
|
||||
const zeroExWithWrongExchangeAddress = new ZeroEx(web3.currentProvider, config);
|
||||
return expect(zeroExWithWrongExchangeAddress.exchange.getContractAddressAsync())
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
it('allows to specify ether token contract address', async () => {
|
||||
const config = {
|
||||
etherTokenContractAddress: ZeroEx.NULL_ADDRESS,
|
||||
};
|
||||
const zeroExWithWrongEtherTokenAddress = new ZeroEx(web3.currentProvider, config);
|
||||
return expect(zeroExWithWrongEtherTokenAddress.etherToken.getContractAddressAsync())
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
it('allows to specify token registry token contract address', async () => {
|
||||
const config = {
|
||||
tokenRegistryContractAddress: ZeroEx.NULL_ADDRESS,
|
||||
};
|
||||
const zeroExWithWrongTokenRegistryAddress = new ZeroEx(web3.currentProvider, config);
|
||||
return expect(zeroExWithWrongTokenRegistryAddress.tokenRegistry.getContractAddressAsync())
|
||||
.to.be.rejectedWith(ZeroExError.ContractDoesNotExist);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -23,7 +23,24 @@ describe('Artifacts', () => {
|
||||
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('proxy contract is deployed', async () => {
|
||||
await (zeroEx.token as any)._getProxyAddressAsync();
|
||||
await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('exchange contract is deployed', async () => {
|
||||
await zeroEx.exchange.getContractAddressAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
});
|
||||
describe('contracts are deployed on ropsten', () => {
|
||||
const ropstenRpcUrl = constants.ROPSTEN_RPC_URL;
|
||||
const packageJSONContent = fs.readFileSync('package.json', 'utf-8');
|
||||
const packageJSON = JSON.parse(packageJSONContent);
|
||||
const mnemonic = packageJSON.config.mnemonic;
|
||||
const web3Provider = new HDWalletProvider(mnemonic, ropstenRpcUrl);
|
||||
const zeroEx = new ZeroEx(web3Provider);
|
||||
it('token registry contract is deployed', async () => {
|
||||
await (zeroEx.tokenRegistry as any)._getTokenRegistryContractAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('proxy contract is deployed', async () => {
|
||||
await (zeroEx.token as any)._getTokenTransferProxyAddressAsync();
|
||||
}).timeout(TIMEOUT);
|
||||
it('exchange contract is deployed', async () => {
|
||||
await zeroEx.exchange.getContractAddressAsync();
|
||||
@@ -2,8 +2,7 @@ import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx, ZeroExError} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
@@ -24,12 +23,16 @@ describe('EtherTokenWrapper', () => {
|
||||
let userAddresses: string[];
|
||||
let addressWithETH: string;
|
||||
let wethContractAddress: string;
|
||||
let depositWeiAmount: BigNumber.BigNumber;
|
||||
let depositWeiAmount: BigNumber;
|
||||
let decimalPlaces: number;
|
||||
const gasPrice = new BigNumber(1);
|
||||
const zeroExConfig = {
|
||||
gasPrice,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
userAddresses = await promisify(web3.eth.getAccounts)();
|
||||
zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
addressWithETH = userAddresses[0];
|
||||
wethContractAddress = await zeroEx.etherToken.getContractAddressAsync();
|
||||
depositWeiAmount = (zeroEx as any)._web3Wrapper.toWei(new BigNumber(5));
|
||||
@@ -48,7 +51,8 @@ describe('EtherTokenWrapper', () => {
|
||||
expect(preETHBalance).to.be.bignumber.gt(0);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(0);
|
||||
|
||||
await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
|
||||
const txHash = await zeroEx.etherToken.depositAsync(depositWeiAmount, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
const postETHBalanceInWei = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
@@ -82,7 +86,8 @@ describe('EtherTokenWrapper', () => {
|
||||
expect(gasCost).to.be.bignumber.lte(MAX_REASONABLE_GAS_COST_IN_WEI);
|
||||
expect(preWETHBalance).to.be.bignumber.equal(depositWeiAmount);
|
||||
|
||||
await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH);
|
||||
const txHash = await zeroEx.etherToken.withdrawAsync(depositWeiAmount, addressWithETH);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
const postETHBalance = await (zeroEx as any)._web3Wrapper.getBalanceInWeiAsync(addressWithETH);
|
||||
const postWETHBalanceInBaseUnits = await zeroEx.token.getBalanceAsync(wethContractAddress, addressWithETH);
|
||||
127
packages/0x.js/test/event_watcher_test.ts
Normal file
127
packages/0x.js/test/event_watcher_test.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import * as Sinon from 'sinon';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {Web3Wrapper} from '../src/web3_wrapper';
|
||||
import {EventWatcher} from '../src/order_watcher/event_watcher';
|
||||
import {
|
||||
ZeroEx,
|
||||
LogEvent,
|
||||
DecodedLogEvent,
|
||||
} from '../src';
|
||||
import {DoneCallback} from '../src/types';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('EventWatcher', () => {
|
||||
let web3: Web3;
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
let eventWatcher: EventWatcher;
|
||||
let web3Wrapper: Web3Wrapper;
|
||||
const numConfirmations = 0;
|
||||
const logA: Web3.LogEntry = {
|
||||
address: '0x71d271f8b14adef568f8f28f1587ce7271ac4ca5',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: [],
|
||||
transactionHash: '0x004881d38cd4a8f72f1a0d68c8b9b8124504706041ff37019c1d1ed6bfda8e17',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
const logB: Web3.LogEntry = {
|
||||
address: '0x8d12a197cb00d4747a1fe03395095ce2a5cc6819',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: [ '0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567' ],
|
||||
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
const logC: Web3.LogEntry = {
|
||||
address: '0x1d271f8b174adef58f1587ce68f8f27271ac4ca5',
|
||||
blockHash: null,
|
||||
blockNumber: null,
|
||||
data: '',
|
||||
logIndex: null,
|
||||
topics: [ '0xf341246adaac6f497bc2a656f546ab9e182111d630394f0c57c710a59a2cb567' ],
|
||||
transactionHash: '0x01ef3c048b18d9b09ea195b4ed94cf8dd5f3d857a1905ff886b152cfb1166f25',
|
||||
transactionIndex: 0,
|
||||
};
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
const pollingIntervalMs = 10;
|
||||
web3Wrapper = new Web3Wrapper(web3.currentProvider);
|
||||
eventWatcher = new EventWatcher(web3Wrapper, pollingIntervalMs);
|
||||
});
|
||||
afterEach(() => {
|
||||
// clean up any stubs after the test has completed
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
eventWatcher.unsubscribe();
|
||||
});
|
||||
it('correctly emits initial log events', (done: DoneCallback) => {
|
||||
const logs: Web3.LogEntry[] = [logA, logB];
|
||||
const expectedLogEvents = [
|
||||
{
|
||||
removed: false,
|
||||
...logA,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logB,
|
||||
},
|
||||
];
|
||||
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
|
||||
getLogsStub.onCall(0).returns(logs);
|
||||
stubs.push(getLogsStub);
|
||||
const callback = (event: LogEvent) => {
|
||||
const expectedLogEvent = expectedLogEvents.shift();
|
||||
expect(event).to.be.deep.equal(expectedLogEvent);
|
||||
if (_.isEmpty(expectedLogEvents)) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
eventWatcher.subscribe(callback);
|
||||
});
|
||||
it('correctly computes the difference and emits only changes', (done: DoneCallback) => {
|
||||
const initialLogs: Web3.LogEntry[] = [logA, logB];
|
||||
const changedLogs: Web3.LogEntry[] = [logA, logC];
|
||||
const expectedLogEvents = [
|
||||
{
|
||||
removed: false,
|
||||
...logA,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logB,
|
||||
},
|
||||
{
|
||||
removed: true,
|
||||
...logB,
|
||||
},
|
||||
{
|
||||
removed: false,
|
||||
...logC,
|
||||
},
|
||||
];
|
||||
const getLogsStub = Sinon.stub(web3Wrapper, 'getLogsAsync');
|
||||
getLogsStub.onCall(0).returns(initialLogs);
|
||||
getLogsStub.onCall(1).returns(changedLogs);
|
||||
stubs.push(getLogsStub);
|
||||
const callback = (event: LogEvent) => {
|
||||
const expectedLogEvent = expectedLogEvents.shift();
|
||||
expect(event).to.be.deep.equal(expectedLogEvent);
|
||||
if (_.isEmpty(expectedLogEvents)) {
|
||||
done();
|
||||
}
|
||||
};
|
||||
eventWatcher.subscribe(callback);
|
||||
});
|
||||
});
|
||||
87
packages/0x.js/test/exchange_transfer_simulator_test.ts
Normal file
87
packages/0x.js/test/exchange_transfer_simulator_test.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import * as chai from 'chai';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx, ExchangeContractErrs, Token} from '../src';
|
||||
import {TradeSide, TransferType} from '../src/types';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {ExchangeTransferSimulator} from '../src/utils/exchange_transfer_simulator';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('ExchangeTransferSimulator', () => {
|
||||
const web3 = web3Factory.create();
|
||||
const zeroEx = new ZeroEx(web3.currentProvider);
|
||||
const transferAmount = new BigNumber(5);
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let coinbase: string;
|
||||
let sender: string;
|
||||
let recipient: string;
|
||||
let exampleTokenAddress: string;
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let txHash: string;
|
||||
before(async () => {
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[coinbase, sender, recipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
exampleTokenAddress = tokens[0].address;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#transferFromAsync', () => {
|
||||
beforeEach(() => {
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token);
|
||||
});
|
||||
it('throws if the user doesn\'t have enough allowance', async () => {
|
||||
return expect(exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientTakerAllowance);
|
||||
});
|
||||
it('throws if the user doesn\'t have enough balance', async () => {
|
||||
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
return expect(exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Maker, TransferType.Trade,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
});
|
||||
it('updates balances and proxyAllowance after transfer', async () => {
|
||||
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setProxyAllowanceAsync(exampleTokenAddress, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any).store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(0);
|
||||
});
|
||||
it('doesn\'t update proxyAllowance after transfer if unlimited', async () => {
|
||||
txHash = await zeroEx.token.transferAsync(exampleTokenAddress, coinbase, sender, transferAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
await exchangeTransferSimulator.transferFromAsync(
|
||||
exampleTokenAddress, sender, recipient, transferAmount, TradeSide.Taker, TransferType.Trade,
|
||||
);
|
||||
const store = (exchangeTransferSimulator as any).store;
|
||||
const senderBalance = await store.getBalanceAsync(exampleTokenAddress, sender);
|
||||
const recipientBalance = await store.getBalanceAsync(exampleTokenAddress, recipient);
|
||||
const senderProxyAllowance = await store.getProxyAllowanceAsync(exampleTokenAddress, sender);
|
||||
expect(senderBalance).to.be.bignumber.equal(0);
|
||||
expect(recipientBalance).to.be.bignumber.equal(transferAmount);
|
||||
expect(senderProxyAllowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,10 +1,8 @@
|
||||
import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import ChaiBigNumber = require('chai-bignumber');
|
||||
import promisify = require('es6-promisify');
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {
|
||||
@@ -13,17 +11,17 @@ import {
|
||||
SignedOrder,
|
||||
SubscriptionOpts,
|
||||
ExchangeEvents,
|
||||
ContractEvent,
|
||||
ExchangeContractErrs,
|
||||
OrderCancellationRequest,
|
||||
OrderFillRequest,
|
||||
LogFillContractEventArgs,
|
||||
LogCancelContractEventArgs,
|
||||
LogEvent,
|
||||
DecodedLogEvent,
|
||||
} from '../src';
|
||||
import {DoneCallback} from '../src/types';
|
||||
import {DoneCallback, BlockParamLiteral} from '../src/types';
|
||||
import {FillScenarios} from './utils/fill_scenarios';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {assert} from '../src/utils/assert';
|
||||
import {TokenTransferProxyWrapper} from '../src/contract_wrappers/token_transfer_proxy_wrapper';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -44,7 +42,7 @@ describe('ExchangeWrapper', () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync();
|
||||
userAddresses = await promisify(web3.eth.getAccounts)();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
@@ -63,7 +61,7 @@ describe('ExchangeWrapper', () => {
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
const fillTakerAmount = new BigNumber(5);
|
||||
const takerTokenFillAmount = new BigNumber(5);
|
||||
before(async () => {
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
@@ -72,7 +70,7 @@ describe('ExchangeWrapper', () => {
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
describe('#batchFillOrKillAsync', () => {
|
||||
it('successfuly batch fillOrKill', async () => {
|
||||
it('successfully batch fillOrKill', async () => {
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const partialFillTakerAmount = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
@@ -81,26 +79,59 @@ describe('ExchangeWrapper', () => {
|
||||
const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const orderFillOrKillRequests = [
|
||||
const orderFillRequests = [
|
||||
{
|
||||
signedOrder,
|
||||
fillTakerAmount: partialFillTakerAmount,
|
||||
takerTokenFillAmount: partialFillTakerAmount,
|
||||
},
|
||||
{
|
||||
signedOrder: anotherSignedOrder,
|
||||
fillTakerAmount: partialFillTakerAmount,
|
||||
takerTokenFillAmount: partialFillTakerAmount,
|
||||
},
|
||||
];
|
||||
await zeroEx.exchange.batchFillOrKillAsync(orderFillOrKillRequests, takerAddress);
|
||||
await zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress);
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
let orderFillRequests: OrderFillRequest[];
|
||||
const fillableAmount = new BigNumber(5);
|
||||
beforeEach(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
orderFillRequests = [
|
||||
{
|
||||
signedOrder,
|
||||
takerTokenFillAmount: new BigNumber(0),
|
||||
},
|
||||
];
|
||||
});
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrKillAsync(orderFillRequests, takerAddress, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#fillOrKillOrderAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
beforeEach(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
});
|
||||
describe('successful fills', () => {
|
||||
it('should fill a valid order', async () => {
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount);
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||
@@ -109,21 +140,17 @@ describe('ExchangeWrapper', () => {
|
||||
.to.be.bignumber.equal(0);
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount);
|
||||
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, fillTakerAmount, takerAddress);
|
||||
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, takerTokenFillAmount, takerAddress);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillTakerAmount);
|
||||
.to.be.bignumber.equal(takerTokenFillAmount);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillTakerAmount);
|
||||
.to.be.bignumber.equal(takerTokenFillAmount);
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
|
||||
});
|
||||
it('should partially fill a valid order', async () => {
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const partialFillAmount = new BigNumber(3);
|
||||
await zeroEx.exchange.fillOrKillOrderAsync(signedOrder, partialFillAmount, takerAddress);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
@@ -136,6 +163,23 @@ describe('ExchangeWrapper', () => {
|
||||
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
|
||||
});
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
const emptyFillableAmount = new BigNumber(0);
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.fillOrKillOrderAsync(signedOrder, emptyFillableAmount, takerAddress, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('fill order(s)', () => {
|
||||
@@ -146,7 +190,7 @@ describe('ExchangeWrapper', () => {
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const fillTakerAmount = new BigNumber(5);
|
||||
const takerTokenFillAmount = new BigNumber(5);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
before(async () => {
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
@@ -169,24 +213,26 @@ describe('ExchangeWrapper', () => {
|
||||
.to.be.bignumber.equal(0);
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
const txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, takerTokenFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillTakerAmount);
|
||||
.to.be.bignumber.equal(takerTokenFillAmount);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillTakerAmount);
|
||||
.to.be.bignumber.equal(takerTokenFillAmount);
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(fillTakerAmount));
|
||||
.to.be.bignumber.equal(fillableAmount.minus(takerTokenFillAmount));
|
||||
});
|
||||
it('should partially fill the valid order', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const partialFillAmount = new BigNumber(3);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
const txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
expect(await zeroEx.token.getBalanceAsync(makerTokenAddress, makerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, makerAddress))
|
||||
@@ -196,34 +242,6 @@ describe('ExchangeWrapper', () => {
|
||||
expect(await zeroEx.token.getBalanceAsync(takerTokenAddress, takerAddress))
|
||||
.to.be.bignumber.equal(fillableAmount.minus(partialFillAmount));
|
||||
});
|
||||
it('should return filled amount', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const partialFillAmount = new BigNumber(3);
|
||||
const filledAmount = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
expect(filledAmount).to.be.bignumber.equal(partialFillAmount);
|
||||
});
|
||||
it('should return the partially filled amount \
|
||||
if the fill amount specified is greater then the amount available', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const partialFillAmount = new BigNumber(3);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
const missingBalance = new BigNumber(1);
|
||||
const totalBalance = partialFillAmount.plus(missingBalance);
|
||||
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, missingBalance);
|
||||
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, totalBalance);
|
||||
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, missingBalance);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, totalBalance);
|
||||
const remainingFillAmount = fillableAmount.minus(partialFillAmount);
|
||||
const filledAmount = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
expect(filledAmount).to.be.bignumber.equal(remainingFillAmount);
|
||||
});
|
||||
it('should fill the valid orders with fees', async () => {
|
||||
const makerFee = new BigNumber(1);
|
||||
const takerFee = new BigNumber(2);
|
||||
@@ -231,12 +249,39 @@ describe('ExchangeWrapper', () => {
|
||||
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
|
||||
makerAddress, takerAddress, fillableAmount, feeRecipient,
|
||||
);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
const txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, takerTokenFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
expect(await zeroEx.token.getBalanceAsync(zrxTokenAddress, feeRecipient))
|
||||
.to.be.bignumber.equal(makerFee.plus(takerFee));
|
||||
});
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
const emptyFillTakerAmount = new BigNumber(0);
|
||||
beforeEach(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
});
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, emptyFillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, emptyFillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, emptyFillTakerAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('#batchFillOrdersAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
@@ -253,29 +298,65 @@ describe('ExchangeWrapper', () => {
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
anotherOrderHashHex = ZeroEx.getOrderHashHex(anotherSignedOrder);
|
||||
orderFillBatch = [
|
||||
{
|
||||
signedOrder,
|
||||
takerTokenFillAmount: fillTakerAmount,
|
||||
},
|
||||
{
|
||||
signedOrder: anotherSignedOrder,
|
||||
takerTokenFillAmount: fillTakerAmount,
|
||||
},
|
||||
];
|
||||
});
|
||||
describe('successful batch fills', () => {
|
||||
it('should no-op for an empty batch', async () => {
|
||||
await zeroEx.exchange.batchFillOrdersAsync(
|
||||
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
beforeEach(() => {
|
||||
orderFillBatch = [
|
||||
{
|
||||
signedOrder,
|
||||
takerTokenFillAmount,
|
||||
},
|
||||
{
|
||||
signedOrder: anotherSignedOrder,
|
||||
takerTokenFillAmount,
|
||||
},
|
||||
];
|
||||
});
|
||||
it('should throw if a batch is empty', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrdersAsync(
|
||||
[], shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
|
||||
});
|
||||
it('should successfully fill multiple orders', async () => {
|
||||
await zeroEx.exchange.batchFillOrdersAsync(
|
||||
const txHash = await zeroEx.exchange.batchFillOrdersAsync(
|
||||
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
|
||||
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
|
||||
expect(filledAmount).to.be.bignumber.equal(fillTakerAmount);
|
||||
expect(anotherFilledAmount).to.be.bignumber.equal(fillTakerAmount);
|
||||
expect(filledAmount).to.be.bignumber.equal(takerTokenFillAmount);
|
||||
expect(anotherFilledAmount).to.be.bignumber.equal(takerTokenFillAmount);
|
||||
});
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
beforeEach(async () => {
|
||||
const emptyFillTakerAmount = new BigNumber(0);
|
||||
orderFillBatch = [
|
||||
{
|
||||
signedOrder,
|
||||
takerTokenFillAmount: emptyFillTakerAmount,
|
||||
},
|
||||
{
|
||||
signedOrder: anotherSignedOrder,
|
||||
takerTokenFillAmount: emptyFillTakerAmount,
|
||||
},
|
||||
];
|
||||
});
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrdersAsync(
|
||||
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrdersAsync(
|
||||
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.batchFillOrdersAsync(
|
||||
orderFillBatch, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -298,25 +379,41 @@ describe('ExchangeWrapper', () => {
|
||||
signedOrders = [signedOrder, anotherSignedOrder];
|
||||
});
|
||||
describe('successful batch fills', () => {
|
||||
it('should no-op for an empty batch', async () => {
|
||||
await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
it('should throw if a batch is empty', async () => {
|
||||
return expect(zeroEx.exchange.fillOrdersUpToAsync(
|
||||
[], fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress),
|
||||
).to.be.rejectedWith(ExchangeContractErrs.BatchOrdersMustHaveAtLeastOneItem);
|
||||
});
|
||||
it('should successfully fill up to specified amount', async () => {
|
||||
await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
const txHash = await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const filledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(signedOrderHashHex);
|
||||
const anotherFilledAmount = await zeroEx.exchange.getFilledTakerAmountAsync(anotherOrderHashHex);
|
||||
expect(filledAmount).to.be.bignumber.equal(fillableAmount);
|
||||
const remainingFillAmount = fillableAmount.minus(1);
|
||||
expect(anotherFilledAmount).to.be.bignumber.equal(remainingFillAmount);
|
||||
});
|
||||
it('should return filled amount', async () => {
|
||||
const filledTakerTokenAmount = await zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, fillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
expect(filledTakerTokenAmount).to.be.bignumber.equal(fillUpToAmount);
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
const emptyFillUpToAmount = new BigNumber(0);
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, emptyFillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, emptyFillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.fillOrdersUpToAsync(
|
||||
signedOrders, emptyFillUpToAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -344,13 +441,27 @@ describe('ExchangeWrapper', () => {
|
||||
describe('#cancelOrderAsync', () => {
|
||||
describe('successful cancels', () => {
|
||||
it('should cancel an order', async () => {
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
|
||||
const cancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHashHex);
|
||||
const txHash = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex);
|
||||
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
|
||||
});
|
||||
it('should return cancelled amount', async () => {
|
||||
const cancelledAmount = await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
|
||||
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
const emptyCancelTakerTokenAmount = new BigNumber(0);
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.cancelOrderAsync(signedOrder, emptyCancelTakerTokenAmount, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -391,22 +502,51 @@ describe('ExchangeWrapper', () => {
|
||||
describe('successful batch cancels', () => {
|
||||
it('should cancel a batch of orders', async () => {
|
||||
await zeroEx.exchange.batchCancelOrdersAsync(cancelBatch);
|
||||
const cancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHashHex);
|
||||
const anotherCancelledAmount = await zeroEx.exchange.getCanceledTakerAmountAsync(
|
||||
const cancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHashHex);
|
||||
const anotherCancelledAmount = await zeroEx.exchange.getCancelledTakerAmountAsync(
|
||||
anotherOrderHashHex,
|
||||
);
|
||||
expect(cancelledAmount).to.be.bignumber.equal(cancelAmount);
|
||||
expect(anotherCancelledAmount).to.be.bignumber.equal(cancelAmount);
|
||||
});
|
||||
});
|
||||
describe('order transaction options', () => {
|
||||
beforeEach(async () => {
|
||||
const emptyTakerTokenCancelAmount = new BigNumber(0);
|
||||
cancelBatch = [
|
||||
{
|
||||
order: signedOrder,
|
||||
takerTokenCancelAmount: emptyTakerTokenCancelAmount,
|
||||
},
|
||||
{
|
||||
order: anotherSignedOrder,
|
||||
takerTokenCancelAmount: emptyTakerTokenCancelAmount,
|
||||
},
|
||||
];
|
||||
});
|
||||
it('should validate when orderTransactionOptions are not present', async () => {
|
||||
return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should validate when orderTransactionOptions specify to validate', async () => {
|
||||
return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, {
|
||||
shouldValidate: true,
|
||||
})).to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should not validate when orderTransactionOptions specify not to validate', async () => {
|
||||
return expect(zeroEx.exchange.batchCancelOrdersAsync(cancelBatch, {
|
||||
shouldValidate: false,
|
||||
})).to.not.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('tests that require partially filled order', () => {
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let takerAddress: string;
|
||||
let fillableAmount: BigNumber.BigNumber;
|
||||
let partialFillAmount: BigNumber.BigNumber;
|
||||
let fillableAmount: BigNumber;
|
||||
let partialFillAmount: BigNumber;
|
||||
let signedOrder: SignedOrder;
|
||||
let orderHash: string;
|
||||
before(() => {
|
||||
@@ -452,23 +592,23 @@ describe('ExchangeWrapper', () => {
|
||||
expect(filledValueT).to.be.bignumber.equal(partialFillAmount);
|
||||
});
|
||||
});
|
||||
describe('#getCanceledTakerAmountAsync', () => {
|
||||
describe('#getCancelledTakerAmountAsync', () => {
|
||||
it('should throw if passed an invalid orderHash', async () => {
|
||||
const invalidOrderHashHex = '0x123';
|
||||
return expect(zeroEx.exchange.getCanceledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected();
|
||||
return expect(zeroEx.exchange.getCancelledTakerAmountAsync(invalidOrderHashHex)).to.be.rejected();
|
||||
});
|
||||
it('should return zero if passed a valid but non-existent orderHash', async () => {
|
||||
const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(NON_EXISTENT_ORDER_HASH);
|
||||
const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(NON_EXISTENT_ORDER_HASH);
|
||||
expect(cancelledValueT).to.be.bignumber.equal(0);
|
||||
});
|
||||
it('should return the cancelledValueT for a valid and partially filled orderHash', async () => {
|
||||
const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHash);
|
||||
const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash);
|
||||
expect(cancelledValueT).to.be.bignumber.equal(0);
|
||||
});
|
||||
it('should return the cancelledValueT for a valid and cancelled orderHash', async () => {
|
||||
const cancelAmount = fillableAmount.minus(partialFillAmount);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmount);
|
||||
const cancelledValueT = await zeroEx.exchange.getCanceledTakerAmountAsync(orderHash);
|
||||
const cancelledValueT = await zeroEx.exchange.getCancelledTakerAmountAsync(orderHash);
|
||||
expect(cancelledValueT).to.be.bignumber.equal(cancelAmount);
|
||||
});
|
||||
});
|
||||
@@ -481,13 +621,9 @@ describe('ExchangeWrapper', () => {
|
||||
let coinbase: string;
|
||||
let takerAddress: string;
|
||||
let makerAddress: string;
|
||||
let fillableAmount: BigNumber.BigNumber;
|
||||
let fillableAmount: BigNumber;
|
||||
let signedOrder: SignedOrder;
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
};
|
||||
const fillTakerAmountInBaseUnits = new BigNumber(1);
|
||||
const takerTokenFillAmountInBaseUnits = new BigNumber(1);
|
||||
const cancelTakerAmountInBaseUnits = new BigNumber(1);
|
||||
before(() => {
|
||||
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||
@@ -502,102 +638,84 @@ describe('ExchangeWrapper', () => {
|
||||
);
|
||||
});
|
||||
afterEach(async () => {
|
||||
await zeroEx.exchange.stopWatchingAllEventsAsync();
|
||||
zeroEx.exchange.unsubscribeAll();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribeAsync` callback,
|
||||
// Since we need to await the receipt of the event in the `subscribe` callback,
|
||||
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
|
||||
// wrap the rest of the test in an async block
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the LogFill event when an order is filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
);
|
||||
zeroExEvent.watch((err: Error, event: ContractEvent) => {
|
||||
expect(err).to.be.null();
|
||||
expect(event).to.not.be.undefined();
|
||||
expect(event.event).to.be.equal('LogFill');
|
||||
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<LogFillContractEventArgs>) => {
|
||||
expect(logEvent.event).to.be.equal(ExchangeEvents.LogFill);
|
||||
done();
|
||||
});
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, indexFilterValues, callback,
|
||||
);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
signedOrder, takerTokenFillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the LogCancel event when an order is cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogCancel, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<LogCancelContractEventArgs>) => {
|
||||
expect(logEvent.event).to.be.equal(ExchangeEvents.LogCancel);
|
||||
done();
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogCancel, indexFilterValues, callback,
|
||||
);
|
||||
zeroExEvent.watch((err: Error, event: ContractEvent) => {
|
||||
expect(err).to.be.null();
|
||||
expect(event).to.not.be.undefined();
|
||||
expect(event.event).to.be.equal('LogCancel');
|
||||
done();
|
||||
});
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelTakerAmountInBaseUnits);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const eventSubscriptionToBeCancelled = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
);
|
||||
eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => {
|
||||
|
||||
const callbackNeverToBeCalled = (err: Error, logEvent: DecodedLogEvent<LogFillContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
});
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled,
|
||||
);
|
||||
|
||||
const newProvider = web3Factory.getRpcProvider();
|
||||
await zeroEx.setProviderAsync(newProvider);
|
||||
|
||||
const eventSubscriptionToStay = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
);
|
||||
eventSubscriptionToStay.watch((err: Error, event: ContractEvent) => {
|
||||
expect(err).to.be.null();
|
||||
expect(event).to.not.be.undefined();
|
||||
expect(event.event).to.be.equal('LogFill');
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<LogFillContractEventArgs>) => {
|
||||
expect(logEvent.event).to.be.equal(ExchangeEvents.LogFill);
|
||||
done();
|
||||
});
|
||||
};
|
||||
await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, indexFilterValues, callback,
|
||||
);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
signedOrder, takerTokenFillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => {
|
||||
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const eventSubscriptionToBeStopped = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
const callbackNeverToBeCalled = (err: Error, logEvent: DecodedLogEvent<LogFillContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
};
|
||||
const subscriptionToken = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, indexFilterValues, callbackNeverToBeCalled,
|
||||
);
|
||||
eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => {
|
||||
done(new Error('Expected this subscription to have been stopped'));
|
||||
});
|
||||
await eventSubscriptionToBeStopped.stopWatchingAsync();
|
||||
zeroEx.exchange.unsubscribe(subscriptionToken);
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
signedOrder, takerTokenFillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance,
|
||||
takerAddress,
|
||||
);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should wrap all event args BigNumber instances in a newer version of BigNumber', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const zeroExEvent = await zeroEx.exchange.subscribeAsync(
|
||||
ExchangeEvents.LogFill, subscriptionOpts, indexFilterValues, exchangeContractAddress,
|
||||
);
|
||||
zeroExEvent.watch((err: Error, event: ContractEvent) => {
|
||||
const args = event.args as LogFillContractEventArgs;
|
||||
expect(args.filledMakerTokenAmount.isBigNumber).to.be.true();
|
||||
expect(args.filledTakerTokenAmount.isBigNumber).to.be.true();
|
||||
expect(args.paidMakerFee.isBigNumber).to.be.true();
|
||||
expect(args.paidTakerFee.isBigNumber).to.be.true();
|
||||
done();
|
||||
});
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillTakerAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
describe('#getOrderHashHexUsingContractCallAsync', () => {
|
||||
let makerTokenAddress: string;
|
||||
@@ -621,4 +739,86 @@ describe('ExchangeWrapper', () => {
|
||||
expect(orderHash).to.equal(orderHashFromContract);
|
||||
});
|
||||
});
|
||||
describe('#getZRXTokenAddressAsync', () => {
|
||||
it('gets the same token as is in token registry', async () => {
|
||||
const zrxAddress = await zeroEx.exchange.getZRXTokenAddressAsync();
|
||||
const zrxToken = tokenUtils.getProtocolTokenOrThrow();
|
||||
expect(zrxAddress).to.equal(zrxToken.address);
|
||||
});
|
||||
});
|
||||
describe('#getLogsAsync', () => {
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: BlockParamLiteral.Earliest,
|
||||
toBlock: BlockParamLiteral.Latest,
|
||||
};
|
||||
let txHash: string;
|
||||
before(async () => {
|
||||
[, makerAddress, takerAddress] = userAddresses;
|
||||
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
});
|
||||
it('should get logs with decoded args emitted by LogFill', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = ExchangeEvents.LogFill;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.exchange.getLogsAsync(eventName, subscriptionOpts, indexFilterValues);
|
||||
expect(logs).to.have.length(1);
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
});
|
||||
it('should only get the logs with the correct event name', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const differentEventName = ExchangeEvents.LogCancel;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.exchange.getLogsAsync(differentEventName, subscriptionOpts, indexFilterValues);
|
||||
expect(logs).to.have.length(0);
|
||||
});
|
||||
it('should only get the logs with the correct indexed fields', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
const differentMakerAddress = userAddresses[2];
|
||||
const anotherSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, differentMakerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
txHash = await zeroEx.exchange.fillOrderAsync(
|
||||
anotherSignedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
|
||||
const eventName = ExchangeEvents.LogFill;
|
||||
const indexFilterValues = {
|
||||
maker: differentMakerAddress,
|
||||
};
|
||||
const logs = await zeroEx.exchange.getLogsAsync<LogFillContractEventArgs>(
|
||||
eventName, subscriptionOpts, indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(args.maker).to.be.equal(differentMakerAddress);
|
||||
});
|
||||
});
|
||||
});
|
||||
410
packages/0x.js/test/order_state_watcher_test.ts
Normal file
410
packages/0x.js/test/order_state_watcher_test.ts
Normal file
@@ -0,0 +1,410 @@
|
||||
import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import * as _ from 'lodash';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import { chaiSetup } from './utils/chai_setup';
|
||||
import { web3Factory } from './utils/web3_factory';
|
||||
import { Web3Wrapper } from '../src/web3_wrapper';
|
||||
import { OrderStateWatcher } from '../src/order_watcher/order_state_watcher';
|
||||
import {
|
||||
Token,
|
||||
ZeroEx,
|
||||
LogEvent,
|
||||
DecodedLogEvent,
|
||||
ZeroExConfig,
|
||||
OrderState,
|
||||
SignedOrder,
|
||||
ZeroExError,
|
||||
OrderStateValid,
|
||||
OrderStateInvalid,
|
||||
ExchangeContractErrs,
|
||||
} from '../src';
|
||||
import { TokenUtils } from './utils/token_utils';
|
||||
import { FillScenarios } from './utils/fill_scenarios';
|
||||
import { DoneCallback } from '../src/types';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {reportCallbackErrors} from './utils/report_callback_errors';
|
||||
|
||||
const TIMEOUT_MS = 150;
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('OrderStateWatcher', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let fillScenarios: FillScenarios;
|
||||
let userAddresses: string[];
|
||||
let zrxTokenAddress: string;
|
||||
let exchangeContractAddress: string;
|
||||
let makerToken: Token;
|
||||
let takerToken: Token;
|
||||
let maker: string;
|
||||
let taker: string;
|
||||
let web3Wrapper: Web3Wrapper;
|
||||
let signedOrder: SignedOrder;
|
||||
const fillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(5), 18);
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[, maker, taker] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
[makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||
web3Wrapper = (zeroEx as any)._web3Wrapper;
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#removeOrder', async () => {
|
||||
it('should successfully remove existing order', async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.include({
|
||||
[orderHash]: signedOrder,
|
||||
});
|
||||
let dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
|
||||
expect(dependentOrderHashes[signedOrder.maker][signedOrder.makerTokenAddress]).to.have.keys(orderHash);
|
||||
await zeroEx.orderStateWatcher.removeOrderAsync(orderHash);
|
||||
expect((zeroEx.orderStateWatcher as any)._orderByOrderHash).to.not.include({
|
||||
[orderHash]: signedOrder,
|
||||
});
|
||||
dependentOrderHashes = (zeroEx.orderStateWatcher as any)._dependentOrderHashes;
|
||||
expect(dependentOrderHashes[signedOrder.maker]).to.be.undefined();
|
||||
});
|
||||
it('should no-op when removing a non-existing order', async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
const nonExistentOrderHash = `0x${orderHash.substr(2).split('').reverse().join('')}`;
|
||||
await zeroEx.orderStateWatcher.removeOrderAsync(nonExistentOrderHash);
|
||||
});
|
||||
});
|
||||
describe('#subscribe', async () => {
|
||||
afterEach(async () => {
|
||||
zeroEx.orderStateWatcher.unsubscribe();
|
||||
});
|
||||
it('should fail when trying to subscribe twice', async () => {
|
||||
zeroEx.orderStateWatcher.subscribe(_.noop);
|
||||
expect(() => zeroEx.orderStateWatcher.subscribe(_.noop))
|
||||
.to.throw(ZeroExError.SubscriptionAlreadyPresent);
|
||||
});
|
||||
});
|
||||
describe('tests with cleanup', async () => {
|
||||
afterEach(async () => {
|
||||
zeroEx.orderStateWatcher.unsubscribe();
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.removeOrderAsync(orderHash);
|
||||
});
|
||||
it('should emit orderStateInvalid when maker allowance set to 0 for watched order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerAllowance);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, new BigNumber(0));
|
||||
})().catch(done);
|
||||
});
|
||||
it('should not emit an orderState event when irrelevant Transfer event received', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
throw new Error('OrderState callback fired for irrelevant order');
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const notTheMaker = userAddresses[0];
|
||||
const anyRecipient = taker;
|
||||
const transferAmount = new BigNumber(2);
|
||||
const notTheMakerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, notTheMaker);
|
||||
await zeroEx.token.transferAsync(makerToken.address, notTheMaker, anyRecipient, transferAmount);
|
||||
setTimeout(() => {
|
||||
done();
|
||||
}, TIMEOUT_MS);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when maker moves balance backing watched order', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.InsufficientMakerBalance);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const anyRecipient = taker;
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
await zeroEx.token.transferAsync(makerToken.address, maker, anyRecipient, makerBalance);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when watched order fully filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
let eventCount = 0;
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
eventCount++;
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
if (eventCount === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillableAmount, shouldThrowOnInsufficientBalanceOrAllowance, taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateValid when watched order partially filled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker);
|
||||
|
||||
const fillAmountInBaseUnits = new BigNumber(2);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
let eventCount = 0;
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
eventCount++;
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
const remainingMakerBalance = makerBalance.sub(fillAmountInBaseUnits);
|
||||
const remainingFillable = fillableAmount.minus(fillAmountInBaseUnits);
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFillable);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingFillable);
|
||||
expect(orderRelevantState.makerBalance).to.be.bignumber.equal(remainingMakerBalance);
|
||||
if (eventCount === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should trigger the callback when orders backing ZRX allowance changes', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const makerFee = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
|
||||
const takerFee = ZeroEx.toBaseUnitAmount(new BigNumber(0), 18);
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerToken.address, takerToken.address, makerFee, takerFee, maker, taker, fillableAmount,
|
||||
taker);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(zrxTokenAddress, maker, new BigNumber(0));
|
||||
})().catch(done);
|
||||
});
|
||||
describe('remainingFillable(M|T)akerTokenAmount', () => {
|
||||
it('should calculate correct remaining fillable', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const takerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(10), 18);
|
||||
const makerFillableAmount = ZeroEx.toBaseUnitAmount(new BigNumber(20), 18);
|
||||
signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, makerFillableAmount,
|
||||
takerFillableAmount,
|
||||
);
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker);
|
||||
const fillAmountInBaseUnits = ZeroEx.toBaseUnitAmount(new BigNumber(2), 18);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
let eventCount = 0;
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
eventCount++;
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(16), 18));
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
ZeroEx.toBaseUnitAmount(new BigNumber(8), 18));
|
||||
if (eventCount === 2) {
|
||||
done();
|
||||
}
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, fillAmountInBaseUnits, shouldThrowOnInsufficientBalanceOrAllowance, taker,
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal approved amount when approved amount is lowest', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
|
||||
const changedMakerApprovalAmount = ZeroEx.toBaseUnitAmount(new BigNumber(3), 18);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
changedMakerApprovalAmount);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
changedMakerApprovalAmount);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerToken.address, maker, changedMakerApprovalAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should equal balance amount when balance amount is lowest', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
|
||||
const remainingAmount = ZeroEx.toBaseUnitAmount(new BigNumber(1), 18);
|
||||
const transferAmount = makerBalance.sub(remainingAmount);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.remainingFillableMakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingAmount);
|
||||
expect(orderRelevantState.remainingFillableTakerTokenAmount).to.be.bignumber.equal(
|
||||
remainingAmount);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.token.transferAsync(
|
||||
makerToken.address, maker, ZeroEx.NULL_ADDRESS, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
it('should emit orderStateInvalid when watched order cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateInvalid when within rounding error range', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const remainingFillableAmountInBaseUnits = new BigNumber(100);
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.false();
|
||||
const invalidOrderState = orderState as OrderStateInvalid;
|
||||
expect(invalidOrderState.orderHash).to.be.equal(orderHash);
|
||||
expect(invalidOrderState.error).to.be.equal(ExchangeContractErrs.OrderFillRoundingError);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(
|
||||
signedOrder, fillableAmount.minus(remainingFillableAmountInBaseUnits),
|
||||
);
|
||||
})().catch(done);
|
||||
});
|
||||
it('should emit orderStateValid when watched order partially cancelled', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerToken.address, takerToken.address, maker, taker, fillableAmount,
|
||||
);
|
||||
|
||||
const makerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, maker);
|
||||
const takerBalance = await zeroEx.token.getBalanceAsync(makerToken.address, taker);
|
||||
|
||||
const cancelAmountInBaseUnits = new BigNumber(2);
|
||||
const orderHash = ZeroEx.getOrderHashHex(signedOrder);
|
||||
await zeroEx.orderStateWatcher.addOrderAsync(signedOrder);
|
||||
|
||||
const callback = reportCallbackErrors(done)((orderState: OrderState) => {
|
||||
expect(orderState.isValid).to.be.true();
|
||||
const validOrderState = orderState as OrderStateValid;
|
||||
expect(validOrderState.orderHash).to.be.equal(orderHash);
|
||||
const orderRelevantState = validOrderState.orderRelevantState;
|
||||
expect(orderRelevantState.cancelledTakerTokenAmount).to.be.bignumber.equal(cancelAmountInBaseUnits);
|
||||
done();
|
||||
});
|
||||
zeroEx.orderStateWatcher.subscribe(callback);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, cancelAmountInBaseUnits);
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
327
packages/0x.js/test/order_validation_test.ts
Normal file
327
packages/0x.js/test/order_validation_test.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
import * as chai from 'chai';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as Sinon from 'sinon';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx, SignedOrder, Token, ExchangeContractErrs, ZeroExError} from '../src';
|
||||
import {TradeSide, TransferType} from '../src/types';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {FillScenarios} from './utils/fill_scenarios';
|
||||
import {OrderValidationUtils} from '../src/utils/order_validation_utils';
|
||||
import {ExchangeTransferSimulator} from '../src/utils/exchange_transfer_simulator';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('OrderValidation', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let exchangeContractAddress: string;
|
||||
let zrxTokenAddress: string;
|
||||
let fillScenarios: FillScenarios;
|
||||
let makerTokenAddress: string;
|
||||
let takerTokenAddress: string;
|
||||
let coinbase: string;
|
||||
let makerAddress: string;
|
||||
let takerAddress: string;
|
||||
let feeRecipient: string;
|
||||
let orderValidationUtils: OrderValidationUtils;
|
||||
const fillableAmount = new BigNumber(5);
|
||||
const fillTakerAmount = new BigNumber(5);
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
exchangeContractAddress = await zeroEx.exchange.getContractAddressAsync();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
[coinbase, makerAddress, takerAddress, feeRecipient] = userAddresses;
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
zrxTokenAddress = tokenUtils.getProtocolTokenOrThrow().address;
|
||||
fillScenarios = new FillScenarios(zeroEx, userAddresses, tokens, zrxTokenAddress, exchangeContractAddress);
|
||||
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
orderValidationUtils = new OrderValidationUtils(zeroEx.token, zeroEx.exchange);
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('validateOrderFillableOrThrowAsync', () => {
|
||||
it('should succeed if the order is fillable', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
);
|
||||
});
|
||||
it('should succeed if the order is asymmetric and fillable', async () => {
|
||||
const makerFillableAmount = fillableAmount;
|
||||
const takerFillableAmount = fillableAmount.minus(4);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
makerFillableAmount, takerFillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, expirationInPast,
|
||||
);
|
||||
return expect(zeroEx.exchange.validateOrderFillableOrThrowAsync(
|
||||
signedOrder,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
|
||||
});
|
||||
});
|
||||
describe('validateFillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw when the fill amount is zero', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const zeroFillAmount = new BigNumber(0);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, zeroFillAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillAmountZero);
|
||||
});
|
||||
it('should throw when the signature is invalid', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
// 27 <--> 28
|
||||
signedOrder.ecSignature.v = 27 + (28 - signedOrder.ecSignature.v);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillableAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ZeroExError.InvalidSignature);
|
||||
});
|
||||
it('should throw when the order is fully filled or cancelled', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillableAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderRemainingFillAmountZero);
|
||||
});
|
||||
it('should throw when sender is not a taker', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const nonTakerAddress = userAddresses[6];
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmount, nonTakerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.TransactionSenderIsNotFillOrderTaker);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, expirationInPast,
|
||||
);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillExpired);
|
||||
});
|
||||
it('should throw when there a rounding error would have occurred', async () => {
|
||||
const makerAmount = new BigNumber(3);
|
||||
const takerAmount = new BigNumber(5);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
makerAmount, takerAmount,
|
||||
);
|
||||
const fillTakerAmountThatCausesRoundingError = new BigNumber(3);
|
||||
return expect(zeroEx.exchange.validateFillOrderThrowIfInvalidAsync(
|
||||
signedOrder, fillTakerAmountThatCausesRoundingError, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.OrderFillRoundingError);
|
||||
});
|
||||
});
|
||||
describe('#validateFillOrKillOrderAndThrowIfInvalidAsync', () => {
|
||||
it('should throw if remaining fillAmount is less then the desired fillAmount', async () => {
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
const tooLargeFillAmount = new BigNumber(7);
|
||||
const fillAmountDifference = tooLargeFillAmount.minus(fillableAmount);
|
||||
await zeroEx.token.transferAsync(takerTokenAddress, coinbase, takerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(takerTokenAddress, takerAddress, tooLargeFillAmount);
|
||||
await zeroEx.token.transferAsync(makerTokenAddress, coinbase, makerAddress, fillAmountDifference);
|
||||
await zeroEx.token.setProxyAllowanceAsync(makerTokenAddress, makerAddress, tooLargeFillAmount);
|
||||
|
||||
return expect(zeroEx.exchange.validateFillOrKillOrderThrowIfInvalidAsync(
|
||||
signedOrder, tooLargeFillAmount, takerAddress,
|
||||
)).to.be.rejectedWith(ExchangeContractErrs.InsufficientRemainingFillAmount);
|
||||
});
|
||||
});
|
||||
describe('validateCancelOrderAndThrowIfInvalidAsync', () => {
|
||||
let signedOrder: SignedOrder;
|
||||
let orderHashHex: string;
|
||||
const cancelAmount = new BigNumber(3);
|
||||
beforeEach(async () => {
|
||||
[coinbase, makerAddress, takerAddress] = userAddresses;
|
||||
const [makerToken, takerToken] = tokenUtils.getNonProtocolTokens();
|
||||
makerTokenAddress = makerToken.address;
|
||||
takerTokenAddress = takerToken.address;
|
||||
signedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, fillableAmount,
|
||||
);
|
||||
orderHashHex = ZeroEx.getOrderHashHex(signedOrder);
|
||||
});
|
||||
it('should throw when cancel amount is zero', async () => {
|
||||
const zeroCancelAmount = new BigNumber(0);
|
||||
return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, zeroCancelAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelAmountZero);
|
||||
});
|
||||
it('should throw when order is expired', async () => {
|
||||
const expirationInPast = new BigNumber(1496826058); // 7th Jun 2017
|
||||
const expiredSignedOrder = await fillScenarios.createFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, expirationInPast,
|
||||
);
|
||||
orderHashHex = ZeroEx.getOrderHashHex(expiredSignedOrder);
|
||||
return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(expiredSignedOrder, cancelAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderCancelExpired);
|
||||
});
|
||||
it('should throw when order is already cancelled or filled', async () => {
|
||||
await zeroEx.exchange.cancelOrderAsync(signedOrder, fillableAmount);
|
||||
return expect(zeroEx.exchange.validateCancelOrderThrowIfInvalidAsync(signedOrder, fillableAmount))
|
||||
.to.be.rejectedWith(ExchangeContractErrs.OrderAlreadyCancelledOrFilled);
|
||||
});
|
||||
});
|
||||
describe('#validateFillOrderBalancesAllowancesThrowIfInvalidAsync', () => {
|
||||
let exchangeTransferSimulator: ExchangeTransferSimulator;
|
||||
let transferFromAsync: Sinon.SinonSpy;
|
||||
const bigNumberMatch = (expected: BigNumber) => {
|
||||
return Sinon.match((value: BigNumber) => value.eq(expected));
|
||||
};
|
||||
beforeEach('create exchangeTransferSimulator', async () => {
|
||||
exchangeTransferSimulator = new ExchangeTransferSimulator(zeroEx.token);
|
||||
transferFromAsync = Sinon.spy();
|
||||
exchangeTransferSimulator.transferFromAsync = transferFromAsync as any;
|
||||
});
|
||||
it('should call exchangeTransferSimulator.transferFrom in a correct order', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
|
||||
makerAddress, takerAddress, fillableAmount, feeRecipient,
|
||||
);
|
||||
await orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator, signedOrder, fillableAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
expect(
|
||||
transferFromAsync.getCall(0).calledWith(
|
||||
makerTokenAddress, makerAddress, takerAddress, bigNumberMatch(fillableAmount),
|
||||
TradeSide.Maker, TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync.getCall(1).calledWith(
|
||||
takerTokenAddress, takerAddress, makerAddress, bigNumberMatch(fillableAmount),
|
||||
TradeSide.Taker, TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync.getCall(2).calledWith(
|
||||
zrxTokenAddress, makerAddress, feeRecipient, bigNumberMatch(makerFee),
|
||||
TradeSide.Maker, TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync.getCall(3).calledWith(
|
||||
zrxTokenAddress, takerAddress, feeRecipient, bigNumberMatch(takerFee),
|
||||
TradeSide.Taker, TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
});
|
||||
it('should call exchangeTransferSimulator.transferFrom with correct values for an open order', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(2);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerFee, takerFee,
|
||||
makerAddress, ZeroEx.NULL_ADDRESS, fillableAmount, feeRecipient,
|
||||
);
|
||||
await orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator, signedOrder, fillableAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
expect(
|
||||
transferFromAsync.getCall(0).calledWith(
|
||||
makerTokenAddress, makerAddress, takerAddress, bigNumberMatch(fillableAmount),
|
||||
TradeSide.Maker, TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync.getCall(1).calledWith(
|
||||
takerTokenAddress, takerAddress, makerAddress, bigNumberMatch(fillableAmount),
|
||||
TradeSide.Taker, TransferType.Trade,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync.getCall(2).calledWith(
|
||||
zrxTokenAddress, makerAddress, feeRecipient, bigNumberMatch(makerFee),
|
||||
TradeSide.Maker, TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
expect(
|
||||
transferFromAsync.getCall(3).calledWith(
|
||||
zrxTokenAddress, takerAddress, feeRecipient, bigNumberMatch(takerFee),
|
||||
TradeSide.Taker, TransferType.Fee,
|
||||
),
|
||||
).to.be.true();
|
||||
});
|
||||
it('should correctly round the fillMakerTokenAmount', async () => {
|
||||
const makerTokenAmount = new BigNumber(3);
|
||||
const takerTokenAmount = new BigNumber(1);
|
||||
const signedOrder = await fillScenarios.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress, makerTokenAmount, takerTokenAmount,
|
||||
);
|
||||
await orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator, signedOrder, takerTokenAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
const makerFillAmount = transferFromAsync.getCall(0).args[3];
|
||||
expect(makerFillAmount).to.be.bignumber.equal(makerTokenAmount);
|
||||
});
|
||||
it('should correctly round the makerFeeAmount', async () => {
|
||||
const makerFee = new BigNumber(2);
|
||||
const takerFee = new BigNumber(4);
|
||||
const signedOrder = await fillScenarios.createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress,
|
||||
fillableAmount, ZeroEx.NULL_ADDRESS,
|
||||
);
|
||||
const fillTakerTokenAmount = fillableAmount.div(2).round(0);
|
||||
await orderValidationUtils.validateFillOrderBalancesAllowancesThrowIfInvalidAsync(
|
||||
exchangeTransferSimulator, signedOrder, fillTakerTokenAmount, takerAddress, zrxTokenAddress,
|
||||
);
|
||||
const makerPartialFee = makerFee.div(2);
|
||||
const takerPartialFee = takerFee.div(2);
|
||||
expect(transferFromAsync.callCount).to.be.equal(4);
|
||||
const partialMakerFee = transferFromAsync.getCall(2).args[3];
|
||||
expect(partialMakerFee).to.be.bignumber.equal(makerPartialFee);
|
||||
const partialTakerFee = transferFromAsync.getCall(3).args[3];
|
||||
expect(partialTakerFee).to.be.bignumber.equal(takerPartialFee);
|
||||
});
|
||||
});
|
||||
});
|
||||
95
packages/0x.js/test/subscription_test.ts
Normal file
95
packages/0x.js/test/subscription_test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import 'mocha';
|
||||
import * as _ from 'lodash';
|
||||
import * as chai from 'chai';
|
||||
import * as Sinon from 'sinon';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import * as Web3 from 'web3';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {
|
||||
ZeroEx,
|
||||
ZeroExError,
|
||||
Token,
|
||||
ApprovalContractEventArgs,
|
||||
TokenEvents,
|
||||
DecodedLogEvent,
|
||||
} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {DoneCallback, BlockParamLiteral} from '../src/types';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
const blockchainLifecycle = new BlockchainLifecycle();
|
||||
|
||||
describe('SubscriptionTest', () => {
|
||||
let web3: Web3;
|
||||
let zeroEx: ZeroEx;
|
||||
let userAddresses: string[];
|
||||
let tokens: Token[];
|
||||
let tokenUtils: TokenUtils;
|
||||
let coinbase: string;
|
||||
let addressWithoutFunds: string;
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
coinbase = userAddresses[0];
|
||||
addressWithoutFunds = userAddresses[1];
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await blockchainLifecycle.startAsync();
|
||||
});
|
||||
afterEach(async () => {
|
||||
await blockchainLifecycle.revertAsync();
|
||||
});
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
let tokenAddress: string;
|
||||
const transferAmount = new BigNumber(42);
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
let stubs: Sinon.SinonStub[] = [];
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
});
|
||||
afterEach(() => {
|
||||
zeroEx.token.unsubscribeAll();
|
||||
_.each(stubs, s => s.restore());
|
||||
stubs = [];
|
||||
});
|
||||
it('Should receive the Error when an error occurs', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
expect(err).to.not.be.null();
|
||||
expect(logEvent).to.be.undefined();
|
||||
done();
|
||||
};
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync')
|
||||
.throws('JSON RPC error'),
|
||||
];
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should allow unsubscribeAll to be called successfully after an error', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => _.noop;
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
stubs = [
|
||||
Sinon.stub((zeroEx as any)._web3Wrapper, 'getBlockAsync')
|
||||
.throws('JSON RPC error'),
|
||||
];
|
||||
zeroEx.token.unsubscribeAll();
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,11 @@
|
||||
import * as _ from 'lodash';
|
||||
import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import {SchemaValidator, schemas} from '@0xproject/json-schemas';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {ZeroEx, Token} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {SchemaValidator} from '../src/utils/schema_validator';
|
||||
import {tokenSchema} from '../src/schemas/token_schema';
|
||||
import {addressSchema} from '../src/schemas/basic_type_schemas';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -49,7 +47,7 @@ describe('TokenRegistryWrapper', () => {
|
||||
|
||||
const schemaValidator = new SchemaValidator();
|
||||
_.each(tokens, token => {
|
||||
const validationResult = schemaValidator.validate(token, tokenSchema);
|
||||
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
});
|
||||
});
|
||||
@@ -61,7 +59,7 @@ describe('TokenRegistryWrapper', () => {
|
||||
|
||||
const schemaValidator = new SchemaValidator();
|
||||
_.each(tokenAddresses, tokenAddress => {
|
||||
const validationResult = schemaValidator.validate(tokenAddress, addressSchema);
|
||||
const validationResult = schemaValidator.validate(tokenAddress, schemas.addressSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
expect(tokenAddress).to.not.be.equal(ZeroEx.NULL_ADDRESS);
|
||||
});
|
||||
@@ -113,7 +111,7 @@ describe('TokenRegistryWrapper', () => {
|
||||
|
||||
const token = await zeroEx.tokenRegistry.getTokenIfExistsAsync(aToken.address);
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const validationResult = schemaValidator.validate(token, tokenSchema);
|
||||
const validationResult = schemaValidator.validate(token, schemas.tokenSchema);
|
||||
expect(validationResult.errors).to.have.lengthOf(0);
|
||||
});
|
||||
it('should return return undefined when passed a token address not in the tokenRegistry', async () => {
|
||||
@@ -2,7 +2,7 @@ import 'mocha';
|
||||
import * as chai from 'chai';
|
||||
import {chaiSetup} from './utils/chai_setup';
|
||||
import * as Web3 from 'web3';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import promisify = require('es6-promisify');
|
||||
import {web3Factory} from './utils/web3_factory';
|
||||
import {
|
||||
@@ -14,10 +14,14 @@ import {
|
||||
ContractEvent,
|
||||
TransferContractEventArgs,
|
||||
ApprovalContractEventArgs,
|
||||
TokenContractEventArgs,
|
||||
LogWithDecodedArgs,
|
||||
LogEvent,
|
||||
DecodedLogEvent,
|
||||
} from '../src';
|
||||
import {BlockchainLifecycle} from './utils/blockchain_lifecycle';
|
||||
import {TokenUtils} from './utils/token_utils';
|
||||
import {DoneCallback} from '../src/types';
|
||||
import {DoneCallback, BlockParamLiteral} from '../src/types';
|
||||
|
||||
chaiSetup.configure();
|
||||
const expect = chai.expect;
|
||||
@@ -34,7 +38,7 @@ describe('TokenWrapper', () => {
|
||||
before(async () => {
|
||||
web3 = web3Factory.create();
|
||||
zeroEx = new ZeroEx(web3.currentProvider);
|
||||
userAddresses = await promisify(web3.eth.getAccounts)();
|
||||
userAddresses = await zeroEx.getAvailableAddressesAsync();
|
||||
tokens = await zeroEx.tokenRegistry.getTokensAsync();
|
||||
tokenUtils = new TokenUtils(tokens);
|
||||
coinbase = userAddresses[0];
|
||||
@@ -48,7 +52,7 @@ describe('TokenWrapper', () => {
|
||||
});
|
||||
describe('#transferAsync', () => {
|
||||
let token: Token;
|
||||
let transferAmount: BigNumber.BigNumber;
|
||||
let transferAmount: BigNumber;
|
||||
before(() => {
|
||||
token = tokens[0];
|
||||
transferAmount = new BigNumber(42);
|
||||
@@ -58,7 +62,8 @@ describe('TokenWrapper', () => {
|
||||
const toAddress = addressWithoutFunds;
|
||||
const preBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
expect(preBalance).to.be.bignumber.equal(0);
|
||||
await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
const txHash = await zeroEx.token.transferAsync(token.address, fromAddress, toAddress, transferAmount);
|
||||
const receipt = await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const postBalance = await zeroEx.token.getBalanceAsync(token.address, toAddress);
|
||||
return expect(postBalance).to.be.bignumber.equal(transferAmount);
|
||||
});
|
||||
@@ -157,7 +162,7 @@ describe('TokenWrapper', () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroEx.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('100000000000000000000000000');
|
||||
const expectedBalance = new BigNumber('1000000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
it('should throw a CONTRACT_DOES_NOT_EXIST error for a non-existent token contract', async () => {
|
||||
@@ -185,7 +190,7 @@ describe('TokenWrapper', () => {
|
||||
const token = tokens[0];
|
||||
const ownerAddress = coinbase;
|
||||
const balance = await zeroExWithoutAccounts.token.getBalanceAsync(token.address, ownerAddress);
|
||||
const expectedBalance = new BigNumber('100000000000000000000000000');
|
||||
const expectedBalance = new BigNumber('1000000000000000000000000000');
|
||||
return expect(balance).to.be.bignumber.equal(expectedBalance);
|
||||
});
|
||||
});
|
||||
@@ -334,104 +339,142 @@ describe('TokenWrapper', () => {
|
||||
return expect(allowance).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
});
|
||||
describe('#subscribeAsync', () => {
|
||||
describe('#subscribe', () => {
|
||||
const indexFilterValues = {};
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = true;
|
||||
let tokenAddress: string;
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: 0,
|
||||
toBlock: 'latest',
|
||||
};
|
||||
const transferAmount = new BigNumber(42);
|
||||
const allowanceAmount = new BigNumber(42);
|
||||
before(() => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
});
|
||||
afterEach(async () => {
|
||||
await zeroEx.token.stopWatchingAllEventsAsync();
|
||||
afterEach(() => {
|
||||
zeroEx.token.unsubscribeAll();
|
||||
});
|
||||
// Hack: Mocha does not allow a test to be both async and have a `done` callback
|
||||
// Since we need to await the receipt of the event in the `subscribeAsync` callback,
|
||||
// Since we need to await the receipt of the event in the `subscribe` callback,
|
||||
// we do need both. A hack is to make the top-level a sync fn w/ a done callback and then
|
||||
// wrap the rest of the test in an async block
|
||||
// Source: https://github.com/mochajs/mocha/issues/2407
|
||||
it('Should receive the Transfer event when an order is filled', (done: DoneCallback) => {
|
||||
it('Should receive the Transfer event when tokens are transfered', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const zeroExEvent = await zeroEx.token.subscribeAsync(
|
||||
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
|
||||
zeroExEvent.watch((err: Error, event: ContractEvent) => {
|
||||
expect(err).to.be.null();
|
||||
expect(event).to.not.be.undefined();
|
||||
const args = event.args as TransferContractEventArgs;
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<TransferContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
expect(logEvent.logIndex).to.be.equal(0);
|
||||
expect(logEvent.transactionIndex).to.be.equal(0);
|
||||
expect(logEvent.blockNumber).to.be.instanceOf(Number);
|
||||
const args = logEvent.args;
|
||||
expect(args._from).to.be.equal(coinbase);
|
||||
expect(args._to).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(transferAmount);
|
||||
done();
|
||||
});
|
||||
};
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callback);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should receive the Approval event when an order is cancelled', (done: DoneCallback) => {
|
||||
it('Should receive the Approval event when allowance is being set', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const zeroExEvent = await zeroEx.token.subscribeAsync(
|
||||
tokenAddress, TokenEvents.Approval, subscriptionOpts, indexFilterValues);
|
||||
zeroExEvent.watch((err: Error, event: ContractEvent) => {
|
||||
expect(err).to.be.null();
|
||||
expect(event).to.not.be.undefined();
|
||||
const args = event.args as ApprovalContractEventArgs;
|
||||
const callback = (err: Error, logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
expect(logEvent).to.not.be.undefined();
|
||||
const args = logEvent.args;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(addressWithoutFunds);
|
||||
expect(args._value).to.be.bignumber.equal(allowanceAmount);
|
||||
done();
|
||||
});
|
||||
};
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Approval, indexFilterValues, callback);
|
||||
await zeroEx.token.setAllowanceAsync(tokenAddress, coinbase, addressWithoutFunds, allowanceAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Outstanding subscriptions are cancelled when zeroEx.setProviderAsync called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const eventSubscriptionToBeCancelled = await zeroEx.token.subscribeAsync(
|
||||
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
|
||||
eventSubscriptionToBeCancelled.watch((err: Error, event: ContractEvent) => {
|
||||
const callbackNeverToBeCalled = (err: Error, logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
});
|
||||
|
||||
};
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled,
|
||||
);
|
||||
const callbackToBeCalled = (err: Error, logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done();
|
||||
};
|
||||
const newProvider = web3Factory.getRpcProvider();
|
||||
await zeroEx.setProviderAsync(newProvider);
|
||||
|
||||
const eventSubscriptionToStay = await zeroEx.token.subscribeAsync(
|
||||
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
|
||||
eventSubscriptionToStay.watch((err: Error, event: ContractEvent) => {
|
||||
expect(err).to.be.null();
|
||||
expect(event).to.not.be.undefined();
|
||||
done();
|
||||
});
|
||||
zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackToBeCalled,
|
||||
);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should stop watch for events when stopWatchingAsync called on the eventEmitter', (done: DoneCallback) => {
|
||||
it('Should cancel subscription when unsubscribe called', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const eventSubscriptionToBeStopped = await zeroEx.token.subscribeAsync(
|
||||
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
|
||||
eventSubscriptionToBeStopped.watch((err: Error, event: ContractEvent) => {
|
||||
done(new Error('Expected this subscription to have been stopped'));
|
||||
});
|
||||
await eventSubscriptionToBeStopped.stopWatchingAsync();
|
||||
const callbackNeverToBeCalled = (err: Error, logEvent: DecodedLogEvent<ApprovalContractEventArgs>) => {
|
||||
done(new Error('Expected this subscription to have been cancelled'));
|
||||
};
|
||||
const subscriptionToken = zeroEx.token.subscribe(
|
||||
tokenAddress, TokenEvents.Transfer, indexFilterValues, callbackNeverToBeCalled);
|
||||
zeroEx.token.unsubscribe(subscriptionToken);
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
done();
|
||||
})().catch(done);
|
||||
});
|
||||
it('Should wrap all event args BigNumber instances in a newer version of BigNumber', (done: DoneCallback) => {
|
||||
(async () => {
|
||||
const zeroExEvent = await zeroEx.token.subscribeAsync(
|
||||
tokenAddress, TokenEvents.Transfer, subscriptionOpts, indexFilterValues);
|
||||
zeroExEvent.watch((err: Error, event: ContractEvent) => {
|
||||
const args = event.args as TransferContractEventArgs;
|
||||
expect(args._value.isBigNumber).to.be.true();
|
||||
done();
|
||||
});
|
||||
await zeroEx.token.transferAsync(tokenAddress, coinbase, addressWithoutFunds, transferAmount);
|
||||
})().catch(done);
|
||||
});
|
||||
describe('#getLogsAsync', () => {
|
||||
let tokenAddress: string;
|
||||
let tokenTransferProxyAddress: string;
|
||||
const subscriptionOpts: SubscriptionOpts = {
|
||||
fromBlock: BlockParamLiteral.Earliest,
|
||||
toBlock: BlockParamLiteral.Latest,
|
||||
};
|
||||
let txHash: string;
|
||||
before(async () => {
|
||||
const token = tokens[0];
|
||||
tokenAddress = token.address;
|
||||
tokenTransferProxyAddress = await zeroEx.proxy.getContractAddressAsync();
|
||||
});
|
||||
it('should get logs with decoded args emitted by Approval', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = TokenEvents.Approval;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
|
||||
tokenAddress, eventName, subscriptionOpts, indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(logs[0].event).to.be.equal(eventName);
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
expect(args._spender).to.be.equal(tokenTransferProxyAddress);
|
||||
expect(args._value).to.be.bignumber.equal(zeroEx.token.UNLIMITED_ALLOWANCE_IN_BASE_UNITS);
|
||||
});
|
||||
it('should only get the logs with the correct event name', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const differentEventName = TokenEvents.Transfer;
|
||||
const indexFilterValues = {};
|
||||
const logs = await zeroEx.token.getLogsAsync(
|
||||
tokenAddress, differentEventName, subscriptionOpts, indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(0);
|
||||
});
|
||||
it('should only get the logs with the correct indexed fields', async () => {
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, coinbase);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
txHash = await zeroEx.token.setUnlimitedProxyAllowanceAsync(tokenAddress, addressWithoutFunds);
|
||||
await zeroEx.awaitTransactionMinedAsync(txHash);
|
||||
const eventName = TokenEvents.Approval;
|
||||
const indexFilterValues = {
|
||||
_owner: coinbase,
|
||||
};
|
||||
const logs = await zeroEx.token.getLogsAsync<ApprovalContractEventArgs>(
|
||||
tokenAddress, eventName, subscriptionOpts, indexFilterValues,
|
||||
);
|
||||
expect(logs).to.have.length(1);
|
||||
const args = logs[0].args;
|
||||
expect(args._owner).to.be.equal(coinbase);
|
||||
});
|
||||
});
|
||||
});
|
||||
26
packages/0x.js/test/utils/blockchain_lifecycle.ts
Normal file
26
packages/0x.js/test/utils/blockchain_lifecycle.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {RPC} from './rpc';
|
||||
|
||||
export class BlockchainLifecycle {
|
||||
private rpc: RPC;
|
||||
private snapshotIdsStack: number[];
|
||||
constructor() {
|
||||
this.rpc = new RPC();
|
||||
this.snapshotIdsStack = [];
|
||||
}
|
||||
// TODO: In order to run these tests on an actual node, we should check if we are running against
|
||||
// TestRPC, if so, use snapshots, otherwise re-deploy contracts before every test
|
||||
public async startAsync(): Promise<void> {
|
||||
const snapshotId = await this.rpc.takeSnapshotAsync();
|
||||
this.snapshotIdsStack.push(snapshotId);
|
||||
}
|
||||
public async revertAsync(): Promise<void> {
|
||||
const snapshotId = this.snapshotIdsStack.pop() as number;
|
||||
const didRevert = await this.rpc.revertSnapshotAsync(snapshotId);
|
||||
if (!didRevert) {
|
||||
throw new Error(`Snapshot with id #${snapshotId} failed to revert`);
|
||||
}
|
||||
}
|
||||
public async mineABlock(): Promise<void> {
|
||||
await this.rpc.mineBlockAsync();
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,6 @@ export const constants = {
|
||||
RPC_HOST: 'localhost',
|
||||
RPC_PORT: 8545,
|
||||
TESTRPC_NETWORK_ID: 50,
|
||||
KOVAN_RPC_URL: 'https://kovan.0xproject.com',
|
||||
KOVAN_RPC_URL: 'https://kovan.infura.io',
|
||||
ROPSTEN_RPC_URL: 'https://ropsten.infura.io',
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {ZeroEx, Token, SignedOrder} from '../../src';
|
||||
import {orderFactory} from '../utils/order_factory';
|
||||
import {constants} from './constants';
|
||||
@@ -21,8 +21,8 @@ export class FillScenarios {
|
||||
}
|
||||
public async createFillableSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
||||
makerAddress: string, takerAddress: string,
|
||||
fillableAmount: BigNumber.BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber.BigNumber):
|
||||
fillableAmount: BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber):
|
||||
Promise<SignedOrder> {
|
||||
return this.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
@@ -31,10 +31,10 @@ export class FillScenarios {
|
||||
}
|
||||
public async createFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress: string, takerTokenAddress: string,
|
||||
makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
|
||||
makerFee: BigNumber, takerFee: BigNumber,
|
||||
makerAddress: string, takerAddress: string,
|
||||
fillableAmount: BigNumber.BigNumber,
|
||||
feeRecepient: string, expirationUnixTimestampSec?: BigNumber.BigNumber,
|
||||
fillableAmount: BigNumber,
|
||||
feeRecepient: string, expirationUnixTimestampSec?: BigNumber,
|
||||
): Promise<SignedOrder> {
|
||||
return this.createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerFee, takerFee, makerAddress, takerAddress,
|
||||
@@ -43,8 +43,8 @@ export class FillScenarios {
|
||||
}
|
||||
public async createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress: string, takerTokenAddress: string, makerAddress: string, takerAddress: string,
|
||||
makerFillableAmount: BigNumber.BigNumber, takerFillableAmount: BigNumber.BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||
makerFillableAmount: BigNumber, takerFillableAmount: BigNumber,
|
||||
expirationUnixTimestampSec?: BigNumber): Promise<SignedOrder> {
|
||||
const makerFee = new BigNumber(0);
|
||||
const takerFee = new BigNumber(0);
|
||||
const feeRecepient = constants.NULL_ADDRESS;
|
||||
@@ -54,23 +54,25 @@ export class FillScenarios {
|
||||
);
|
||||
}
|
||||
public async createPartiallyFilledSignedOrderAsync(makerTokenAddress: string, takerTokenAddress: string,
|
||||
takerAddress: string, fillableAmount: BigNumber.BigNumber,
|
||||
partialFillAmount: BigNumber.BigNumber) {
|
||||
takerAddress: string, fillableAmount: BigNumber,
|
||||
partialFillAmount: BigNumber) {
|
||||
const [makerAddress] = this.userAddresses;
|
||||
const signedOrder = await this.createAsymmetricFillableSignedOrderAsync(
|
||||
makerTokenAddress, takerTokenAddress, makerAddress, takerAddress,
|
||||
fillableAmount, fillableAmount,
|
||||
);
|
||||
const shouldThrowOnInsufficientBalanceOrAllowance = false;
|
||||
await this.zeroEx.exchange.fillOrderAsync(signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress);
|
||||
await this.zeroEx.exchange.fillOrderAsync(
|
||||
signedOrder, partialFillAmount, shouldThrowOnInsufficientBalanceOrAllowance, takerAddress,
|
||||
);
|
||||
return signedOrder;
|
||||
}
|
||||
private async createAsymmetricFillableSignedOrderWithFeesAsync(
|
||||
makerTokenAddress: string, takerTokenAddress: string,
|
||||
makerFee: BigNumber.BigNumber, takerFee: BigNumber.BigNumber,
|
||||
makerFee: BigNumber, takerFee: BigNumber,
|
||||
makerAddress: string, takerAddress: string,
|
||||
makerFillableAmount: BigNumber.BigNumber, takerFillableAmount: BigNumber.BigNumber,
|
||||
feeRecepient: string, expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||
makerFillableAmount: BigNumber, takerFillableAmount: BigNumber,
|
||||
feeRecepient: string, expirationUnixTimestampSec?: BigNumber): Promise<SignedOrder> {
|
||||
|
||||
await Promise.all([
|
||||
this.increaseBalanceAndAllowanceAsync(makerTokenAddress, makerAddress, makerFillableAmount),
|
||||
@@ -88,8 +90,8 @@ export class FillScenarios {
|
||||
return signedOrder;
|
||||
}
|
||||
private async increaseBalanceAndAllowanceAsync(
|
||||
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
|
||||
if (amount.isZero()) {
|
||||
tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
||||
if (amount.isZero() || address === ZeroEx.NULL_ADDRESS) {
|
||||
return; // noop
|
||||
}
|
||||
await Promise.all([
|
||||
@@ -98,11 +100,11 @@ export class FillScenarios {
|
||||
]);
|
||||
}
|
||||
private async increaseBalanceAsync(
|
||||
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
|
||||
tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
||||
await this.zeroEx.token.transferAsync(tokenAddress, this.coinbase, address, amount);
|
||||
}
|
||||
private async increaseAllowanceAsync(
|
||||
tokenAddress: string, address: string, amount: BigNumber.BigNumber): Promise<void> {
|
||||
tokenAddress: string, address: string, amount: BigNumber): Promise<void> {
|
||||
const oldMakerAllowance = await this.zeroEx.token.getProxyAllowanceAsync(tokenAddress, address);
|
||||
const newMakerAllowance = oldMakerAllowance.plus(amount);
|
||||
await this.zeroEx.token.setProxyAllowanceAsync(
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import BigNumber from 'bignumber.js';
|
||||
import {ZeroEx, SignedOrder} from '../../src';
|
||||
|
||||
export const orderFactory = {
|
||||
@@ -7,15 +7,15 @@ export const orderFactory = {
|
||||
zeroEx: ZeroEx,
|
||||
maker: string,
|
||||
taker: string,
|
||||
makerFee: BigNumber.BigNumber,
|
||||
takerFee: BigNumber.BigNumber,
|
||||
makerTokenAmount: BigNumber.BigNumber,
|
||||
makerFee: BigNumber,
|
||||
takerFee: BigNumber,
|
||||
makerTokenAmount: BigNumber,
|
||||
makerTokenAddress: string,
|
||||
takerTokenAmount: BigNumber.BigNumber,
|
||||
takerTokenAmount: BigNumber,
|
||||
takerTokenAddress: string,
|
||||
exchangeContractAddress: string,
|
||||
feeRecipient: string,
|
||||
expirationUnixTimestampSec?: BigNumber.BigNumber): Promise<SignedOrder> {
|
||||
expirationUnixTimestampSec?: BigNumber): Promise<SignedOrder> {
|
||||
const defaultExpirationUnixTimestampSec = new BigNumber(2524604400); // Close to infinite
|
||||
expirationUnixTimestampSec = _.isUndefined(expirationUnixTimestampSec) ?
|
||||
defaultExpirationUnixTimestampSec :
|
||||
14
packages/0x.js/test/utils/report_callback_errors.ts
Normal file
14
packages/0x.js/test/utils/report_callback_errors.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { DoneCallback } from '../../src/types';
|
||||
|
||||
export const reportCallbackErrors = (done: DoneCallback) => {
|
||||
return (f: (...args: any[]) => void) => {
|
||||
const wrapped = (...args: any[]) => {
|
||||
try {
|
||||
f(...args);
|
||||
} catch (err) {
|
||||
done(err);
|
||||
}
|
||||
};
|
||||
return wrapped;
|
||||
};
|
||||
};
|
||||
@@ -26,6 +26,12 @@ export class RPC {
|
||||
const didRevert = await this.sendAsync(payload);
|
||||
return didRevert;
|
||||
}
|
||||
public async mineBlockAsync(): Promise<void> {
|
||||
const method = 'evm_mine';
|
||||
const params: any[] = [];
|
||||
const payload = this.toPayload(method, params);
|
||||
await this.sendAsync(payload);
|
||||
}
|
||||
private toPayload(method: string, params: any[] = []): string {
|
||||
const payload = JSON.stringify({
|
||||
id: this.id,
|
||||
@@ -40,6 +46,9 @@ export class RPC {
|
||||
method: 'POST',
|
||||
uri: `http://${this.host}:${this.port}`,
|
||||
body: payload,
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
};
|
||||
const bodyString = await request(opts);
|
||||
const body = JSON.parse(bodyString);
|
||||
@@ -1,5 +1,5 @@
|
||||
import * as _ from 'lodash';
|
||||
import {Token, ZeroExError} from '../../src';
|
||||
import {Token, InternalZeroExError} from '../../src/types';
|
||||
|
||||
const PROTOCOL_TOKEN_SYMBOL = 'ZRX';
|
||||
|
||||
@@ -11,7 +11,7 @@ export class TokenUtils {
|
||||
public getProtocolTokenOrThrow(): Token {
|
||||
const zrxToken = _.find(this.tokens, {symbol: PROTOCOL_TOKEN_SYMBOL});
|
||||
if (_.isUndefined(zrxToken)) {
|
||||
throw new Error(ZeroExError.ZrxNotInTokenRegistry);
|
||||
throw new Error(InternalZeroExError.ZrxNotInTokenRegistry);
|
||||
}
|
||||
return zrxToken;
|
||||
}
|
||||
@@ -13,10 +13,10 @@
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./test/**/*",
|
||||
"./node_modules/types-bn/index.d.ts",
|
||||
"./node_modules/types-ethereumjs-util/index.d.ts",
|
||||
"./node_modules/web3-typescript-typings/index.d.ts",
|
||||
"./node_modules/chai-typescript-typings/index.d.ts",
|
||||
"./node_modules/chai-as-promised-typescript-typings/index.d.ts"
|
||||
"../../node_modules/types-bn/index.d.ts",
|
||||
"../../node_modules/types-ethereumjs-util/index.d.ts",
|
||||
"../../node_modules/web3-typescript-typings/index.d.ts",
|
||||
"../../node_modules/chai-typescript-typings/index.d.ts",
|
||||
"../../node_modules/chai-as-promised-typescript-typings/index.d.ts"
|
||||
]
|
||||
}
|
||||
5
packages/0x.js/tslint.json
Normal file
5
packages/0x.js/tslint.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@0xproject/tslint-config"
|
||||
]
|
||||
}
|
||||
6
packages/assert/CHANGELOG.md
Normal file
6
packages/assert/CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# CHANGELOG
|
||||
|
||||
v0.0.4 - _Nov. 14, 2017_
|
||||
------------------------
|
||||
* Re-publish Assert previously published under NPM package @0xproject/0x-assert
|
||||
* Added assertion isValidBaseUnitAmount which checks both that the value is a valid bigNumber and that it does not contain decimals.
|
||||
10
packages/assert/README.md
Normal file
10
packages/assert/README.md
Normal file
@@ -0,0 +1,10 @@
|
||||
assert
|
||||
------
|
||||
|
||||
Standard type and schema assertions to be used across all 0x projects and packages
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
npm install @0xproject/assert --save
|
||||
```
|
||||
46
packages/assert/package.json
Normal file
46
packages/assert/package.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@0xproject/assert",
|
||||
"version": "0.0.5",
|
||||
"description": "Provides a standard way of performing type and schema validation across 0x projects",
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"lint": "tslint src/**/*.ts test/**/*.ts",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js",
|
||||
"prepublishOnly": "run-p build",
|
||||
"test": "run-s clean build run_mocha",
|
||||
"test:circleci": "yarn test"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x.js/packages/assert/README.md",
|
||||
"devDependencies": {
|
||||
"@0xproject/tslint-config": "^0.1.1",
|
||||
"@types/lodash": "^4.14.78",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/valid-url": "^1.0.2",
|
||||
"chai": "^4.0.1",
|
||||
"chai-typescript-typings": "^0.0.1",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"mocha": "^4.0.1",
|
||||
"npm-run-all": "^4.1.1",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "5.8.0",
|
||||
"typescript": "^2.4.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@0xproject/json-schemas": "^0.6.8",
|
||||
"bignumber.js": "~4.1.0",
|
||||
"ethereum-address": "^0.0.4",
|
||||
"lodash": "^4.17.4",
|
||||
"valid-url": "^1.0.9"
|
||||
}
|
||||
}
|
||||
14
packages/assert/scripts/postpublish.js
Normal file
14
packages/assert/scripts/postpublish.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const postpublish_utils = require('../../../scripts/postpublish_utils');
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const subPackageName = packageJSON.name;
|
||||
|
||||
postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
|
||||
.then(function(result) {
|
||||
const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
|
||||
const assets = [];
|
||||
return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
|
||||
})
|
||||
.catch (function(err) {
|
||||
throw err;
|
||||
});
|
||||
5
packages/assert/src/globals.d.ts
vendored
Normal file
5
packages/assert/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
declare module 'dirty-chai';
|
||||
|
||||
declare module 'ethereum-address' {
|
||||
const isAddress: (arg: any) => boolean;
|
||||
}
|
||||
@@ -1,31 +1,43 @@
|
||||
import BigNumber from 'bignumber.js';
|
||||
import * as ethereum_address from 'ethereum-address';
|
||||
import * as _ from 'lodash';
|
||||
import * as BigNumber from 'bignumber.js';
|
||||
import * as Web3 from 'web3';
|
||||
import {Web3Wrapper} from '../web3_wrapper';
|
||||
import {SchemaValidator} from './schema_validator';
|
||||
import * as validUrl from 'valid-url';
|
||||
import {
|
||||
SchemaValidator,
|
||||
Schema,
|
||||
} from '@0xproject/json-schemas';
|
||||
|
||||
const HEX_REGEX = /^0x[0-9A-F]*$/i;
|
||||
|
||||
export const assert = {
|
||||
isBigNumber(variableName: string, value: BigNumber.BigNumber): void {
|
||||
const isBigNumber = _.isObject(value) && value.isBigNumber;
|
||||
isBigNumber(variableName: string, value: BigNumber): void {
|
||||
const isBigNumber = _.isObject(value) && (value as any).isBigNumber;
|
||||
this.assert(isBigNumber, this.typeAssertionMessage(variableName, 'BigNumber', value));
|
||||
},
|
||||
isValidBaseUnitAmount(variableName: string, value: BigNumber) {
|
||||
assert.isBigNumber(variableName, value);
|
||||
const hasDecimals = value.decimalPlaces() !== 0;
|
||||
this.assert(
|
||||
!hasDecimals, `${variableName} should be in baseUnits (no decimals), found value: ${value.toNumber()}`,
|
||||
);
|
||||
},
|
||||
isUndefined(value: any, variableName?: string): void {
|
||||
this.assert(_.isUndefined(value), this.typeAssertionMessage(variableName, 'undefined', value));
|
||||
},
|
||||
isString(variableName: string, value: string): void {
|
||||
this.assert(_.isString(value), this.typeAssertionMessage(variableName, 'string', value));
|
||||
},
|
||||
isFunction(variableName: string, value: any): void {
|
||||
this.assert(_.isFunction(value), this.typeAssertionMessage(variableName, 'function', value));
|
||||
},
|
||||
isHexString(variableName: string, value: string): void {
|
||||
this.assert(_.isString(value) && HEX_REGEX.test(value),
|
||||
this.typeAssertionMessage(variableName, 'HexString', value));
|
||||
},
|
||||
isETHAddressHex(variableName: string, value: string): void {
|
||||
const web3 = new Web3();
|
||||
this.assert(web3.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
|
||||
this.assert(ethereum_address.isAddress(value), this.typeAssertionMessage(variableName, 'ETHAddressHex', value));
|
||||
this.assert(
|
||||
web3.isAddress(value) && !web3.isChecksumAddress(value),
|
||||
ethereum_address.isAddress(value) && value.toLowerCase() === value,
|
||||
`Checksummed addresses are not supported. Convert ${variableName} to lower case before passing`,
|
||||
);
|
||||
},
|
||||
@@ -40,18 +52,6 @@ export const assert = {
|
||||
`Expected ${variableName} to be one of: ${enumValuesAsString}, encountered: ${value}`,
|
||||
);
|
||||
},
|
||||
async isSenderAddressAsync(variableName: string, senderAddressHex: string,
|
||||
web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
assert.isETHAddressHex(variableName, senderAddressHex);
|
||||
const isSenderAddressAvailable = await web3Wrapper.isSenderAddressAvailableAsync(senderAddressHex);
|
||||
assert.assert(isSenderAddressAvailable,
|
||||
`Specified ${variableName} ${senderAddressHex} isn't available through the supplied web3 provider`,
|
||||
);
|
||||
},
|
||||
async isUserAddressAvailableAsync(web3Wrapper: Web3Wrapper): Promise<void> {
|
||||
const availableAddresses = await web3Wrapper.getAvailableAddressesAsync();
|
||||
this.assert(!_.isEmpty(availableAddresses), 'No addresses were available on the provided web3 provider');
|
||||
},
|
||||
hasAtMostOneUniqueValue(value: any[], errMsg: string): void {
|
||||
this.assert(_.uniq(value).length <= 1, errMsg);
|
||||
},
|
||||
@@ -61,6 +61,10 @@ export const assert = {
|
||||
isBoolean(variableName: string, value: boolean): void {
|
||||
this.assert(_.isBoolean(value), this.typeAssertionMessage(variableName, 'boolean', value));
|
||||
},
|
||||
isWeb3Provider(variableName: string, value: any): void {
|
||||
const isWeb3Provider = _.isFunction((value as any).send) || _.isFunction((value as any).sendAsync);
|
||||
this.assert(isWeb3Provider, this.typeAssertionMessage(variableName, 'Web3.Provider', value));
|
||||
},
|
||||
doesConformToSchema(variableName: string, value: any, schema: Schema): void {
|
||||
const schemaValidator = new SchemaValidator();
|
||||
const validationResult = schemaValidator.validate(value, schema);
|
||||
@@ -70,6 +74,14 @@ Encountered: ${JSON.stringify(value, null, '\t')}
|
||||
Validation errors: ${validationResult.errors.join(', ')}`;
|
||||
this.assert(!hasValidationErrors, msg);
|
||||
},
|
||||
isHttpUrl(variableName: string, value: any): void {
|
||||
const isValidUrl = validUrl.isWebUri(value);
|
||||
this.assert(isValidUrl, this.typeAssertionMessage(variableName, 'http url', value));
|
||||
},
|
||||
isUri(variableName: string, value: any): void {
|
||||
const isValidUri = validUrl.isUri(value);
|
||||
this.assert(isValidUri, this.typeAssertionMessage(variableName, 'uri', value));
|
||||
},
|
||||
assert(condition: boolean, message: string): void {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
338
packages/assert/test/assert_test.ts
Normal file
338
packages/assert/test/assert_test.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import 'mocha';
|
||||
import * as dirtyChai from 'dirty-chai';
|
||||
import * as chai from 'chai';
|
||||
import {BigNumber} from 'bignumber.js';
|
||||
import {schemas} from '@0xproject/json-schemas';
|
||||
import {assert} from '../src/index';
|
||||
|
||||
chai.config.includeStack = true;
|
||||
chai.use(dirtyChai);
|
||||
const expect = chai.expect;
|
||||
|
||||
describe('Assertions', () => {
|
||||
const variableName = 'variable';
|
||||
describe('#isBigNumber', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
new BigNumber(23),
|
||||
new BigNumber('45'),
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
'test',
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isBigNumber.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isUndefined', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
undefined,
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isUndefined.bind(assert, input, variableName)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
'test',
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isUndefined.bind(assert, input, variableName)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isString', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
'hello',
|
||||
'goodbye',
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isString.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isFunction', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
BigNumber,
|
||||
assert.isString.bind(this),
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isFunction.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isHexString', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
'0x61a3ed31B43c8780e905a260a35faefEc527be7516aa11c0256729b5b351bc33',
|
||||
'0x40349190569279751135161d22529dc25add4f6069af05be04cacbda2ace2254',
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
'0x61a3ed31B43c8780e905a260a35faYfEc527be7516aa11c0256729b5b351bc33',
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isHexString.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isETHAddressHex', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
'0x0000000000000000000000000000000000000000',
|
||||
'0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd',
|
||||
'0x12459c951127e0c374ff9105dda097662a027093',
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
'0x6FFFd0ae3f7d88c9b4925323f54c6e4b2918c5fd',
|
||||
'0x6FFFd0ae3f7d88c9b4925323f54c6e4',
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.isETHAddressHex.bind(assert, variableName, input)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#doesBelongToStringEnum', () => {
|
||||
enum TestEnums {
|
||||
Test1 = 'Test1',
|
||||
Test2 = 'Test2',
|
||||
}
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
TestEnums.Test1,
|
||||
TestEnums.Test2,
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.doesBelongToStringEnum.bind(assert, variableName, input, TestEnums)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#hasAtMostOneUniqueValue', () => {
|
||||
const errorMsg = 'more than one unique value';
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
['hello'],
|
||||
['goodbye', 'goodbye', 'goodbye'],
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
['hello', 'goodbye'],
|
||||
['goodbye', 42, false, false],
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.hasAtMostOneUniqueValue.bind(assert, input, errorMsg)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#isNumber', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
42,
|
||||
0.00,
|
||||
21e+42,
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
false,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isNumber.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isBoolean', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
true,
|
||||
false,
|
||||
];
|
||||
validInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.not.throw());
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input => expect(assert.isBoolean.bind(assert, variableName, input)).to.throw());
|
||||
});
|
||||
});
|
||||
describe('#isWeb3Provider', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
{ send: () => 45 },
|
||||
{ sendAsync: () => 45 },
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.isWeb3Provider.bind(assert, variableName, input)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#doesConformToSchema', () => {
|
||||
const schema = schemas.addressSchema;
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
'0x6fffd0ae3f7d88c9b4925323f54c6e4b2918c5fd',
|
||||
'0x12459c951127e0c374ff9105dda097662a027093',
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.doesConformToSchema.bind(assert, variableName, input, schema)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#isHttpUrl', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
'http://www.google.com',
|
||||
'https://api.example-relayer.net',
|
||||
'https://api.radarrelay.com/0x/v0/',
|
||||
'https://zeroex.beta.radarrelay.com:8000/0x/v0/',
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.isHttpUrl.bind(assert, variableName, input)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
'ws://www.api.example-relayer.net',
|
||||
'www.google.com',
|
||||
'api.example-relayer.net',
|
||||
'user:password@api.example-relayer.net',
|
||||
'//api.example-relayer.net',
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.isHttpUrl.bind(assert, variableName, input)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#isUri', () => {
|
||||
it('should not throw for valid input', () => {
|
||||
const validInputs = [
|
||||
'http://www.google.com',
|
||||
'https://api.example-relayer.net',
|
||||
'https://api.radarrelay.com/0x/v0/',
|
||||
'https://zeroex.beta.radarrelay.com:8000/0x/v0/',
|
||||
'ws://www.api.example-relayer.net',
|
||||
'wss://www.api.example-relayer.net',
|
||||
'user:password@api.example-relayer.net',
|
||||
];
|
||||
validInputs.forEach(input =>
|
||||
expect(assert.isUri.bind(assert, variableName, input)).to.not.throw(),
|
||||
);
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
const invalidInputs = [
|
||||
42,
|
||||
{ random: 'test' },
|
||||
undefined,
|
||||
new BigNumber(45),
|
||||
'www.google.com',
|
||||
'api.example-relayer.net',
|
||||
'//api.example-relayer.net',
|
||||
];
|
||||
invalidInputs.forEach(input =>
|
||||
expect(assert.isUri.bind(assert, variableName, input)).to.throw(),
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#assert', () => {
|
||||
const assertMessage = 'assert not satisfied';
|
||||
it('should not throw for valid input', () => {
|
||||
expect(assert.assert.bind(assert, true, assertMessage)).to.not.throw();
|
||||
});
|
||||
it('should throw for invalid input', () => {
|
||||
expect(assert.assert.bind(assert, false, assertMessage)).to.throw();
|
||||
});
|
||||
});
|
||||
describe('#typeAssertionMessage', () => {
|
||||
it('should render correct message', () => {
|
||||
expect(assert.typeAssertionMessage('variable', 'string', 'number'))
|
||||
.to.equal(`Expected variable to be of type string, encountered: number`);
|
||||
});
|
||||
});
|
||||
});
|
||||
18
packages/assert/tsconfig.json
Normal file
18
packages/assert/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [ "es2017", "dom"],
|
||||
"outDir": "lib",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*",
|
||||
"./test/**/*",
|
||||
"../../node_modules/chai-typescript-typings/index.d.ts",
|
||||
"../../node_modules/web3-typescript-typings/index.d.ts"
|
||||
]
|
||||
}
|
||||
5
packages/assert/tslint.json
Normal file
5
packages/assert/tslint.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@0xproject/tslint-config"
|
||||
]
|
||||
}
|
||||
4
packages/connect/CHANGELOG.md
Normal file
4
packages/connect/CHANGELOG.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# CHANGELOG
|
||||
|
||||
v0.0.0 - _Nov. 15, 2017_
|
||||
------------------------
|
||||
1
packages/connect/README.md
Normal file
1
packages/connect/README.md
Normal file
@@ -0,0 +1 @@
|
||||
This repository contains a Javascript library that makes it easy to interact with Relayers that conform to the [Standard Relayer API](https://github.com/0xProject/standard-relayer-api)
|
||||
68
packages/connect/package.json
Normal file
68
packages/connect/package.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "@0xproject/connect",
|
||||
"version": "0.0.1",
|
||||
"description": "A javascript library for interacting with the standard relayer api",
|
||||
"keywords": [
|
||||
"0x-connect",
|
||||
"0xproject",
|
||||
"ethereum",
|
||||
"tokens",
|
||||
"exchange"
|
||||
],
|
||||
"main": "lib/src/index.js",
|
||||
"types": "lib/src/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "shx rm -rf _bundles lib test_temp",
|
||||
"copy_test_fixtures": "copyfiles -u 2 './test/fixtures/**/*.json' ./lib/test/fixtures",
|
||||
"lint": "tslint src/**/*.ts test/**/*.ts",
|
||||
"prepublishOnly": "run-p build",
|
||||
"run_mocha": "mocha lib/test/**/*_test.js",
|
||||
"test": "run-s clean build copy_test_fixtures run_mocha",
|
||||
"test:circleci": "yarn test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/0xProject/0x.js.git"
|
||||
},
|
||||
"author": "Brandon Millman",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/0xProject/0x.js/issues"
|
||||
},
|
||||
"homepage": "https://github.com/0xProject/0x.js/packages/connect/README.md",
|
||||
"dependencies": {
|
||||
"0x.js": "^0.26.0",
|
||||
"@0xproject/assert": "^0.0.5",
|
||||
"@0xproject/json-schemas": "^0.6.8",
|
||||
"bignumber.js": "~4.1.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"lodash": "^4.17.4",
|
||||
"query-string": "^5.0.1",
|
||||
"websocket": "^1.0.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0xproject/tslint-config": "^0.1.1",
|
||||
"@types/fetch-mock": "^5.12.1",
|
||||
"@types/lodash": "^4.14.77",
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/query-string": "^5.0.1",
|
||||
"@types/websocket": "^0.0.34",
|
||||
"chai": "^4.0.1",
|
||||
"chai-as-promised": "^7.1.0",
|
||||
"chai-as-promised-typescript-typings": "0.0.3",
|
||||
"chai-typescript-typings": "^0.0.1",
|
||||
"copyfiles": "^1.2.0",
|
||||
"dirty-chai": "^2.0.1",
|
||||
"fetch-mock": "^5.13.1",
|
||||
"mocha": "^4.0.0",
|
||||
"npm-run-all": "^4.0.2",
|
||||
"shx": "^0.2.2",
|
||||
"tslint": "5.8.0",
|
||||
"typescript": "~2.6.1",
|
||||
"web3-typescript-typings": "^0.7.1"
|
||||
}
|
||||
}
|
||||
14
packages/connect/scripts/postpublish.js
Normal file
14
packages/connect/scripts/postpublish.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const postpublish_utils = require('../../../scripts/postpublish_utils');
|
||||
const packageJSON = require('../package.json');
|
||||
|
||||
const subPackageName = packageJSON.name;
|
||||
|
||||
postpublish_utils.getLatestTagAndVersionAsync(subPackageName)
|
||||
.then(function(result) {
|
||||
const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
|
||||
const assets = [];
|
||||
return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
|
||||
})
|
||||
.catch (function(err) {
|
||||
throw err;
|
||||
});
|
||||
6
packages/connect/src/globals.d.ts
vendored
Normal file
6
packages/connect/src/globals.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
declare module 'dirty-chai';
|
||||
|
||||
declare module '*.json' {
|
||||
const value: any;
|
||||
export default value;
|
||||
}
|
||||
171
packages/connect/src/http_client.ts
Normal file
171
packages/connect/src/http_client.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import 'isomorphic-fetch';
|
||||
import * as _ from 'lodash';
|
||||
import {BigNumber} from 'bignumber.js';
|
||||
import * as queryString from 'query-string';
|
||||
import {assert} from '@0xproject/assert';
|
||||
import {schemas} from '@0xproject/json-schemas';
|
||||
import {SignedOrder} from '0x.js';
|
||||
import {
|
||||
Client,
|
||||
FeesRequest,
|
||||
FeesResponse,
|
||||
OrderbookRequest,
|
||||
OrderbookResponse,
|
||||
OrdersRequest,
|
||||
TokenPairsItem,
|
||||
TokenPairsRequest,
|
||||
} from './types';
|
||||
import {schemas as clientSchemas} from './schemas/schemas';
|
||||
import {typeConverters} from './utils/type_converters';
|
||||
|
||||
interface RequestOptions {
|
||||
params?: object;
|
||||
payload?: object;
|
||||
}
|
||||
|
||||
enum RequestType {
|
||||
Get = 'GET',
|
||||
Post = 'POST',
|
||||
}
|
||||
|
||||
/**
|
||||
* This class includes all the functionality related to interacting with a set of HTTP endpoints
|
||||
* that implement the standard relayer API v0
|
||||
*/
|
||||
export class HttpClient implements Client {
|
||||
private apiEndpointUrl: string;
|
||||
/**
|
||||
* Instantiates a new HttpClient instance
|
||||
* @param url The base url for making API calls
|
||||
* @return An instance of HttpClient
|
||||
*/
|
||||
constructor(url: string) {
|
||||
assert.isHttpUrl('url', url);
|
||||
this.apiEndpointUrl = url;
|
||||
}
|
||||
/**
|
||||
* Retrieve token pair info from the API
|
||||
* @param request A TokenPairsRequest instance describing specific token information
|
||||
* to retrieve
|
||||
* @return The resulting TokenPairsItems that match the request
|
||||
*/
|
||||
public async getTokenPairsAsync(request?: TokenPairsRequest): Promise<TokenPairsItem[]> {
|
||||
if (!_.isUndefined(request)) {
|
||||
assert.doesConformToSchema('request', request, clientSchemas.relayerTokenPairsRequestSchema);
|
||||
}
|
||||
const requestOpts = {
|
||||
params: request,
|
||||
};
|
||||
const tokenPairs = await this._requestAsync('/token_pairs', RequestType.Get, requestOpts);
|
||||
assert.doesConformToSchema(
|
||||
'tokenPairs', tokenPairs, schemas.relayerApiTokenPairsResponseSchema);
|
||||
_.each(tokenPairs, (tokenPair: object) => {
|
||||
typeConverters.convertStringsFieldsToBigNumbers(tokenPair, [
|
||||
'tokenA.minAmount',
|
||||
'tokenA.maxAmount',
|
||||
'tokenB.minAmount',
|
||||
'tokenB.maxAmount',
|
||||
]);
|
||||
});
|
||||
return tokenPairs;
|
||||
}
|
||||
/**
|
||||
* Retrieve orders from the API
|
||||
* @param request An OrdersRequest instance describing specific orders to retrieve
|
||||
* @return The resulting SignedOrders that match the request
|
||||
*/
|
||||
public async getOrdersAsync(request?: OrdersRequest): Promise<SignedOrder[]> {
|
||||
if (!_.isUndefined(request)) {
|
||||
assert.doesConformToSchema('request', request, clientSchemas.relayerOrdersRequestSchema);
|
||||
}
|
||||
const requestOpts = {
|
||||
params: request,
|
||||
};
|
||||
const orders = await this._requestAsync(`/orders`, RequestType.Get, requestOpts);
|
||||
assert.doesConformToSchema('orders', orders, schemas.signedOrdersSchema);
|
||||
_.each(orders, (order: object) => typeConverters.convertOrderStringFieldsToBigNumber(order));
|
||||
return orders;
|
||||
}
|
||||
/**
|
||||
* Retrieve a specific order from the API
|
||||
* @param orderHash An orderHash generated from the desired order
|
||||
* @return The SignedOrder that matches the supplied orderHash
|
||||
*/
|
||||
public async getOrderAsync(orderHash: string): Promise<SignedOrder> {
|
||||
assert.doesConformToSchema('orderHash', orderHash, schemas.orderHashSchema);
|
||||
const order = await this._requestAsync(`/order/${orderHash}`, RequestType.Get);
|
||||
assert.doesConformToSchema('order', order, schemas.signedOrderSchema);
|
||||
typeConverters.convertOrderStringFieldsToBigNumber(order);
|
||||
return order;
|
||||
}
|
||||
/**
|
||||
* Retrieve an orderbook from the API
|
||||
* @param request An OrderbookRequest instance describing the specific orderbook to retrieve
|
||||
* @return The resulting OrderbookResponse that matches the request
|
||||
*/
|
||||
public async getOrderbookAsync(request: OrderbookRequest): Promise<OrderbookResponse> {
|
||||
assert.doesConformToSchema('request', request, clientSchemas.relayerOrderBookRequestSchema);
|
||||
const requestOpts = {
|
||||
params: request,
|
||||
};
|
||||
const orderBook = await this._requestAsync('/orderbook', RequestType.Get, requestOpts);
|
||||
assert.doesConformToSchema('orderBook', orderBook, schemas.relayerApiOrderBookResponseSchema);
|
||||
typeConverters.convertOrderbookStringFieldsToBigNumber(orderBook);
|
||||
return orderBook;
|
||||
}
|
||||
/**
|
||||
* Retrieve fee information from the API
|
||||
* @param request A FeesRequest instance describing the specific fees to retrieve
|
||||
* @return The resulting FeesResponse that matches the request
|
||||
*/
|
||||
public async getFeesAsync(request: FeesRequest): Promise<FeesResponse> {
|
||||
assert.doesConformToSchema('request', request, schemas.relayerApiFeesPayloadSchema);
|
||||
typeConverters.convertBigNumberFieldsToStrings(request, [
|
||||
'makerTokenAmount',
|
||||
'takerTokenAmount',
|
||||
'expirationUnixTimestampSec',
|
||||
'salt',
|
||||
]);
|
||||
const requestOpts = {
|
||||
payload: request,
|
||||
};
|
||||
const fees = await this._requestAsync('/fees', RequestType.Post, requestOpts);
|
||||
assert.doesConformToSchema('fees', fees, schemas.relayerApiFeesResponseSchema);
|
||||
typeConverters.convertStringsFieldsToBigNumbers(fees, ['makerFee', 'takerFee']);
|
||||
return fees;
|
||||
}
|
||||
/**
|
||||
* Submit a signed order to the API
|
||||
* @param signedOrder A SignedOrder instance to submit
|
||||
*/
|
||||
public async submitOrderAsync(signedOrder: SignedOrder): Promise<void> {
|
||||
assert.doesConformToSchema('signedOrder', signedOrder, schemas.signedOrderSchema);
|
||||
const requestOpts = {
|
||||
payload: signedOrder,
|
||||
};
|
||||
await this._requestAsync('/order', RequestType.Post, requestOpts);
|
||||
}
|
||||
private async _requestAsync(path: string, requestType: RequestType, requestOptions?: RequestOptions): Promise<any> {
|
||||
const params = _.get(requestOptions, 'params');
|
||||
const payload = _.get(requestOptions, 'payload');
|
||||
let query = '';
|
||||
if (!_.isUndefined(params) && !_.isEmpty(params)) {
|
||||
const stringifiedParams = queryString.stringify(params);
|
||||
query = `?${stringifiedParams}`;
|
||||
}
|
||||
const url = `${this.apiEndpointUrl}/v0${path}${query}`;
|
||||
const headers = new Headers({
|
||||
'content-type': 'application/json',
|
||||
});
|
||||
const response = await fetch(url, {
|
||||
method: requestType,
|
||||
body: payload,
|
||||
headers,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw Error(response.statusText);
|
||||
}
|
||||
const json = await response.json();
|
||||
return json;
|
||||
}
|
||||
}
|
||||
15
packages/connect/src/index.ts
Normal file
15
packages/connect/src/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export {HttpClient} from './http_client';
|
||||
export {WebSocketOrderbookChannel} from './ws_orderbook_channel';
|
||||
export {
|
||||
Client,
|
||||
FeesRequest,
|
||||
FeesResponse,
|
||||
OrderbookChannel,
|
||||
OrderbookChannelHandler,
|
||||
OrderbookChannelSubscriptionOpts,
|
||||
OrderbookRequest,
|
||||
OrderbookResponse,
|
||||
OrdersRequest,
|
||||
TokenPairsItem,
|
||||
TokenPairsRequest,
|
||||
} from './types';
|
||||
@@ -0,0 +1,8 @@
|
||||
export const relayerOrderBookRequestSchema = {
|
||||
id: '/RelayerOrderBookRequest',
|
||||
type: 'object',
|
||||
properties: {
|
||||
baseTokenAddress: {$ref: '/Address'},
|
||||
quoteTokenAddress: {$ref: '/Address'},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export const relayerOrderBookRequestSchema = {
|
||||
id: '/RelayerOrderBookRequest',
|
||||
type: 'object',
|
||||
properties: {
|
||||
baseTokenAddress: {$ref: '/Address'},
|
||||
quoteTokenAddress: {$ref: '/Address'},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
export const relayerOrdersRequestSchema = {
|
||||
id: '/RelayerOrdersRequest',
|
||||
type: 'object',
|
||||
properties: {
|
||||
exchangeContractAddress: {$ref: '/Address'},
|
||||
tokenAddress: {$ref: '/Address'},
|
||||
makerTokenAddress: {$ref: '/Address'},
|
||||
takerTokenAddress: {$ref: '/Address'},
|
||||
tokenA: {$ref: '/Address'},
|
||||
tokenB: {$ref: '/Address'},
|
||||
maker: {$ref: '/Address'},
|
||||
taker: {$ref: '/Address'},
|
||||
trader: {$ref: '/Address'},
|
||||
feeRecipient: {$ref: '/Address'},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,8 @@
|
||||
export const relayerTokenPairsRequestSchema = {
|
||||
id: '/RelayerTokenPairsRequest',
|
||||
type: 'object',
|
||||
properties: {
|
||||
tokenA: {$ref: '/Address'},
|
||||
tokenB: {$ref: '/Address'},
|
||||
},
|
||||
};
|
||||
15
packages/connect/src/schemas/schemas.ts
Normal file
15
packages/connect/src/schemas/schemas.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
relayerOrderBookRequestSchema,
|
||||
} from './relayer_orderbook_request_schema';
|
||||
import {
|
||||
relayerOrdersRequestSchema,
|
||||
} from './relayer_orders_request_schema';
|
||||
import {
|
||||
relayerTokenPairsRequestSchema,
|
||||
} from './relayer_token_pairs_request_schema';
|
||||
|
||||
export const schemas = {
|
||||
relayerOrderBookRequestSchema,
|
||||
relayerOrdersRequestSchema,
|
||||
relayerTokenPairsRequestSchema,
|
||||
};
|
||||
120
packages/connect/src/types.ts
Normal file
120
packages/connect/src/types.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import {SignedOrder} from '0x.js';
|
||||
import {BigNumber} from 'bignumber.js';
|
||||
|
||||
export interface Client {
|
||||
getTokenPairsAsync: (request?: TokenPairsRequest) => Promise<TokenPairsItem[]>;
|
||||
getOrdersAsync: (request?: OrdersRequest) => Promise<SignedOrder[]>;
|
||||
getOrderAsync: (orderHash: string) => Promise<SignedOrder>;
|
||||
getOrderbookAsync: (request: OrderbookRequest) => Promise<OrderbookResponse>;
|
||||
getFeesAsync: (request: FeesRequest) => Promise<FeesResponse>;
|
||||
submitOrderAsync: (signedOrder: SignedOrder) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface OrderbookChannel {
|
||||
subscribe: (subscriptionOpts: OrderbookChannelSubscriptionOpts, handler: OrderbookChannelHandler) => void;
|
||||
close: () => void;
|
||||
}
|
||||
|
||||
export interface OrderbookChannelHandler {
|
||||
onSnapshot: (channel: OrderbookChannel, snapshot: OrderbookResponse) => void;
|
||||
onUpdate: (channel: OrderbookChannel, order: SignedOrder) => void;
|
||||
onError: (channel: OrderbookChannel, err: Error) => void;
|
||||
onClose: (channel: OrderbookChannel) => void;
|
||||
}
|
||||
|
||||
export type OrderbookChannelMessage =
|
||||
SnapshotOrderbookChannelMessage |
|
||||
UpdateOrderbookChannelMessage |
|
||||
UnknownOrderbookChannelMessage;
|
||||
|
||||
export enum OrderbookChannelMessageTypes {
|
||||
Snapshot = 'snapshot',
|
||||
Update = 'update',
|
||||
Unknown = 'unknown',
|
||||
}
|
||||
|
||||
export interface SnapshotOrderbookChannelMessage {
|
||||
type: OrderbookChannelMessageTypes.Snapshot;
|
||||
payload: OrderbookResponse;
|
||||
}
|
||||
|
||||
export interface UpdateOrderbookChannelMessage {
|
||||
type: OrderbookChannelMessageTypes.Update;
|
||||
payload: SignedOrder;
|
||||
}
|
||||
|
||||
export interface UnknownOrderbookChannelMessage {
|
||||
type: OrderbookChannelMessageTypes.Unknown;
|
||||
payload: undefined;
|
||||
}
|
||||
|
||||
/*
|
||||
* baseTokenAddress: The address of token designated as the baseToken in the currency pair calculation of price
|
||||
* quoteTokenAddress: The address of token designated as the quoteToken in the currency pair calculation of price
|
||||
* snapshot: If true, a snapshot of the orderbook will be sent before the updates to the orderbook
|
||||
* limit: Maximum number of bids and asks in orderbook snapshot
|
||||
*/
|
||||
export interface OrderbookChannelSubscriptionOpts {
|
||||
baseTokenAddress: string;
|
||||
quoteTokenAddress: string;
|
||||
snapshot: boolean;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
export interface TokenPairsRequest {
|
||||
tokenA?: string;
|
||||
tokenB?: string;
|
||||
}
|
||||
|
||||
export interface TokenPairsItem {
|
||||
tokenA: TokenTradeInfo;
|
||||
tokenB: TokenTradeInfo;
|
||||
}
|
||||
|
||||
export interface TokenTradeInfo {
|
||||
address: string;
|
||||
minAmount: BigNumber;
|
||||
maxAmount: BigNumber;
|
||||
precision: number;
|
||||
}
|
||||
|
||||
export interface OrdersRequest {
|
||||
exchangeContractAddress?: string;
|
||||
tokenAddress?: string;
|
||||
makerTokenAddress?: string;
|
||||
takerTokenAddress?: string;
|
||||
tokenA?: string;
|
||||
tokenB?: string;
|
||||
maker?: string;
|
||||
taker?: string;
|
||||
trader?: string;
|
||||
feeRecipient?: string;
|
||||
}
|
||||
|
||||
export interface OrderbookRequest {
|
||||
baseTokenAddress: string;
|
||||
quoteTokenAddress: string;
|
||||
}
|
||||
|
||||
export interface OrderbookResponse {
|
||||
bids: SignedOrder[];
|
||||
asks: SignedOrder[];
|
||||
}
|
||||
|
||||
export interface FeesRequest {
|
||||
exchangeContractAddress: string;
|
||||
maker: string;
|
||||
taker: string;
|
||||
makerTokenAddress: string;
|
||||
takerTokenAddress: string;
|
||||
makerTokenAmount: BigNumber;
|
||||
takerTokenAmount: BigNumber;
|
||||
expirationUnixTimestampSec: BigNumber;
|
||||
salt: BigNumber;
|
||||
}
|
||||
|
||||
export interface FeesResponse {
|
||||
feeRecipient: string;
|
||||
makerFee: BigNumber;
|
||||
takerFee: BigNumber;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import * as _ from 'lodash';
|
||||
import {SignedOrder} from '0x.js';
|
||||
import {assert} from '@0xproject/assert';
|
||||
import {schemas} from '@0xproject/json-schemas';
|
||||
import {
|
||||
OrderbookChannelMessage,
|
||||
OrderbookChannelMessageTypes,
|
||||
} from '../types';
|
||||
import {typeConverters} from './type_converters';
|
||||
|
||||
export const orderbookChannelMessageParsers = {
|
||||
parser(utf8Data: string): OrderbookChannelMessage {
|
||||
const messageObj = JSON.parse(utf8Data);
|
||||
const type: string = _.get(messageObj, 'type');
|
||||
assert.assert(!_.isUndefined(type), `Message is missing a type parameter: ${utf8Data}`);
|
||||
switch (type) {
|
||||
case (OrderbookChannelMessageTypes.Snapshot): {
|
||||
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelSnapshotSchema);
|
||||
const orderbook = messageObj.payload;
|
||||
typeConverters.convertOrderbookStringFieldsToBigNumber(orderbook);
|
||||
return {
|
||||
type,
|
||||
payload: orderbook,
|
||||
};
|
||||
}
|
||||
case (OrderbookChannelMessageTypes.Update): {
|
||||
assert.doesConformToSchema('message', messageObj, schemas.relayerApiOrderbookChannelUpdateSchema);
|
||||
const order = messageObj.payload;
|
||||
typeConverters.convertOrderStringFieldsToBigNumber(order);
|
||||
return {
|
||||
type,
|
||||
payload: order,
|
||||
};
|
||||
}
|
||||
default: {
|
||||
return {
|
||||
type: OrderbookChannelMessageTypes.Unknown,
|
||||
payload: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user