Compare commits
923 Commits
production
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c8d78f6ae | ||
|
|
36d2008962 | ||
|
|
ea4ead21ca | ||
|
|
e6ced26b51 | ||
|
|
f63922b84e | ||
|
|
503eaf70cf | ||
|
|
285ccb1471 | ||
|
|
49356ef46c | ||
|
|
20eb50b281 | ||
|
|
dbb8cafdc3 | ||
|
|
f4029eaf88 | ||
|
|
ad9fe0c80a | ||
|
|
79ed3ad330 | ||
|
|
da5c5143ba | ||
|
|
823cee2d50 | ||
|
|
b93f96f0a6 | ||
|
|
cf32836440 | ||
|
|
b76c39403c | ||
|
|
77eac048ea | ||
|
|
c1ad375aa7 | ||
|
|
1169352233 | ||
|
|
4948e4e265 | ||
|
|
e6f902dada | ||
|
|
9c0b36b965 | ||
|
|
db1152fed9 | ||
|
|
474685d7c1 | ||
|
|
4804e44446 | ||
|
|
ab7579ccac | ||
|
|
9d4a3be785 | ||
|
|
34a4b3585c | ||
|
|
a5ac973395 | ||
|
|
3ceed65fa5 | ||
|
|
a6d4d5a1b7 | ||
|
|
a4428714e0 | ||
|
|
cf65f963c8 | ||
|
|
5eb0687586 | ||
|
|
d20425963b | ||
|
|
aea5e02e61 | ||
|
|
651629bd5d | ||
|
|
1e122db75a | ||
|
|
80c5dc1561 | ||
|
|
75e951dcc1 | ||
|
|
d021d51837 | ||
|
|
7297a232bc | ||
|
|
8734cf618a | ||
|
|
64aa9445de | ||
|
|
7bdd0fa187 | ||
|
|
67166fe5b6 | ||
|
|
dda7bacca7 | ||
|
|
5522c5649a | ||
|
|
9c0f824ad7 | ||
|
|
ad123dd415 | ||
|
|
5a1ebc74c0 | ||
|
|
996b85883e | ||
|
|
be455b2e55 | ||
|
|
881f539fad | ||
|
|
95af97b34f | ||
|
|
7f985b63bc | ||
|
|
9a83209551 | ||
|
|
f1166ec420 | ||
|
|
11c3233c56 | ||
|
|
c3fa6c822e | ||
|
|
d71c891add | ||
|
|
240e05a211 | ||
|
|
582dc351f7 | ||
|
|
a4404e432a | ||
|
|
b3348c4cce | ||
|
|
afb86aad20 | ||
|
|
a51ed583fa | ||
|
|
5fc20a59a1 | ||
|
|
6089e3b7b7 | ||
|
|
ed642d1ff7 | ||
|
|
3026c0c3fc | ||
|
|
1b3ef26cb6 | ||
|
|
314659f9db | ||
|
|
c88671d284 | ||
|
|
e963fd25d5 | ||
|
|
39cdb89540 | ||
|
|
3ae1f03776 | ||
|
|
9162a7f01f | ||
|
|
b024e7e2d0 | ||
|
|
87bd8fa36f | ||
|
|
e1c95d0576 | ||
|
|
31de1cb133 | ||
|
|
92a31e3e3b | ||
|
|
ca69a445ff | ||
|
|
ddcbeba327 | ||
|
|
cbc684ac93 | ||
|
|
18df6d7878 | ||
|
|
e438c546a7 | ||
|
|
ed665a52af | ||
|
|
30852fd11c | ||
|
|
24e5b376ab | ||
|
|
2ac3ac8cd3 | ||
|
|
d74ca064d2 | ||
|
|
055a76e7ba | ||
|
|
87b8319bf3 | ||
|
|
a345269a8d | ||
|
|
82d5641468 | ||
|
|
a2ad7e4327 | ||
|
|
b4a8ddd86a | ||
|
|
301eb5cd67 | ||
|
|
f23e619222 | ||
|
|
0506845c2a | ||
|
|
24eda77855 | ||
|
|
34e6089be9 | ||
|
|
b6c521479b | ||
|
|
ad21b42b68 | ||
|
|
ff97b5d4fa | ||
|
|
f10694f8b0 | ||
|
|
b0f659b818 | ||
|
|
279980ce49 | ||
|
|
2162e38c1a | ||
|
|
dbc0690265 | ||
|
|
9ddc406b75 | ||
|
|
065a1c7b45 | ||
|
|
264a379285 | ||
|
|
3b2e65e5ad | ||
|
|
d014ee2d6f | ||
|
|
0473d96ab8 | ||
|
|
12775d3170 | ||
|
|
84541a166d | ||
|
|
d5b3705389 | ||
|
|
4e9c9f5451 | ||
|
|
ab9e4de749 | ||
|
|
0ca9271a70 | ||
|
|
feb5bdd9c4 | ||
|
|
7dfd544992 | ||
|
|
468c2618b6 | ||
|
|
b8fdb18915 | ||
|
|
19a9ec567f | ||
|
|
42f5c848db | ||
|
|
39906629a6 | ||
|
|
92db0697d7 | ||
|
|
51199e4d87 | ||
|
|
7fef8d340f | ||
|
|
432f920e16 | ||
|
|
a8e604c9cf | ||
|
|
d7f2b20ee1 | ||
|
|
b233467c6e | ||
|
|
66e8cbc911 | ||
|
|
bcf5c54121 | ||
|
|
c83e16f98e | ||
|
|
c02dd53fc5 | ||
|
|
aafc5911f4 | ||
|
|
79855ff4c2 | ||
|
|
e07dda5031 | ||
|
|
1b86e3636f | ||
|
|
d2ae088d8e | ||
|
|
e86411a6c8 | ||
|
|
3c1f4d7ef9 | ||
|
|
72827b12a6 | ||
|
|
136755fcda | ||
|
|
ef451f8342 | ||
|
|
d3267f47ef | ||
|
|
5a61a2e34a | ||
|
|
41b304c5a5 | ||
|
|
de19a464b4 | ||
|
|
2e7ee86ff5 | ||
|
|
15fe096a07 | ||
|
|
5a8e1d0980 | ||
|
|
d07b5d2e9f | ||
|
|
a51171eef1 | ||
|
|
be40b10ae9 | ||
|
|
f50a7f52b2 | ||
|
|
d0d5959dce | ||
|
|
a0c5110517 | ||
|
|
3c44ba45a1 | ||
|
|
2ef46aa5fc | ||
|
|
80d4c0838e | ||
|
|
18ffbfcf86 | ||
|
|
eea99afaee | ||
|
|
dd24d8d90f | ||
|
|
9378bb1853 | ||
|
|
d8a3e80e07 | ||
|
|
7706ca4a08 | ||
|
|
bdea9e8517 | ||
|
|
95d5441031 | ||
|
|
19615ea0db | ||
|
|
533aefe07e | ||
|
|
7809b84d55 | ||
|
|
6f9305c49f | ||
|
|
877012d992 | ||
|
|
9e216a818a | ||
|
|
f862a7696b | ||
|
|
2f4c55bb58 | ||
|
|
c87e676ca8 | ||
|
|
201a0c4bcd | ||
|
|
9225739901 | ||
|
|
57fe37cbe5 | ||
|
|
9b3d716618 | ||
|
|
ce59c975b1 | ||
|
|
5efc55aef3 | ||
|
|
8a6edc5ec6 | ||
|
|
1103eb9dd8 | ||
|
|
8196b1e081 | ||
|
|
61da556645 | ||
|
|
01fafcf066 | ||
|
|
89d8da3a67 | ||
|
|
5be6d0d724 | ||
|
|
0fe9d92663 | ||
|
|
efa8bc01c1 | ||
|
|
3923f260e8 | ||
|
|
fbef2d85fa | ||
|
|
adffdd5401 | ||
|
|
b6b730ea2a | ||
|
|
d3515e12c9 | ||
|
|
9cbc66743d | ||
|
|
26571f0f43 | ||
|
|
34928cac18 | ||
|
|
d93fc791e3 | ||
|
|
ed1ed7b8b9 | ||
|
|
6ba483d2b4 | ||
|
|
430956ad1e | ||
|
|
110ee2ded3 | ||
|
|
1fefd25a87 | ||
|
|
e74ca5ee57 | ||
|
|
bf56ace9f9 | ||
|
|
827470fd71 | ||
|
|
4f6eb68c50 | ||
|
|
e31ec2f276 | ||
|
|
1efdaca2e8 | ||
|
|
f0dbd5b5d6 | ||
|
|
f48bf8f246 | ||
|
|
76b9ff5042 | ||
|
|
423eb31992 | ||
|
|
0f9e13c7d4 | ||
|
|
7de52afbe6 | ||
|
|
ee1ea93195 | ||
|
|
2ae129fd94 | ||
|
|
dd99426add | ||
|
|
3091810320 | ||
|
|
93ad3534f6 | ||
|
|
27b32efbf0 | ||
|
|
389e752096 | ||
|
|
9e2a875027 | ||
|
|
4563a8ffaa | ||
|
|
07430955bd | ||
|
|
32155cead7 | ||
|
|
2146ff290b | ||
|
|
ee0d5595b3 | ||
|
|
1a18a369c1 | ||
|
|
b836390b22 | ||
|
|
56078e2487 | ||
|
|
5f7e2b7613 | ||
|
|
4ba24bba25 | ||
|
|
e7fd05afe9 | ||
|
|
cbcd42c096 | ||
|
|
da8920c4dc | ||
|
|
df2beddf16 | ||
|
|
afc063fb97 | ||
|
|
45a8c809a3 | ||
|
|
12cb8a0c29 | ||
|
|
333eec3dd8 | ||
|
|
2ccaf2fce0 | ||
|
|
5e5a58db19 | ||
|
|
a3941f75ef | ||
|
|
fbfd9ecb34 | ||
|
|
ccf681dda7 | ||
|
|
c560ae3cff | ||
|
|
2ac2822257 | ||
|
|
07cd0cbd8f | ||
|
|
421d0ef8f4 | ||
|
|
9848b17fc1 | ||
|
|
b9de71862d | ||
|
|
52ebe7dde0 | ||
|
|
b21e11b8fb | ||
|
|
8a1d8e6df2 | ||
|
|
d86cdd3678 | ||
|
|
201a3871a0 | ||
|
|
c6b0e66a42 | ||
|
|
067592df50 | ||
|
|
a9493190c2 | ||
|
|
479d55de4a | ||
|
|
c2c51e709a | ||
|
|
e5ad52f55d | ||
|
|
98164e4447 | ||
|
|
cae4a45572 | ||
|
|
050f45015a | ||
|
|
efe19444d5 | ||
|
|
743c060463 | ||
|
|
a1ca054fb8 | ||
|
|
801fbf62e8 | ||
|
|
e3d6ef0039 | ||
|
|
a66573dbc1 | ||
|
|
37744ec85a | ||
|
|
968ab3c103 | ||
|
|
46842c9cc5 | ||
|
|
697388f1ce | ||
|
|
8f4029b3e5 | ||
|
|
d4d5e500d6 | ||
|
|
d5adf90a9b | ||
|
|
794ae55610 | ||
|
|
5e79d1d4f3 | ||
|
|
9026a3d27f | ||
|
|
3287af4974 | ||
|
|
2122b01023 | ||
|
|
44e85f0dec | ||
|
|
14cb4e3332 | ||
|
|
8231ed6a1e | ||
|
|
722eb21cfb | ||
|
|
805e5ec0c3 | ||
|
|
9b7336b066 | ||
|
|
d2f68fefe9 | ||
|
|
cdbc84e93a | ||
|
|
898e373b77 | ||
|
|
f2196bc297 | ||
|
|
3f7ad54b2e | ||
|
|
5d91e48afa | ||
|
|
d148b5ec77 | ||
|
|
4b6482e0ad | ||
|
|
879b87f2a6 | ||
|
|
5a89cf17d2 | ||
|
|
e2c69203ec | ||
|
|
31eecc5afe | ||
|
|
8d733df278 | ||
|
|
a5d74393d1 | ||
|
|
1b0c1b1250 | ||
|
|
3e7f263e18 | ||
|
|
1cb0a6b385 | ||
|
|
1f974e5176 | ||
|
|
05503371cf | ||
|
|
0cbff08f59 | ||
|
|
1218726ffc | ||
|
|
2ac70bc9a2 | ||
|
|
c817f5e62c | ||
|
|
9a80486227 | ||
|
|
c47f58f8e8 | ||
|
|
e5b608809b | ||
|
|
c9a1b6b153 | ||
|
|
515884bf63 | ||
|
|
90379522fe | ||
|
|
958fc79204 | ||
|
|
ad39b6337c | ||
|
|
06b0e108d5 | ||
|
|
2f4533e057 | ||
|
|
3b8b900405 | ||
|
|
e3b8413eee | ||
|
|
2eac46fdb5 | ||
|
|
1d029fcf7f | ||
|
|
b0f95d93ab | ||
|
|
ecee54c10b | ||
|
|
d278dbdb7c | ||
|
|
1777be323c | ||
|
|
0c04a55fd0 | ||
|
|
5bcfd8fcd8 | ||
|
|
7d2b27be7d | ||
|
|
21e30645cd | ||
|
|
afd68116e7 | ||
|
|
ec208e93ab | ||
|
|
d866dd7a65 | ||
|
|
358662fb84 | ||
|
|
cf4d45cf59 | ||
|
|
6659148d4b | ||
|
|
bf26304746 | ||
|
|
a3e4e19a9d | ||
|
|
17acbdda6d | ||
|
|
2b8eee6e03 | ||
|
|
dd0e0bedf0 | ||
|
|
69538704f6 | ||
|
|
4b71ae91ae | ||
|
|
b1e540c8df | ||
|
|
90adbf3ece | ||
|
|
0fe359bd1f | ||
|
|
9045eacccd | ||
|
|
e210e34dbc | ||
|
|
5a78d523a8 | ||
|
|
a554e43c07 | ||
|
|
ba1333f84f | ||
|
|
5e1e80a858 | ||
|
|
34792d5d65 | ||
|
|
5b919a6dc5 | ||
|
|
2226a2cc18 | ||
|
|
1dfe384271 | ||
|
|
e27c7bcf87 | ||
|
|
741a234c76 | ||
|
|
4ef0949061 | ||
|
|
4a0138b666 | ||
|
|
68af224331 | ||
|
|
b63a2208fc | ||
|
|
301e36b5c4 | ||
|
|
46c9027bdb | ||
|
|
0ffef469df | ||
|
|
8ba0a07772 | ||
|
|
3bc5959075 | ||
|
|
db5bf4420a | ||
|
|
ac656802c9 | ||
|
|
5a551a5e3b | ||
|
|
2d1f504a3f | ||
|
|
1fc35956cb | ||
|
|
f35d7dd960 | ||
|
|
4c3a28c780 | ||
|
|
eb7e5d10c9 | ||
|
|
bdbb086e10 | ||
|
|
923128de59 | ||
|
|
22c2642653 | ||
|
|
8d70c017ed | ||
|
|
83a9388e99 | ||
|
|
5a49f5c6c2 | ||
|
|
c61490ed62 | ||
|
|
5667e0b12e | ||
|
|
f6c4ff0990 | ||
|
|
60914a1358 | ||
|
|
402006f835 | ||
|
|
7f87a3a9d7 | ||
|
|
7beb1867d7 | ||
|
|
4473bfd5df | ||
|
|
7e55c1b6f8 | ||
|
|
269bbfe549 | ||
|
|
0174fd5226 | ||
|
|
1ad21a667c | ||
|
|
8d676886de | ||
|
|
fc1284b374 | ||
|
|
5bf176485b | ||
|
|
1143bd803a | ||
|
|
3efd16c6af | ||
|
|
23d858f4b3 | ||
|
|
f7bdf8d0a9 | ||
|
|
d7a70d5456 | ||
|
|
7948a63875 | ||
|
|
b74e98c381 | ||
|
|
a038159526 | ||
|
|
d9a9b64650 | ||
|
|
b13d8df2de | ||
|
|
3b873f4841 | ||
|
|
b48ff0adfa | ||
|
|
434b077467 | ||
|
|
1d160803c3 | ||
|
|
abb65a4394 | ||
|
|
dfccb2ea72 | ||
|
|
6e70484362 | ||
|
|
7afbdb41b1 | ||
|
|
0c7bb4d1f6 | ||
|
|
e5ab809026 | ||
|
|
aac27ff520 | ||
|
|
045c53accb | ||
|
|
63f60d06eb | ||
|
|
857d56aa74 | ||
|
|
0c6868564f | ||
|
|
0bbf9119e2 | ||
|
|
591d904212 | ||
|
|
836a2c4457 | ||
|
|
a5528e49d8 | ||
|
|
b274b363ff | ||
|
|
43b23b80dc | ||
|
|
c1523966f8 | ||
|
|
8a0d065296 | ||
|
|
7d5cade288 | ||
|
|
440a007915 | ||
|
|
ea7e7aae34 | ||
|
|
9f3a5d2bc8 | ||
|
|
8c5f296980 | ||
|
|
3bf0fe7328 | ||
|
|
263933efcd | ||
|
|
b656fb55d5 | ||
|
|
736b85e9df | ||
|
|
a0930bd71b | ||
|
|
3f1332ffca | ||
|
|
5d7957c5fb | ||
|
|
06b24d85b7 | ||
|
|
aaef1c7273 | ||
|
|
3ee90bdbef | ||
|
|
ad5c2dae93 | ||
|
|
71a24237f2 | ||
|
|
ee160d9e7b | ||
|
|
290e53f697 | ||
|
|
6b7496e0c7 | ||
|
|
767fc7e98a | ||
|
|
b028dead40 | ||
|
|
866b45457d | ||
|
|
22351df704 | ||
|
|
09804961c7 | ||
|
|
f02fd3c01f | ||
|
|
25a83b546b | ||
|
|
07d7fb3537 | ||
|
|
8852924a14 | ||
|
|
583139ca2c | ||
|
|
6f99a2f881 | ||
|
|
de09b9056d | ||
|
|
877c5cfbc7 | ||
|
|
0d7e4783e7 | ||
|
|
04c4a71f14 | ||
|
|
ccc5f8868a | ||
|
|
2bc629cac5 | ||
|
|
a3071d5ec6 | ||
|
|
9cfef6b834 | ||
|
|
013358a69c | ||
|
|
a6ab615a96 | ||
|
|
b5a76aee44 | ||
|
|
9334b13a0d | ||
|
|
b8df8bcec4 | ||
|
|
a2c650b2a0 | ||
|
|
2f3207babe | ||
|
|
cf1ea86f7d | ||
|
|
53c9d82bd1 | ||
|
|
615ffc59db | ||
|
|
7d21c310b1 | ||
|
|
130b7a7022 | ||
|
|
8abeb5eb8e | ||
|
|
e9881777b2 | ||
|
|
8f27d671f3 | ||
|
|
f1994947b9 | ||
|
|
0608159096 | ||
|
|
e624721fe3 | ||
|
|
57ec79a46a | ||
|
|
d784be5d7e | ||
|
|
60d9d546a2 | ||
|
|
a801427e99 | ||
|
|
f1fafaadf2 | ||
|
|
8f295786bc | ||
|
|
95388ad451 | ||
|
|
8688f2fc80 | ||
|
|
c979260395 | ||
|
|
db4319d8e2 | ||
|
|
adf9376590 | ||
|
|
ff9f4cdae2 | ||
|
|
340decd978 | ||
|
|
cc7c6f81d4 | ||
|
|
d0a8b123d2 | ||
|
|
d8fea29125 | ||
|
|
795b5182fc | ||
|
|
62e1225dd3 | ||
|
|
95bcad836c | ||
|
|
802aae237e | ||
|
|
5eb32cf64c | ||
|
|
5e72240be9 | ||
|
|
67fc186293 | ||
|
|
5e54eac89b | ||
|
|
4c980e0177 | ||
|
|
38e061e0c5 | ||
|
|
967445c14f | ||
|
|
7db7b1214b | ||
|
|
57e65010f1 | ||
|
|
b9c909ad40 | ||
|
|
299344cf87 | ||
|
|
ef85ba4a08 | ||
|
|
77211dbaf7 | ||
|
|
ce7de0b63c | ||
|
|
ddcd05cbc2 | ||
|
|
692578fd12 | ||
|
|
df19530235 | ||
|
|
667a38053b | ||
|
|
4317f67a2b | ||
|
|
2448ad1779 | ||
|
|
72fe25aff9 | ||
|
|
d7c161d7b6 | ||
|
|
ffed19b8b3 | ||
|
|
0a06f9aba9 | ||
|
|
f50abae460 | ||
|
|
3532cd25a1 | ||
|
|
61f0c3063a | ||
|
|
11874a1957 | ||
|
|
0465179a93 | ||
|
|
ae9f6a2c2f | ||
|
|
9eacb3dedc | ||
|
|
285e3550f5 | ||
|
|
c6bd150bd9 | ||
|
|
351c315736 | ||
|
|
ebcc3607c2 | ||
|
|
98d5314ef2 | ||
|
|
7973e2398a | ||
|
|
310dd99086 | ||
|
|
0ba78e7180 | ||
|
|
29e96e83ae | ||
|
|
4e54d195b9 | ||
|
|
b446f6733c | ||
|
|
80549ff1a7 | ||
|
|
87643fba45 | ||
|
|
923c596b74 | ||
|
|
92ac2155d4 | ||
|
|
f002131366 | ||
|
|
d104af895f | ||
|
|
3ff860c483 | ||
|
|
c83842eb60 | ||
|
|
f890daa108 | ||
|
|
203df0d736 | ||
|
|
2efa5562be | ||
|
|
95b2a04937 | ||
|
|
465b14a0dd | ||
|
|
bc9bb10ea7 | ||
|
|
36ad6ebbbe | ||
|
|
3e40d71c51 | ||
|
|
6e5dbfd22a | ||
|
|
e1df171f95 | ||
|
|
b686c5bea9 | ||
|
|
6ec263692b | ||
|
|
c68b9e143e | ||
|
|
f87bdea8fb | ||
|
|
29faace44a | ||
|
|
13532666c3 | ||
|
|
2ff77801b0 | ||
|
|
bb901b91bc | ||
|
|
332fd2a012 | ||
|
|
d47527e7d0 | ||
|
|
04b0b39049 | ||
|
|
38bbbc07dc | ||
|
|
7ded22fb2c | ||
|
|
19c5b617d1 | ||
|
|
9d78188f64 | ||
|
|
491a5cb846 | ||
|
|
0957f2301e | ||
|
|
8280d7a628 | ||
|
|
de4a87e5f8 | ||
|
|
b18dc95755 | ||
|
|
78870fc56e | ||
|
|
3c355c30a9 | ||
|
|
13efeec84b | ||
|
|
e4f585ad15 | ||
|
|
c42b8d9d91 | ||
|
|
f5acc2f564 | ||
|
|
e3ef214b21 | ||
|
|
653fd60a24 | ||
|
|
d16892d7af | ||
|
|
2e013122db | ||
|
|
133c2321f9 | ||
|
|
f4baed5c37 | ||
|
|
e790622df6 | ||
|
|
44014b5bcb | ||
|
|
0c7f572030 | ||
|
|
d9fe157f41 | ||
|
|
ea57755c0b | ||
|
|
dfdeaba729 | ||
|
|
2282d231bd | ||
|
|
840fd0db21 | ||
|
|
b3111721b6 | ||
|
|
4d24f4620a | ||
|
|
287611dd2c | ||
|
|
1cb2e79998 | ||
|
|
931d295f2a | ||
|
|
27ee62b755 | ||
|
|
22f3ba9405 | ||
|
|
8c6f7c8aef | ||
|
|
abe5075dd3 | ||
|
|
94f584f792 | ||
|
|
2a14955d74 | ||
|
|
496349d4b5 | ||
|
|
7c5a79c80a | ||
|
|
d806500b38 | ||
|
|
24c3f466c0 | ||
|
|
3dfc1bc9e3 | ||
|
|
4606e6e953 | ||
|
|
efdb2487e4 | ||
|
|
7d68b0cc82 | ||
|
|
de6a44d14e | ||
|
|
e006e5cbcd | ||
|
|
7ec3124d82 | ||
|
|
cc1cbc12ad | ||
|
|
db2ee2ab27 | ||
|
|
c45a0edd60 | ||
|
|
933c378a8d | ||
|
|
de6ccd6969 | ||
|
|
12b33a5c37 | ||
|
|
7bc78928b1 | ||
|
|
85b51a378a | ||
|
|
accff4e43c | ||
|
|
f96d8f3f8b | ||
|
|
df574bfffd | ||
|
|
fe6a441a52 | ||
|
|
dcabaeba8c | ||
|
|
2a1f9706e3 | ||
|
|
3f76e8a9dc | ||
|
|
8625c23e81 | ||
|
|
fb43ff70d4 | ||
|
|
19974a7d3d | ||
|
|
999a669c53 | ||
|
|
bb0412f1c8 | ||
|
|
7b74045ff4 | ||
|
|
a73cd731d9 | ||
|
|
c7054051e3 | ||
|
|
4684224a53 | ||
|
|
4a850e2758 | ||
|
|
52b02eda14 | ||
|
|
778c09b96d | ||
|
|
dc791a2f24 | ||
|
|
b5251849a0 | ||
|
|
b49f1505eb | ||
|
|
8a21f48d2a | ||
|
|
b32eeec4d6 | ||
|
|
4b1b929cff | ||
|
|
2105492ff9 | ||
|
|
9070b74653 | ||
|
|
e45396435b | ||
|
|
d869be5208 | ||
|
|
d1f7349ac5 | ||
|
|
8c605047c3 | ||
|
|
fc15f2982f | ||
|
|
cf58b69bbf | ||
|
|
af08592d01 | ||
|
|
2ec8d1f447 | ||
|
|
83c8f9c5ac | ||
|
|
c0e4a07bad | ||
|
|
f359a96f9e | ||
|
|
3d713224bf | ||
|
|
65b302c7af | ||
|
|
bc4672760c | ||
|
|
9485398879 | ||
|
|
2ae1cdbec2 | ||
|
|
4be08c7be8 | ||
|
|
1199618471 | ||
|
|
97255a525f | ||
|
|
0e538f2e06 | ||
|
|
300bf67414 | ||
|
|
daf9546e75 | ||
|
|
bd8ed3bddf | ||
|
|
3bcb0426c3 | ||
|
|
dd3d590a44 | ||
|
|
e4eb09f87f | ||
|
|
36f5c75880 | ||
|
|
5ab366465b | ||
|
|
546c4a3345 | ||
|
|
a9828d8539 | ||
|
|
69c536be7d | ||
|
|
d67950145c | ||
|
|
1d70f0196c | ||
|
|
bf604e0a8f | ||
|
|
c917118d97 | ||
|
|
adacd21e57 | ||
|
|
5bddb0b047 | ||
|
|
80ab08d3b9 | ||
|
|
6c276ada49 | ||
|
|
54462019be | ||
|
|
f9fd3c0488 | ||
|
|
9cc0c401f0 | ||
|
|
ed0a46a525 | ||
|
|
e4992a6dfb | ||
|
|
4253de1ed4 | ||
|
|
443507c6bd | ||
|
|
52b665e79a | ||
|
|
c2a0dee752 | ||
|
|
aae6847235 | ||
|
|
805099de1b | ||
|
|
53d8cd4412 | ||
|
|
aaa21faae5 | ||
|
|
ea0adc1d9e | ||
|
|
4d3e71a346 | ||
|
|
81c71415ed | ||
|
|
07d8dddfb3 | ||
|
|
8241dfe9ba | ||
|
|
d188b2512c | ||
|
|
1423811d4e | ||
|
|
9183ffcf10 | ||
|
|
245e52d6fb | ||
|
|
76e47fd077 | ||
|
|
d91894e0ea | ||
|
|
9e7ff7e092 | ||
|
|
1a821f9c2a | ||
|
|
79341717ec | ||
|
|
af028e41e6 | ||
|
|
51a6ff9e85 | ||
|
|
7d4014f573 | ||
|
|
e4a82a838b | ||
|
|
f29f5a8f4a | ||
|
|
5435744f05 | ||
|
|
94faf2d9f9 | ||
|
|
f20550259f | ||
|
|
f47133cd4b | ||
|
|
594216e998 | ||
|
|
8bde44134b | ||
|
|
2852afc12b | ||
|
|
c1d14c6652 | ||
|
|
40411ca2aa | ||
|
|
adc3f2ade4 | ||
|
|
5b48e4444d | ||
|
|
7698739215 | ||
|
|
0b255f5f32 | ||
|
|
dd2d32a06a | ||
|
|
67ff2cd06a | ||
|
|
d6b90fe871 | ||
|
|
9e6fccc554 | ||
|
|
4225e59ae0 | ||
|
|
5217763a1e | ||
|
|
b1cb8d7209 | ||
|
|
3c2add5f36 | ||
|
|
f7bec5285a | ||
|
|
9945a0bdc9 | ||
|
|
253ea739d7 | ||
|
|
793fc4216a | ||
|
|
8ea1d1e9c4 | ||
|
|
56c0660c87 | ||
|
|
9e33a4abd8 | ||
|
|
beee5a3003 | ||
|
|
2341dcc12f | ||
|
|
4d8068301a | ||
|
|
b8f624f1e0 | ||
|
|
a4465279f1 | ||
|
|
26f24480fd | ||
|
|
2a048cdd64 | ||
|
|
bd51c9575a | ||
|
|
33c9120991 | ||
|
|
c788ef0c2f | ||
|
|
cab5dd438b | ||
|
|
5e058c2f6f | ||
|
|
bee6de08a4 | ||
|
|
7681797b90 | ||
|
|
f2b8fd3531 | ||
|
|
72028e4314 | ||
|
|
c94765bca5 | ||
|
|
a8aebfe14a | ||
|
|
48cdac80fb | ||
|
|
d8712e7a83 | ||
|
|
45caf5a40a | ||
|
|
9f3f0bfd09 | ||
|
|
d8936051aa | ||
|
|
4f9216969d | ||
|
|
22df1545e4 | ||
|
|
9c2a3b1981 | ||
|
|
c1e06dd5df | ||
|
|
8d54e087f0 | ||
|
|
7a0f62eeff | ||
|
|
aa74f13a24 | ||
|
|
7cde324aac | ||
|
|
f18dc83cd6 | ||
|
|
e5f7327d1f | ||
|
|
e09d0643dc | ||
|
|
4b149c87bf | ||
|
|
b27b01215f | ||
|
|
9694e415d1 | ||
|
|
99afc7e187 | ||
|
|
c4a3b874d5 | ||
|
|
1aa7dde498 | ||
|
|
cc6c077678 | ||
|
|
19a907aed3 | ||
|
|
fca0ccc0ec | ||
|
|
58b7b67103 | ||
|
|
f55ece0642 | ||
|
|
f5c930adef | ||
|
|
6aa38d2046 | ||
|
|
d28d83b90a | ||
|
|
d82b0ecebd | ||
|
|
3604ff8651 | ||
|
|
36b183f26d | ||
|
|
ae839a0fc8 | ||
|
|
91b00a0088 | ||
|
|
fd23d9c722 | ||
|
|
29c24884e5 | ||
|
|
e0e6c94ae7 | ||
|
|
7b6986e46b | ||
|
|
8baf5dd691 | ||
|
|
eefaf0ae6a | ||
|
|
e273ec998c | ||
|
|
a48a611de5 | ||
|
|
bb4bf112d9 | ||
|
|
77dfa2fdc6 | ||
|
|
83a4adf570 | ||
|
|
9729af93bb | ||
|
|
a1599b16b6 | ||
|
|
da8f3d2473 | ||
|
|
85cb296950 | ||
|
|
62e4a3c714 | ||
|
|
40e7491234 | ||
|
|
4d76f1c6f4 | ||
|
|
39f23f75b5 | ||
|
|
c36618a445 | ||
|
|
4ed93d9105 | ||
|
|
112e1427bd | ||
|
|
62d0bde1cb | ||
|
|
4f13a1c463 | ||
|
|
b41de7ac78 | ||
|
|
b6d8aa4d6e | ||
|
|
d6f9d84248 | ||
|
|
c2e267e6d6 | ||
|
|
0cf6d8e722 | ||
|
|
b121081042 | ||
|
|
f6d56cba21 | ||
|
|
ccf1d04449 | ||
|
|
c6d69324b0 | ||
|
|
48cbe43d42 | ||
|
|
246948f6fd | ||
|
|
b482e4d021 | ||
|
|
e3ac30bb18 | ||
|
|
63b6edba33 | ||
|
|
1ccd161979 | ||
|
|
370a89631c | ||
|
|
ff01374e5a | ||
|
|
6d99920d1b | ||
|
|
47486b73db | ||
|
|
bc0aff684b | ||
|
|
0a477d71be | ||
|
|
abb35aa575 | ||
|
|
6eff53b379 | ||
|
|
ec3f05cb75 | ||
|
|
fcf6d72fa4 | ||
|
|
f7fbbf512d | ||
|
|
fe09824a68 | ||
|
|
87dd9a7159 | ||
|
|
2fa72b83bb | ||
|
|
f368789f98 | ||
|
|
42d8b4a0ca | ||
|
|
7c5c90b258 | ||
|
|
cbe3e2c29e | ||
|
|
7b3e682dbb | ||
|
|
993013c7ee | ||
|
|
0d4b9ddfc0 | ||
|
|
ac7b2373b0 | ||
|
|
6fa244dd7d | ||
|
|
a60abd9793 | ||
|
|
83681967df | ||
|
|
8ceae3683d | ||
|
|
378943f94d | ||
|
|
dac2f8b949 | ||
|
|
e66461cb1c | ||
|
|
29b79e9ca9 | ||
|
|
ef2cb2c055 | ||
|
|
45f8ff9ea0 | ||
|
|
92cf4e82a3 | ||
|
|
284ff9eecc | ||
|
|
2dbcb3588a | ||
|
|
8200f2121b | ||
|
|
df1778fd54 | ||
|
|
fb8c2e98e3 | ||
|
|
6486d9a741 | ||
|
|
c97289a4cd | ||
|
|
a4d09fa089 | ||
|
|
fd6d06d115 | ||
|
|
b0f4deb0d8 | ||
|
|
68ed982992 | ||
|
|
ebfa84b9d2 | ||
|
|
d4c40355f8 | ||
|
|
cdfd851573 | ||
|
|
b67b59e298 | ||
|
|
8cf992dd1c | ||
|
|
6648d73ab8 | ||
|
|
d9020ca104 |
29
.circleci/config.yml
Normal file
@ -0,0 +1,29 @@
|
||||
# Javascript Node CircleCI 2.0 configuration file
|
||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
||||
version: 2
|
||||
jobs:
|
||||
build-and-test:
|
||||
docker:
|
||||
- image: circleci/node:8.10-browsers
|
||||
working_directory: ~/insight
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- dependency-cache-{{ checksum "package.json" }}
|
||||
- run: npm install
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
key: dependency-cache-{{ checksum "package.json" }}
|
||||
|
||||
# run tests
|
||||
- run: npm run test:ci
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_and_test:
|
||||
jobs:
|
||||
- build-and-test
|
||||
31
.gitignore
vendored
@ -1,31 +1,6 @@
|
||||
# from https://github.com/github/gitignore/blob/master/Node.gitignore
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
*.swp
|
||||
tags
|
||||
pids
|
||||
logs
|
||||
results
|
||||
build
|
||||
|
||||
node_modules
|
||||
|
||||
# extras
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
.project
|
||||
peerdb.json
|
||||
|
||||
npm-debug.log
|
||||
.nodemonignore
|
||||
|
||||
yarn.lock
|
||||
*.log
|
||||
.DS_Store
|
||||
db/txs/*
|
||||
db/txs
|
||||
@ -38,3 +13,5 @@ db/testnet/blocks
|
||||
|
||||
README.html
|
||||
public
|
||||
|
||||
package-lock.json
|
||||
|
||||
65
.jscsrc
Executable file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"excludeFiles": ["node_modules/**", "mynode/**"],
|
||||
|
||||
"requireCurlyBraces": [
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"try",
|
||||
"catch"
|
||||
],
|
||||
"requireOperatorBeforeLineBreak": true,
|
||||
"requireCamelCaseOrUpperCaseIdentifiers": true,
|
||||
"maximumLineLength": {
|
||||
"value": 200,
|
||||
"allowComments": true,
|
||||
"allowRegex": true
|
||||
},
|
||||
"validateIndentation": 2,
|
||||
"validateQuoteMarks": "'",
|
||||
|
||||
"disallowMultipleLineStrings": true,
|
||||
"disallowMixedSpacesAndTabs": true,
|
||||
"disallowTrailingWhitespace": true,
|
||||
"disallowSpaceAfterPrefixUnaryOperators": true,
|
||||
"disallowMultipleVarDecl": null,
|
||||
|
||||
"requireSpaceAfterKeywords": [
|
||||
"if",
|
||||
"else",
|
||||
"for",
|
||||
"while",
|
||||
"do",
|
||||
"switch",
|
||||
"return",
|
||||
"try",
|
||||
"catch"
|
||||
],
|
||||
"requireSpaceBeforeBinaryOperators": [
|
||||
"=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
|
||||
"&=", "|=", "^=", "+=",
|
||||
|
||||
"+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
|
||||
"|", "^", "&&", "||", "===", "==", ">=",
|
||||
"<=", "<", ">", "!=", "!=="
|
||||
],
|
||||
"requireSpaceAfterBinaryOperators": true,
|
||||
"requireSpacesInConditionalExpression": true,
|
||||
"requireSpaceBeforeBlockStatements": true,
|
||||
"disallowSpacesInsideObjectBrackets": "all",
|
||||
"disallowSpacesInsideArrayBrackets": "all",
|
||||
"disallowSpacesInsideParentheses": true,
|
||||
|
||||
"disallowMultipleLineBreaks": true,
|
||||
|
||||
"disallowCommaBeforeLineBreak": null,
|
||||
"disallowDanglingUnderscores": null,
|
||||
"disallowEmptyBlocks": null,
|
||||
"disallowTrailingComma": null,
|
||||
"requireCommaBeforeLineBreak": null,
|
||||
"requireDotNotation": null,
|
||||
"requireMultipleVarDecl": null,
|
||||
"requireParenthesesAroundIIFE": true
|
||||
}
|
||||
@ -1,4 +1,8 @@
|
||||
sudo: false
|
||||
language: node_js
|
||||
node_js:
|
||||
- '0.10'
|
||||
install: npm install
|
||||
- 'v8'
|
||||
install:
|
||||
- npm install
|
||||
after_success:
|
||||
- yarn send-coverage
|
||||
|
||||
94
Gruntfile.js
@ -1,94 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(grunt) {
|
||||
|
||||
//Load NPM tasks
|
||||
grunt.loadNpmTasks('grunt-contrib-watch');
|
||||
grunt.loadNpmTasks('grunt-mocha-test');
|
||||
grunt.loadNpmTasks('grunt-nodemon');
|
||||
grunt.loadNpmTasks('grunt-concurrent');
|
||||
grunt.loadNpmTasks('grunt-env');
|
||||
grunt.loadNpmTasks('grunt-markdown');
|
||||
|
||||
// Project Configuration
|
||||
grunt.initConfig({
|
||||
pkg: grunt.file.readJSON('package.json'),
|
||||
watch: {
|
||||
readme: {
|
||||
files: ['README.md'],
|
||||
tasks: ['markdown']
|
||||
},
|
||||
js: {
|
||||
files: ['Gruntfile.js', 'insight.js', 'app/**/*.js'],
|
||||
options: {
|
||||
livereload: true,
|
||||
},
|
||||
},
|
||||
test: {
|
||||
files: ['test/**/*.js', 'test/*.js','app/**/*.js'],
|
||||
tasks: ['test'],
|
||||
}
|
||||
},
|
||||
jshint: {
|
||||
all: {
|
||||
src: ['Gruntfile.js', 'insight.js', 'app/**/*.js', 'lib/*.js', 'config/*.js'],
|
||||
options: {
|
||||
jshintrc: true
|
||||
}
|
||||
}
|
||||
},
|
||||
mochaTest: {
|
||||
options: {
|
||||
reporter: 'spec',
|
||||
},
|
||||
src: ['test/**/*.js'],
|
||||
},
|
||||
nodemon: {
|
||||
dev: {
|
||||
script: 'insight.js',
|
||||
options: {
|
||||
args: [],
|
||||
ignore: ['test/**/*', 'util/**/*', 'dev-util/**/*'],
|
||||
// nodeArgs: ['--debug'],
|
||||
delayTime: 1,
|
||||
env: {
|
||||
PORT: 3000
|
||||
},
|
||||
cwd: __dirname
|
||||
}
|
||||
}
|
||||
},
|
||||
concurrent: {
|
||||
tasks: ['nodemon', 'watch'],
|
||||
options: {
|
||||
logConcurrentOutput: true
|
||||
}
|
||||
},
|
||||
env: {
|
||||
test: {
|
||||
NODE_ENV: 'test'
|
||||
}
|
||||
},
|
||||
markdown: {
|
||||
all: {
|
||||
files: [
|
||||
{
|
||||
expand: true,
|
||||
src: 'README.md',
|
||||
dest: '.',
|
||||
ext: '.html'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//Making grunt default to force in order not to break the project.
|
||||
grunt.option('force', true);
|
||||
|
||||
//Default task(s).
|
||||
grunt.registerTask('default', ['concurrent']);
|
||||
|
||||
//Test task.
|
||||
grunt.registerTask('test', ['env:test', 'mochaTest']);
|
||||
};
|
||||
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013-2017 BitPay, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
391
README.md
@ -1,185 +1,135 @@
|
||||
# Flosight API
|
||||
|
||||
# *insight API*
|
||||
A Florincoin blockchain REST and web socket API service for [Flocore Node](https://github.com/bitpay/flocore-node).
|
||||
|
||||
*insight API* is an open-source bitcoin blockchain REST
|
||||
and websocket API. Insight API runs in NodeJS and uses LevelDB for storage.
|
||||
This is a backend-only service. If you're looking for the web frontend application, take a look at https://github.com/bitpay/flosight-ui.
|
||||
|
||||
This is a backend-only service. If you're looking for the web frontend application,
|
||||
take a look at https://github.com/bitpay/insight.
|
||||
## Getting Started
|
||||
|
||||
*Insight API* allows to develop bitcoin-related applications (such as wallets) that
|
||||
require certain information from the blockchain that bitcoind does not provide.
|
||||
```bashl
|
||||
npm install -g flocore@latest
|
||||
flocore create mynode
|
||||
cd mynode
|
||||
flocore install flosight-api
|
||||
flocore start
|
||||
```
|
||||
|
||||
A blockchain explorer front-end has been developed on top of *Insight API*. It can
|
||||
be downloaded at [Github Insight Repository](https://github.com/bitpay/insight).
|
||||
|
||||
## Warning
|
||||
Insight file sync does not work with **bitcoind** v0.10
|
||||
In order to use Insigtht you must set the environment variable INSIGHT_FORCE_RPC_SYNC = 1
|
||||
We are working on `bitcore-node` to replace Insight-api. Check `bitcore-node` on [github](https://github.com/bitpay/bitcore-node).
|
||||
The API endpoints will be available by default at: `http://localhost:3001/flosight-api/`
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* **bitcoind** - Download and Install [Bitcoin](http://bitcoin.org/en/download)
|
||||
- [Flocore 5.x](https://github.com/bitpay/flocore)
|
||||
|
||||
*insight API* needs a *trusted* bitcoind node to run. *insight API* will connect to the node
|
||||
through the RPC API, bitcoin peer-to-peer protocol, and will even read its raw block .dat files for syncing.
|
||||
**Note:** You can use an existing Florincoin data directory, however `txindex`, `addressindex`, `timestampindex` and `spentindex` needs to be set to true in `florincoin.conf`, as well as a few other additional fields.
|
||||
|
||||
Configure bitcoind to listen to RPC calls and set `txindex` to true.
|
||||
The easiest way to do this is by copying `./etc/bitcoind/bitcoin.conf` to your
|
||||
bitcoin data directory (usually `~/.bitcoin` on Linux, `%appdata%\Bitcoin\` on Windows,
|
||||
or `~/Library/Application Support/Bitcoin` on Mac OS X).
|
||||
|
||||
bitcoind must be running and must have finished downloading the blockchain **before** running *insight API*.
|
||||
|
||||
|
||||
* **Node.js v0.10.x** - Download and Install [Node.js](http://www.nodejs.org/download/).
|
||||
|
||||
* **NPM** - Node.js package manager, should be automatically installed when you get node.js.
|
||||
|
||||
|
||||
## Quick Install
|
||||
Check the Prerequisites section above before installing.
|
||||
|
||||
To install Insight API, clone the main repository:
|
||||
|
||||
$ git clone https://github.com/bitpay/insight-api && cd insight-api
|
||||
|
||||
Install dependencies:
|
||||
|
||||
$ npm install
|
||||
|
||||
Run the main application:
|
||||
|
||||
$ node insight.js
|
||||
|
||||
Then open a browser and go to:
|
||||
|
||||
http://localhost:3001
|
||||
|
||||
Please note that the app will need to sync its internal database
|
||||
with the blockchain state, which may take some time. You can check
|
||||
sync progress at http://localhost:3001/api/sync.
|
||||
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is specified in the [config](config/) folder, particularly the [config.js](config/config.js) file. There you can specify your application name and database name. Certain configuration values are pulled from environment variables if they are defined:
|
||||
## Notes on Upgrading from v0.3
|
||||
|
||||
The unspent outputs format now has `satoshis` and `height`:
|
||||
```
|
||||
BITCOIND_HOST # RPC bitcoind host
|
||||
BITCOIND_PORT # RPC bitcoind Port
|
||||
BITCOIND_P2P_HOST # P2P bitcoind Host (will default to BITCOIND_HOST, if specified)
|
||||
BITCOIND_P2P_PORT # P2P bitcoind Port
|
||||
BITCOIND_USER # RPC username
|
||||
BITCOIND_PASS # RPC password
|
||||
BITCOIND_DATADIR # bitcoind datadir. 'testnet3' will be appended automatically if testnet is used. NEED to finish with '/'. e.g: `/vol/data/`
|
||||
INSIGHT_NETWORK [= 'livenet' | 'testnet']
|
||||
INSIGHT_PORT # insight api port
|
||||
INSIGHT_DB # Path where to store insight's internal DB. (defaults to $HOME/.insight)
|
||||
INSIGHT_SAFE_CONFIRMATIONS=6 # Nr. of confirmation needed to start caching transaction information
|
||||
INSIGHT_IGNORE_CACHE # True to ignore cache of spents in transaction, with more than INSIGHT_SAFE_CONFIRMATIONS confirmations. This is useful for tracking double spents for old transactions.
|
||||
ENABLE_CURRENCYRATES # if "true" will enable a plugin to obtain historic conversion rates for various currencies
|
||||
ENABLE_RATELIMITER # if "true" will enable the ratelimiter plugin
|
||||
LOGGER_LEVEL # defaults to 'info', can be 'debug','verbose','error', etc.
|
||||
ENABLE_HTTPS # if "true" it will server using SSL/HTTPS
|
||||
ENABLE_EMAILSTORE # if "true" will enable a plugin to store data with a validated email address
|
||||
INSIGHT_EMAIL_CONFIRM_HOST # Only meanfull if ENABLE_EMAILSTORE is enable. Hostname for the confirm URLs. E.g: 'https://insight.bitpay.com'
|
||||
[
|
||||
{
|
||||
"address":"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||
"txid":"d5f8a96faccf79d4c087fa217627bb1120e83f8ea1a7d84b1de4277ead9bbac1",
|
||||
"vout":0,
|
||||
"scriptPubKey":"76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||
"amount":0.000006,
|
||||
"satoshis":600,
|
||||
"confirmations":0,
|
||||
"ts":1461349425
|
||||
},
|
||||
{
|
||||
"address": "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||
"txid": "bc9df3b92120feaee4edc80963d8ed59d6a78ea0defef3ec3cb374f2015bfc6e",
|
||||
"vout": 1,
|
||||
"scriptPubKey": "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||
"amount": 0.12345678,
|
||||
"satoshis: 12345678,
|
||||
"confirmations": 1,
|
||||
"height": 300001
|
||||
}
|
||||
]
|
||||
```
|
||||
The `timestamp` property will only be set for unconfirmed transactions and `height` can be used for determining block order. The `confirmationsFromCache` is nolonger set or necessary, confirmation count is only cached for the time between blocks.
|
||||
|
||||
There is a new `GET` endpoint or raw blocks at `/rawblock/<blockHash>`:
|
||||
|
||||
Response format:
|
||||
```
|
||||
{
|
||||
"rawblock": "blockhexstring..."
|
||||
}
|
||||
```
|
||||
|
||||
Make sure that bitcoind is configured to [accept incoming connections using 'rpcallowip'](https://en.bitcoin.it/wiki/Running_Bitcoin).
|
||||
There are a few changes to the `GET` endpoint for `/addr/[:address]`:
|
||||
|
||||
In case the network is changed (testnet to livenet or vice versa) levelDB database needs to be deleted. This can be performed running:
|
||||
```util/sync.js -D``` and waiting for *insight* to synchronize again. Once the database is deleted, the sync.js process can be safely interrupted (CTRL+C) and continued from the synchronization process embedded in main app.
|
||||
- The list of txids in an address summary does not include orphaned transactions
|
||||
- The txids will be sorted in block order
|
||||
- The list of txids will be limited at 1000 txids
|
||||
- There are two new query options "from" and "to" for pagination of the txids (e.g. `/addr/[:address]?from=1000&to=2000`)
|
||||
|
||||
## Synchronization
|
||||
Some additional general notes:
|
||||
- The transaction history for an address will be sorted in block order
|
||||
- The response for the `/sync` endpoint does not include `startTs` and `endTs` as the sync is no longer relevant as indexes are built in florincoind.
|
||||
- The endpoint for `/peer` is no longer relevant connection to florincoind is via ZMQ.
|
||||
- `/tx` endpoint results will now include block height, and spentTx related fields will be set to `null` if unspent.
|
||||
- `/block` endpoint results does not include `confirmations` and will include `poolInfo`.
|
||||
|
||||
The initial synchronization process scans the blockchain from the paired bitcoind server to update addresses and balances. *insight-api* needs exactly one trusted bitcoind node to run. This node must have finished downloading the blockchain before running *insight-api*.
|
||||
## Notes on Upgrading from v0.2
|
||||
|
||||
While *insight* is synchronizing the website can be accessed (the sync process is embedded in the webserver), but there may be missing data or incorrect balances for addresses. The 'sync' status is shown at the `/api/sync` endpoint.
|
||||
Some of the fields and methods are not supported:
|
||||
|
||||
The blockchain can be read from bitcoind's raw `.dat` files or RPC interface.
|
||||
Reading the information from the `.dat` files is much faster so it's the
|
||||
recommended (and default) alternative. `.dat` files are scanned in the default
|
||||
location for each platform (for example, `~/.bitcoin` on Linux). In case a
|
||||
non-standard location is used, it needs to be defined (see the Configuration section).
|
||||
As of June 2014, using `.dat` files the sync process takes 9 hrs.
|
||||
for livenet and 30 mins. for testnet.
|
||||
The `/tx/<txid>` endpoint JSON response will not include the following fields on the "vin"
|
||||
object:
|
||||
- `doubleSpentTxId` // double spends are not currently tracked
|
||||
- `isConfirmed` // confirmation of the previous output
|
||||
- `confirmations` // confirmations of the previous output
|
||||
- `unconfirmedInput`
|
||||
|
||||
While synchronizing the blockchain, *insight-api* listens for new blocks and
|
||||
transactions relayed by the bitcoind node. Those are also stored on *insight-api*'s database.
|
||||
In case *insight-api* is shutdown for a period of time, restarting it will trigger
|
||||
a partial (historic) synchronization of the blockchain. Depending on the size of
|
||||
that synchronization task, a reverse RPC or forward `.dat` syncing strategy will be used.
|
||||
The `/tx/<txid>` endpoint JSON response will not include the following fields on the "vout"
|
||||
object.
|
||||
- `spentTs`
|
||||
|
||||
If bitcoind is shutdown, *insight-api* needs to be stopped and restarted
|
||||
once bitcoind is restarted.
|
||||
The `/status?q=getTxOutSetInfo` method has also been removed due to the query being very slow and locking florincoind.
|
||||
|
||||
### Syncing old blockchain data manually
|
||||
Plug-in support for Flosight API is also no longer available, as well as the endpoints:
|
||||
- `/email/retrieve`
|
||||
- `/rates/:code`
|
||||
|
||||
Old blockchain data can be manually synced issuing:
|
||||
Caching support has not yet been added in the v0.3 upgrade.
|
||||
|
||||
$ util/sync.js
|
||||
## Query Rate Limit
|
||||
|
||||
Check util/sync.js --help for options, particulary -D to erase the current DB.
|
||||
To protect the server, flosight-api has a built it query rate limiter. It can be configurable in `flocore-node.json` with:
|
||||
``` json
|
||||
"servicesConfig": {
|
||||
"flosight-api": {
|
||||
"rateLimiterOptions": {
|
||||
"whitelist": ["::ffff:127.0.0.1"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
With all the configuration options available: https://github.com/bitpay/flosight-api/blob/master/lib/ratelimiter.js#L10-17
|
||||
|
||||
*NOTE*: there is no need to run this manually since the historic synchronization
|
||||
is built in into the web application. Running *insight-api* normally will trigger
|
||||
the historic sync automatically.
|
||||
|
||||
|
||||
### DB storage requirement
|
||||
|
||||
To store the blockchain and address related information, *insight-api* uses LevelDB.
|
||||
Two DBs are created: txs and blocks. By default these are stored on
|
||||
|
||||
``~/.insight/``
|
||||
|
||||
Please note that some older versions of Insight-API store that on `<insight's root>/db`.
|
||||
|
||||
This can be changed at config/config.js. As of June 2014, storing the livenet blockchain takes ~35GB of disk space (2GB for the testnet).
|
||||
|
||||
## Development
|
||||
|
||||
To run insight locally for development with grunt:
|
||||
|
||||
```$ NODE_ENV=development grunt```
|
||||
|
||||
To run the tests
|
||||
|
||||
```$ grunt test```
|
||||
|
||||
|
||||
Contributions and suggestions are welcome at [insight-api github repository](https://github.com/bitpay/insight-api).
|
||||
|
||||
## Caching schema
|
||||
|
||||
Since v0.2 a new cache schema has been introduced. Only information from transactions with
|
||||
INSIGHT_SAFE_CONFIRMATIONS settings will be cached (by default SAFE_CONFIRMATIONS=6). There
|
||||
are 3 different caches:
|
||||
* Number of confirmations
|
||||
* Transaction output spent/unspent status
|
||||
* scriptPubKey for unspent transactions
|
||||
|
||||
Cache data is only populated on request, i.e., only after accessing the required data for
|
||||
the first time, the information is cached, there is not pre-caching procedure. To ignore
|
||||
cache by default, use INSIGHT_IGNORE_CACHE. Also, address related calls support `?noCache=1`
|
||||
to ignore the cache in a particular API request.
|
||||
|
||||
## API
|
||||
|
||||
By default, insight provides a REST API at `/api`, but this prefix is configurable from the var `apiPrefix` in the `config.js` file.
|
||||
|
||||
The end-points are:
|
||||
Or disabled entirely with:
|
||||
``` json
|
||||
"servicesConfig": {
|
||||
"flosight-api": {
|
||||
"disableRateLimiter": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## API HTTP Endpoints
|
||||
|
||||
### Block
|
||||
```
|
||||
/api/block/[:hash]
|
||||
/api/block/00000000a967199a2fad0877433c93df785a8d8ce062e5f9b451cd1397bdbf62
|
||||
```
|
||||
### Block index
|
||||
|
||||
### Block Index
|
||||
Get block hash by height
|
||||
```
|
||||
/api/block-index/[:height]
|
||||
@ -187,22 +137,77 @@ Get block hash by height
|
||||
```
|
||||
This would return:
|
||||
```
|
||||
{"blockHash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"}
|
||||
{
|
||||
"blockHash":"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||
}
|
||||
```
|
||||
which is the hash of the Genesis block (0 height)
|
||||
|
||||
|
||||
### Raw Block
|
||||
```
|
||||
/api/rawblock/[:blockHash]
|
||||
/api/rawblock/[:blockHeight]
|
||||
```
|
||||
|
||||
This would return:
|
||||
```
|
||||
{
|
||||
"rawblock":"blockhexstring..."
|
||||
}
|
||||
```
|
||||
|
||||
### Block Summaries
|
||||
|
||||
Get block summaries by date:
|
||||
```
|
||||
/api/blocks?limit=3&blockDate=2016-04-22
|
||||
```
|
||||
|
||||
Example response:
|
||||
```
|
||||
{
|
||||
"blocks": [
|
||||
{
|
||||
"height": 408495,
|
||||
"size": 989237,
|
||||
"hash": "00000000000000000108a1f4d4db839702d72f16561b1154600a26c453ecb378",
|
||||
"time": 1461360083,
|
||||
"txlength": 1695,
|
||||
"poolInfo": {
|
||||
"poolName": "BTCC Pool",
|
||||
"url": "https://pool.btcc.com/"
|
||||
}
|
||||
}
|
||||
],
|
||||
"length": 1,
|
||||
"pagination": {
|
||||
"next": "2016-04-23",
|
||||
"prev": "2016-04-21",
|
||||
"currentTs": 1461369599,
|
||||
"current": "2016-04-22",
|
||||
"isToday": true,
|
||||
"more": true,
|
||||
"moreTs": 1461369600
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Transaction
|
||||
```
|
||||
/api/tx/[:txid]
|
||||
/api/tx/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c
|
||||
/api/raw/[:rawid]
|
||||
/api/raw/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c
|
||||
/api/rawtx/[:rawid]
|
||||
/api/rawtx/525de308971eabd941b139f46c7198b5af9479325c2395db7f2fb5ae8562556c
|
||||
```
|
||||
|
||||
### Address
|
||||
```
|
||||
/api/addr/[:addr][?noTxList=1&noCache=1]
|
||||
/api/addr/[:addr][?noTxList=1][&from=&to=]
|
||||
/api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?noTxList=1
|
||||
/api/addr/mmvP3mTe53qxHdPqXEvdu8WdC7GfQ2vmx5?from=1000&to=2000
|
||||
```
|
||||
|
||||
### Address Properties
|
||||
```
|
||||
/api/addr/[:addr]/balance
|
||||
@ -211,39 +216,38 @@ which is the hash of the Genesis block (0 height)
|
||||
/api/addr/[:addr]/unconfirmedBalance
|
||||
```
|
||||
The response contains the value in Satoshis.
|
||||
|
||||
### Unspent Outputs
|
||||
```
|
||||
/api/addr/[:addr]/utxo[?noCache=1]
|
||||
/api/addr/[:addr]/utxo
|
||||
```
|
||||
Sample return:
|
||||
``` json
|
||||
```
|
||||
[
|
||||
{
|
||||
"address": "n2PuaAguxZqLddRbTnAoAuwKYgN2w2hZk7",
|
||||
"txid": "dbfdc2a0d22a8282c4e7be0452d595695f3a39173bed4f48e590877382b112fc",
|
||||
"vout": 0,
|
||||
"ts": 1401276201,
|
||||
"scriptPubKey": "76a914e50575162795cd77366fb80d728e3216bd52deac88ac",
|
||||
"amount": 0.001,
|
||||
"confirmations": 3
|
||||
},
|
||||
{
|
||||
"address": "n2PuaAguxZqLddRbTnAoAuwKYgN2w2hZk7",
|
||||
"txid": "e2b82af55d64f12fd0dd075d0922ee7d6a300f58fe60a23cbb5831b31d1d58b4",
|
||||
"vout": 0,
|
||||
"ts": 1401226410,
|
||||
"scriptPubKey": "76a914e50575162795cd77366fb80d728e3216bd52deac88ac",
|
||||
"amount": 0.001,
|
||||
"confirmation": 6,
|
||||
"confirmationsFromCache": true
|
||||
}
|
||||
{
|
||||
"address":"mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||
"txid":"d5f8a96faccf79d4c087fa217627bb1120e83f8ea1a7d84b1de4277ead9bbac1",
|
||||
"vout":0,
|
||||
"scriptPubKey":"76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||
"amount":0.000006,
|
||||
"satoshis":600,
|
||||
"confirmations":0,
|
||||
"ts":1461349425
|
||||
},
|
||||
{
|
||||
"address": "mo9ncXisMeAoXwqcV5EWuyncbmCcQN4rVs",
|
||||
"txid": "bc9df3b92120feaee4edc80963d8ed59d6a78ea0defef3ec3cb374f2015bfc6e",
|
||||
"vout": 1,
|
||||
"scriptPubKey": "76a91453c0307d6851aa0ce7825ba883c6bd9ad242b48688ac",
|
||||
"amount": 0.12345678,
|
||||
"satoshis: 12345678,
|
||||
"confirmations": 1,
|
||||
"height": 300001
|
||||
}
|
||||
]
|
||||
```
|
||||
Please note that in case confirmations are cached (which happens by default when the number of confirmations is bigger that INSIGHT_SAFE_CONFIRMATIONS) the response will include the pair confirmationsFromCache:true, and confirmations will equal INSIGHT_SAFE_CONFIRMATIONS. See noCache and INSIGHT_IGNORE_CACHE options for details.
|
||||
|
||||
|
||||
|
||||
### Unspent Outputs for multiple addresses
|
||||
### Unspent Outputs for Multiple Addresses
|
||||
GET method:
|
||||
```
|
||||
/api/addrs/[:addrs]/utxo
|
||||
@ -271,7 +275,7 @@ addrs: 2NF2baYuJAkCKo5onjUKEPdARQkZ6SYyKd5,2NAre8sX2povnjy4aeiHKeEh97Qhn97tB1f
|
||||
/api/txs/?address=mmhmMNfBiZZ37g1tgg2t8DDbNoEdqKVxAL
|
||||
```
|
||||
|
||||
### Transactions for multiple addresses
|
||||
### Transactions for Multiple Addresses
|
||||
GET method:
|
||||
```
|
||||
/api/addrs/[:addrs]/txs[?from=&to=]
|
||||
@ -288,6 +292,9 @@ POST params:
|
||||
addrs: 2NF2baYuJAkCKo5onjUKEPdARQkZ6SYyKd5,2NAre8sX2povnjy4aeiHKeEh97Qhn97tB1f
|
||||
from (optional): 0
|
||||
to (optional): 20
|
||||
noAsm (optional): 1 (will omit script asm from results)
|
||||
noScriptSig (optional): 1 (will omit the scriptSig from all inputs)
|
||||
noSpent (option): 1 (will omit spent information per output)
|
||||
```
|
||||
|
||||
Sample output:
|
||||
@ -320,8 +327,7 @@ Sample output:
|
||||
|
||||
Note: if pagination params are not specified, the result is an array of transactions.
|
||||
|
||||
|
||||
### Transaction broadcasting
|
||||
### Transaction Broadcasting
|
||||
POST method:
|
||||
```
|
||||
/api/tx/send
|
||||
@ -348,17 +354,17 @@ POST response:
|
||||
}
|
||||
```
|
||||
|
||||
### Historic blockchain data sync status
|
||||
### Historic Blockchain Data Sync Status
|
||||
```
|
||||
/api/sync
|
||||
```
|
||||
|
||||
### Live network p2p data sync status
|
||||
### Live Network P2P Data Sync Status
|
||||
```
|
||||
/api/peer
|
||||
```
|
||||
|
||||
### Status of the bitcoin network
|
||||
### Status of the Florincoin Network
|
||||
```
|
||||
/api/status?q=xxx
|
||||
```
|
||||
@ -367,12 +373,11 @@ Where "xxx" can be:
|
||||
|
||||
* getInfo
|
||||
* getDifficulty
|
||||
* getTxOutSetInfo
|
||||
* getBestBlockHash
|
||||
* getLastBlockHash
|
||||
|
||||
|
||||
### Utility methods
|
||||
### Utility Methods
|
||||
```
|
||||
/api/utils/estimatefee[?nbBlocks=2]
|
||||
```
|
||||
@ -381,9 +386,9 @@ Where "xxx" can be:
|
||||
## Web Socket API
|
||||
The web socket API is served using [socket.io](http://socket.io).
|
||||
|
||||
The following are the events published by insight:
|
||||
The following are the events published by flosight:
|
||||
|
||||
'tx': new transaction received from network. This event is published in the 'inv' room. Data will be a app/models/Transaction object.
|
||||
`tx`: new transaction received from network. This event is published in the 'inv' room. Data will be a app/models/Transaction object.
|
||||
Sample output:
|
||||
```
|
||||
{
|
||||
@ -394,7 +399,7 @@ Sample output:
|
||||
```
|
||||
|
||||
|
||||
'block': new block received from network. This event is published in the 'inv' room. Data will be a app/models/Block object.
|
||||
`block`: new block received from network. This event is published in the `inv` room. Data will be a app/models/Block object.
|
||||
Sample output:
|
||||
```
|
||||
{
|
||||
@ -404,9 +409,9 @@ Sample output:
|
||||
}
|
||||
```
|
||||
|
||||
'<bitcoinAddress>': new transaction concerning <bitcoinAddress> received from network. This event is published in the '<bitcoinAddress>' room.
|
||||
`<florincoinAddress>`: new transaction concerning <florincoinAddress> received from network. This event is published in the `<florincoinAddress>` room.
|
||||
|
||||
'status': every 1% increment on the sync task, this event will be triggered. This event is published in the 'sync' room.
|
||||
`status`: every 1% increment on the sync task, this event will be triggered. This event is published in the `sync` room.
|
||||
|
||||
Sample output:
|
||||
```
|
||||
@ -424,18 +429,18 @@ Sample output:
|
||||
|
||||
### Example Usage
|
||||
|
||||
The following html page connects to the socket.io insight API and listens for new transactions.
|
||||
The following html page connects to the socket.io flosight API and listens for new transactions.
|
||||
|
||||
html
|
||||
```
|
||||
<html>
|
||||
<body>
|
||||
<script src="http://<insight-server>:<port>/socket.io/socket.io.js"></script>
|
||||
<script src="http://<flosight-server>:<port>/socket.io/socket.io.js"></script>
|
||||
<script>
|
||||
eventToListenTo = 'tx'
|
||||
room = 'inv'
|
||||
|
||||
var socket = io("http://<insight-server>:<port>/");
|
||||
var socket = io("http://<flosight-server>:<port>/");
|
||||
socket.on('connect', function() {
|
||||
// Join the room.
|
||||
socket.emit('subscribe', room);
|
||||
|
||||
62
app/.angular-cli.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"project": {
|
||||
"name": "insight-ui"
|
||||
},
|
||||
"apps": [
|
||||
{
|
||||
"root": "src",
|
||||
"outDir": "dist",
|
||||
"assets": [
|
||||
"assets",
|
||||
"favicon.ico"
|
||||
],
|
||||
"index": "index.html",
|
||||
"main": "app/main.ts",
|
||||
"polyfills": "polyfills.ts",
|
||||
"test": "test.ts",
|
||||
"tsconfig": "tsconfig.app.json",
|
||||
"testTsconfig": "tsconfig.spec.json",
|
||||
"prefix": "app",
|
||||
"scripts": [],
|
||||
"environmentSource": "environments/environment.ts",
|
||||
"environments": {
|
||||
"dev": "environments/environment.ts",
|
||||
"prod": "environments/environment.prod.ts"
|
||||
}
|
||||
}
|
||||
],
|
||||
"e2e": {
|
||||
"protractor": {
|
||||
"config": "./protractor.conf.js"
|
||||
}
|
||||
},
|
||||
"lint": [
|
||||
{
|
||||
"project": "src/tsconfig.app.json"
|
||||
},
|
||||
{
|
||||
"project": "src/tsconfig.spec.json"
|
||||
},
|
||||
{
|
||||
"project": "e2e/tsconfig.e2e.json"
|
||||
}
|
||||
],
|
||||
"test": {
|
||||
"karma": {
|
||||
"config": "./karma.conf.js"
|
||||
},
|
||||
"codeCoverage": {
|
||||
"exclude": [
|
||||
"src/polyfills.ts",
|
||||
"src/test.ts",
|
||||
"src/mocks.ts",
|
||||
"**/*.mock.ts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaults": {
|
||||
"styleExt": "css",
|
||||
"component": {}
|
||||
}
|
||||
}
|
||||
40
app/.gitignore
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
# Specifies intentionally untracked files to ignore when using Git
|
||||
# http://git-scm.com/docs/gitignore
|
||||
|
||||
*~
|
||||
*.sw[mnpcod]
|
||||
*.log
|
||||
*.tmp
|
||||
*.tmp.*
|
||||
log.txt
|
||||
*.sublime-project
|
||||
*.sublime-workspace
|
||||
.vscode/
|
||||
npm-debug.log*
|
||||
|
||||
.sourcemaps/
|
||||
|
||||
.idea/
|
||||
.sass-cache/
|
||||
.tmp/
|
||||
.versions/
|
||||
coverage/
|
||||
dist/
|
||||
node_modules/
|
||||
tmp/
|
||||
temp/
|
||||
hooks/
|
||||
platforms/
|
||||
plugins/
|
||||
plugins/android.json
|
||||
plugins/ios.json
|
||||
www/
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# e2e
|
||||
/e2e/*.js
|
||||
/e2e/*.map
|
||||
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
UserInterfaceState.xcuserstate
|
||||
24
app/README.md
Normal file
@ -0,0 +1,24 @@
|
||||
[](https://travis-ci.org/bitpay/insight-ui)
|
||||
[](https://codecov.io/github/bitpay/insight-ui?branch=ionic)
|
||||
|
||||
# Insight UI
|
||||
|
||||
## Install & Start
|
||||
You need to be running [the latest node LTS](https://nodejs.org/en/download/) or newer.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/bitpay/insight-ui.git
|
||||
cd insight-ui/app
|
||||
npm install
|
||||
npm start
|
||||
```
|
||||
|
||||
## Run Unit Tests
|
||||
```bash
|
||||
npm test
|
||||
```
|
||||
|
||||
## Run E2E
|
||||
```
|
||||
npm run e2e
|
||||
```
|
||||
@ -1,284 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var _ = require('lodash');
|
||||
var Address = require('../models/Address');
|
||||
var common = require('./common');
|
||||
var async = require('async');
|
||||
|
||||
var MAX_BATCH_SIZE = 100;
|
||||
var RPC_CONCURRENCY = 5;
|
||||
|
||||
var tDb = require('../../lib/TransactionDb').default();
|
||||
|
||||
var checkSync = function(req, res) {
|
||||
if (req.historicSync) {
|
||||
var i = req.historicSync.info()
|
||||
if (i.status !== 'finished') {
|
||||
common.notReady(req, res, i.syncPercentage);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
var getAddr = function(req, res, next) {
|
||||
var a;
|
||||
try {
|
||||
var addr = req.param('addr');
|
||||
a = new Address(addr);
|
||||
} catch (e) {
|
||||
common.handleErrors({
|
||||
message: 'Invalid address:' + e.message,
|
||||
code: 1
|
||||
}, res, next);
|
||||
return null;
|
||||
}
|
||||
return a;
|
||||
};
|
||||
|
||||
var getAddrs = function(req, res, next) {
|
||||
var as = [];
|
||||
try {
|
||||
var addrStrs = req.param('addrs');
|
||||
var s = addrStrs.split(',');
|
||||
if (s.length === 0) return as;
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
var a = new Address(s[i]);
|
||||
as.push(a);
|
||||
}
|
||||
} catch (e) {
|
||||
common.handleErrors({
|
||||
message: 'Invalid addrs param:' + e.message,
|
||||
code: 1
|
||||
}, res, next);
|
||||
return null;
|
||||
}
|
||||
return as;
|
||||
};
|
||||
|
||||
exports.show = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
var a = getAddr(req, res, next);
|
||||
|
||||
if (a) {
|
||||
a.update(function(err) {
|
||||
if (err) {
|
||||
return common.handleErrors(err, res);
|
||||
} else {
|
||||
return res.jsonp(a.getObj());
|
||||
}
|
||||
}, {
|
||||
txLimit: req.query.noTxList ? 0 : -1,
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
exports.utxo = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
|
||||
var a = getAddr(req, res, next);
|
||||
if (a) {
|
||||
a.update(function(err) {
|
||||
if (err)
|
||||
return common.handleErrors(err, res);
|
||||
else {
|
||||
return res.jsonp(a.unspent);
|
||||
}
|
||||
}, {
|
||||
onlyUnspent: 1,
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.multiutxo = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
var as = getAddrs(req, res, next);
|
||||
if (as) {
|
||||
var utxos = [];
|
||||
async.eachLimit(as, RPC_CONCURRENCY, function(a, callback) {
|
||||
a.update(function(err) {
|
||||
if (err) callback(err);
|
||||
utxos = utxos.concat(a.unspent);
|
||||
callback();
|
||||
}, {
|
||||
onlyUnspent: 1,
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
}, function(err) { // finished callback
|
||||
if (err) return common.handleErrors(err, res);
|
||||
res.jsonp(utxos);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.multitxs = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
|
||||
function processTxs(txs, from, to, cb) {
|
||||
txs = _.uniq(_.flatten(txs), 'txid');
|
||||
var nbTxs = txs.length;
|
||||
|
||||
if (_.isUndefined(from) && _.isUndefined(to)) {
|
||||
from = 0;
|
||||
to = MAX_BATCH_SIZE;
|
||||
}
|
||||
if (!_.isUndefined(from) && _.isUndefined(to))
|
||||
to = from + MAX_BATCH_SIZE;
|
||||
|
||||
if (!_.isUndefined(from) && !_.isUndefined(to) && to - from > MAX_BATCH_SIZE)
|
||||
to = from + MAX_BATCH_SIZE;
|
||||
|
||||
if (from < 0) from = 0;
|
||||
if (to < 0) to = 0;
|
||||
if (from > nbTxs) from = nbTxs;
|
||||
if (to > nbTxs) to = nbTxs;
|
||||
|
||||
txs.sort(function(a, b) {
|
||||
var b = (b.firstSeenTs || b.ts)+ b.txid;
|
||||
var a = (a.firstSeenTs || a.ts)+ a.txid;
|
||||
if (a > b) return -1;
|
||||
if (a < b) return 1;
|
||||
return 0;
|
||||
});
|
||||
txs = txs.slice(from, to);
|
||||
|
||||
var txIndex = {};
|
||||
_.each(txs, function(tx) {
|
||||
txIndex[tx.txid] = tx;
|
||||
});
|
||||
|
||||
async.eachLimit(txs, RPC_CONCURRENCY, function(tx2, callback) {
|
||||
tDb.fromIdWithInfo(tx2.txid, function(err, tx) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return common.handleErrors(err, res);
|
||||
}
|
||||
if (tx && tx.info) {
|
||||
|
||||
if (tx2.firstSeenTs)
|
||||
tx.info.firstSeenTs = tx2.firstSeenTs;
|
||||
|
||||
txIndex[tx.txid].info = tx.info;
|
||||
} else {
|
||||
// TX no longer available
|
||||
txIndex[tx2.txid].info = {
|
||||
txid: tx2.txid,
|
||||
possibleDoubleSpend: true,
|
||||
firstSeenTs: tx2.firstSeenTs,
|
||||
};
|
||||
}
|
||||
|
||||
callback();
|
||||
});
|
||||
}, function(err) {
|
||||
if (err) return cb(err);
|
||||
|
||||
// It could be that a txid is stored at an address but it is
|
||||
// no longer at bitcoind (for example a double spend)
|
||||
|
||||
var transactions = _.compact(_.pluck(txs, 'info'));
|
||||
transactions = {
|
||||
totalItems: nbTxs,
|
||||
from: +from,
|
||||
to: +to,
|
||||
items: transactions,
|
||||
};
|
||||
return cb(null, transactions);
|
||||
});
|
||||
};
|
||||
|
||||
var from = req.param('from');
|
||||
var to = req.param('to');
|
||||
|
||||
var as = getAddrs(req, res, next);
|
||||
if (as) {
|
||||
var txs = [];
|
||||
async.eachLimit(as, RPC_CONCURRENCY, function(a, callback) {
|
||||
a.update(function(err) {
|
||||
if (err) callback(err);
|
||||
|
||||
txs.push(a.transactions);
|
||||
callback();
|
||||
}, {
|
||||
ignoreCache: req.param('noCache'),
|
||||
includeTxInfo: true,
|
||||
});
|
||||
}, function(err) { // finished callback
|
||||
if (err) return common.handleErrors(err, res);
|
||||
|
||||
processTxs(txs, from, to, function(err, transactions) {
|
||||
if (err) return common.handleErrors(err, res);
|
||||
res.jsonp(transactions);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
exports.balance = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
var a = getAddr(req, res, next);
|
||||
if (a)
|
||||
a.update(function(err) {
|
||||
if (err) {
|
||||
return common.handleErrors(err, res);
|
||||
} else {
|
||||
return res.jsonp(a.balanceSat);
|
||||
}
|
||||
}, {
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
};
|
||||
|
||||
exports.totalReceived = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
var a = getAddr(req, res, next);
|
||||
if (a)
|
||||
a.update(function(err) {
|
||||
if (err) {
|
||||
return common.handleErrors(err, res);
|
||||
} else {
|
||||
return res.jsonp(a.totalReceivedSat);
|
||||
}
|
||||
}, {
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
};
|
||||
|
||||
exports.totalSent = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
var a = getAddr(req, res, next);
|
||||
if (a)
|
||||
a.update(function(err) {
|
||||
if (err) {
|
||||
return common.handleErrors(err, res);
|
||||
} else {
|
||||
return res.jsonp(a.totalSentSat);
|
||||
}
|
||||
}, {
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
};
|
||||
|
||||
exports.unconfirmedBalance = function(req, res, next) {
|
||||
if (!checkSync(req, res)) return;
|
||||
var a = getAddr(req, res, next);
|
||||
if (a)
|
||||
a.update(function(err) {
|
||||
if (err) {
|
||||
return common.handleErrors(err, res);
|
||||
} else {
|
||||
return res.jsonp(a.unconfirmedBalanceSat);
|
||||
}
|
||||
}, {
|
||||
ignoreCache: req.param('noCache')
|
||||
});
|
||||
};
|
||||
@ -1,173 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var common = require('./common');
|
||||
var async = require('async');
|
||||
var bdb = require('../../lib/BlockDb').default();
|
||||
var tdb = require('../../lib/TransactionDb').default();
|
||||
|
||||
/**
|
||||
* Find block by hash ...
|
||||
*/
|
||||
exports.block = function(req, res, next, hash) {
|
||||
bdb.fromHashWithInfo(hash, function(err, block) {
|
||||
if (err || !block)
|
||||
return common.handleErrors(err, res, next);
|
||||
else {
|
||||
tdb.getPoolInfo(block.info.tx[0], function(info) {
|
||||
block.info.poolInfo = info;
|
||||
req.block = block.info;
|
||||
return next();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show block
|
||||
*/
|
||||
exports.show = function(req, res) {
|
||||
if (req.block) {
|
||||
res.jsonp(req.block);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show block by Height
|
||||
*/
|
||||
exports.blockindex = function(req, res, next, height) {
|
||||
bdb.blockIndex(height, function(err, hashStr) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
res.status(400).send('Bad Request'); // TODO
|
||||
} else {
|
||||
res.jsonp(hashStr);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var getBlock = function(blockhash, cb) {
|
||||
bdb.fromHashWithInfo(blockhash, function(err, block) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return cb(err);
|
||||
}
|
||||
|
||||
// TODO
|
||||
if (!block.info) {
|
||||
console.log('Could not get %s from RPC. Orphan? Error?', blockhash); //TODO
|
||||
// Probably orphan
|
||||
block.info = {
|
||||
hash: blockhash,
|
||||
isOrphan: 1,
|
||||
};
|
||||
}
|
||||
|
||||
tdb.getPoolInfo(block.info.tx[0], function(info) {
|
||||
block.info.poolInfo = info;
|
||||
return cb(err, block.info);
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* List of blocks by date
|
||||
*/
|
||||
|
||||
var DFLT_LIMIT=200;
|
||||
// in testnet, this number is much bigger, we dont support
|
||||
// exploring blocks by date.
|
||||
|
||||
exports.list = function(req, res) {
|
||||
var isToday = false;
|
||||
|
||||
//helper to convert timestamps to yyyy-mm-dd format
|
||||
var formatTimestamp = function(date) {
|
||||
var yyyy = date.getUTCFullYear().toString();
|
||||
var mm = (date.getUTCMonth() + 1).toString(); // getMonth() is zero-based
|
||||
var dd = date.getUTCDate().toString();
|
||||
|
||||
return yyyy + '-' + (mm[1] ? mm : '0' + mm[0]) + '-' + (dd[1] ? dd : '0' + dd[0]); //padding
|
||||
};
|
||||
|
||||
var dateStr;
|
||||
var todayStr = formatTimestamp(new Date());
|
||||
|
||||
if (req.query.blockDate) {
|
||||
// TODO: Validate format yyyy-mm-dd
|
||||
dateStr = req.query.blockDate;
|
||||
isToday = dateStr === todayStr;
|
||||
} else {
|
||||
dateStr = todayStr;
|
||||
isToday = true;
|
||||
}
|
||||
var gte = Math.round((new Date(dateStr)).getTime() / 1000);
|
||||
|
||||
//pagination
|
||||
var lte = parseInt(req.query.startTimestamp) || gte + 86400;
|
||||
var prev = formatTimestamp(new Date((gte - 86400) * 1000));
|
||||
var next = lte ? formatTimestamp(new Date(lte * 1000)) :null;
|
||||
var limit = parseInt(req.query.limit || DFLT_LIMIT) + 1;
|
||||
var more;
|
||||
|
||||
bdb.getBlocksByDate(gte, lte, limit, function(err, blockList) {
|
||||
|
||||
if (err) {
|
||||
res.status(500).send(err);
|
||||
} else {
|
||||
var l = blockList.length;
|
||||
|
||||
if (l===limit) {
|
||||
more = true;
|
||||
blockList.pop;
|
||||
}
|
||||
|
||||
var moreTs=lte;
|
||||
async.mapSeries(blockList,
|
||||
function(b, cb) {
|
||||
getBlock(b.hash, function(err, info) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return cb(err);
|
||||
}
|
||||
if (b.ts < moreTs) moreTs = b.ts;
|
||||
return cb(err, {
|
||||
height: info.height,
|
||||
size: info.size,
|
||||
hash: b.hash,
|
||||
time: b.ts || info.time,
|
||||
txlength: info.tx.length,
|
||||
poolInfo: info.poolInfo
|
||||
});
|
||||
});
|
||||
}, function(err, allblocks) {
|
||||
|
||||
// sort blocks by height
|
||||
allblocks.sort(
|
||||
function compare(a,b) {
|
||||
if (a.height < b.height) return 1;
|
||||
if (a.height > b.height) return -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
res.jsonp({
|
||||
blocks: allblocks,
|
||||
length: allblocks.length,
|
||||
pagination: {
|
||||
next: next,
|
||||
prev: prev,
|
||||
currentTs: lte - 1,
|
||||
current: dateStr,
|
||||
isToday: isToday,
|
||||
more: more,
|
||||
moreTs: moreTs,
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@ -1,19 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
exports.notReady = function (err, res, p) {
|
||||
res.status(503).send('Server not yet ready. Sync Percentage:' + p);
|
||||
};
|
||||
|
||||
exports.handleErrors = function (err, res) {
|
||||
if (err) {
|
||||
if (err.code) {
|
||||
res.status(400).send(err.message + '. Code:' + err.code);
|
||||
}
|
||||
else {
|
||||
res.status(503).send(err.message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
res.status(404).send('Not found');
|
||||
}
|
||||
};
|
||||
@ -1,60 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var config = require('../../config/config');
|
||||
|
||||
// Set the initial vars
|
||||
var timestamp = +new Date(),
|
||||
delay = config.currencyRefresh * 60000,
|
||||
bitstampRate = 0;
|
||||
|
||||
exports.index = function(req, res) {
|
||||
|
||||
var _xhr = function() {
|
||||
if (typeof XMLHttpRequest !== 'undefined' && XMLHttpRequest !== null) {
|
||||
return new XMLHttpRequest();
|
||||
} else if (typeof require !== 'undefined' && require !== null) {
|
||||
var XMLhttprequest = require('xmlhttprequest').XMLHttpRequest;
|
||||
return new XMLhttprequest();
|
||||
}
|
||||
};
|
||||
|
||||
var _request = function(url, cb) {
|
||||
var request;
|
||||
request = _xhr();
|
||||
request.open('GET', url, true);
|
||||
request.onreadystatechange = function() {
|
||||
if (request.readyState === 4) {
|
||||
if (request.status === 200) {
|
||||
return cb(false, request.responseText);
|
||||
}
|
||||
|
||||
return cb(true, {
|
||||
status: request.status,
|
||||
message: 'Request error'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return request.send(null);
|
||||
};
|
||||
|
||||
// Init
|
||||
var currentTime = +new Date();
|
||||
if (bitstampRate === 0 || currentTime >= (timestamp + delay)) {
|
||||
timestamp = currentTime;
|
||||
|
||||
_request('https://www.bitstamp.net/api/ticker/', function(err, data) {
|
||||
if (!err) bitstampRate = parseFloat(JSON.parse(data).last);
|
||||
|
||||
res.jsonp({
|
||||
status: 200,
|
||||
data: { bitstamp: bitstampRate }
|
||||
});
|
||||
});
|
||||
} else {
|
||||
res.jsonp({
|
||||
status: 200,
|
||||
data: { bitstamp: bitstampRate }
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,26 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var config = require('../../config/config');
|
||||
|
||||
var _getVersion = function() {
|
||||
var pjson = require('../../package.json');
|
||||
return pjson.version;
|
||||
};
|
||||
|
||||
exports.render = function(req, res) {
|
||||
|
||||
if (config.publicPath) {
|
||||
return res.sendfile(config.publicPath + '/index.html');
|
||||
}
|
||||
else {
|
||||
var version = _getVersion();
|
||||
res.send('insight API v' + version);
|
||||
}
|
||||
};
|
||||
|
||||
exports.version = function(req, res) {
|
||||
var version = _getVersion();
|
||||
res.json({
|
||||
version: version
|
||||
});
|
||||
};
|
||||
@ -1,27 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var common = require('./common');
|
||||
var Rpc = require('../../lib/Rpc');
|
||||
|
||||
|
||||
exports.verify = function(req, res) {
|
||||
var address = req.param('address'),
|
||||
signature = req.param('signature'),
|
||||
message = req.param('message');
|
||||
|
||||
if(typeof(address) == 'undefined'
|
||||
|| typeof(signature) == 'undefined'
|
||||
|| typeof(message) == 'undefined') {
|
||||
return common.handleErrors({
|
||||
message: 'Missing parameters (expected "address", "signature" and "message")',
|
||||
code: 1
|
||||
}, res);
|
||||
}
|
||||
|
||||
Rpc.verifyMessage(address, signature, message, function(err, result) {
|
||||
if (err) {
|
||||
return common.handleErrors(err, res);
|
||||
}
|
||||
res.json({'result' : result});
|
||||
});
|
||||
};
|
||||
@ -1,80 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// server-side socket behaviour
|
||||
var ios = null; // io is already taken in express
|
||||
var util = require('bitcore').util;
|
||||
var logger = require('../../lib/logger').logger;
|
||||
|
||||
module.exports.init = function(io_ext) {
|
||||
ios = io_ext;
|
||||
if (ios) {
|
||||
// when a new socket connects
|
||||
ios.sockets.on('connection', function(socket) {
|
||||
logger.verbose('New connection from ' + socket.id);
|
||||
// when it subscribes, make it join the according room
|
||||
socket.on('subscribe', function(topic) {
|
||||
logger.debug('subscribe to ' + topic);
|
||||
socket.join(topic);
|
||||
socket.emit('subscribed');
|
||||
});
|
||||
|
||||
// disconnect handler
|
||||
socket.on('disconnect', function() {
|
||||
logger.verbose('disconnected ' + socket.id);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
return ios;
|
||||
};
|
||||
|
||||
var simpleTx = function(tx) {
|
||||
return {
|
||||
txid: tx
|
||||
};
|
||||
};
|
||||
|
||||
var fullTx = function(tx) {
|
||||
var t = {
|
||||
txid: tx.txid,
|
||||
size: tx.size,
|
||||
};
|
||||
// Outputs
|
||||
var valueOut = 0;
|
||||
tx.vout.forEach(function(o) {
|
||||
valueOut += o.valueSat;
|
||||
});
|
||||
|
||||
t.valueOut = (valueOut.toFixed(8) / util.COIN);
|
||||
try {
|
||||
t.vout = tx.vout.map(function(o) {
|
||||
var r = {};
|
||||
r[o.scriptPubKey.addresses] = o.valueSat;
|
||||
return r;
|
||||
});
|
||||
} catch (e) {};
|
||||
return t;
|
||||
};
|
||||
|
||||
module.exports.broadcastTx = function(tx) {
|
||||
if (ios) {
|
||||
var t = (typeof tx === 'string') ? simpleTx(tx) : fullTx(tx);
|
||||
ios.sockets.in('inv').emit('tx', t);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.broadcastBlock = function(block) {
|
||||
if (ios)
|
||||
ios.sockets.in('inv').emit('block', block);
|
||||
};
|
||||
|
||||
module.exports.broadcastAddressTx = function(txid, address) {
|
||||
if (ios) {
|
||||
ios.sockets.in(address).emit(address, txid);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.broadcastSyncInfo = function(historicSync) {
|
||||
if (ios)
|
||||
ios.sockets.in('sync').emit('status', historicSync);
|
||||
};
|
||||
@ -1,55 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Status = require('../models/Status'),
|
||||
common = require('./common');
|
||||
|
||||
/**
|
||||
* Status
|
||||
*/
|
||||
exports.show = function(req, res) {
|
||||
|
||||
var option = req.query.q;
|
||||
var statusObject = new Status();
|
||||
|
||||
var returnJsonp = function (err) {
|
||||
if (err || ! statusObject)
|
||||
return common.handleErrors(err, res);
|
||||
else {
|
||||
res.jsonp(statusObject);
|
||||
}
|
||||
};
|
||||
|
||||
switch(option) {
|
||||
case 'getDifficulty':
|
||||
statusObject.getDifficulty(returnJsonp);
|
||||
break;
|
||||
case 'getTxOutSetInfo':
|
||||
statusObject.getTxOutSetInfo(returnJsonp);
|
||||
break;
|
||||
case 'getLastBlockHash':
|
||||
statusObject.getLastBlockHash(returnJsonp);
|
||||
break;
|
||||
case 'getBestBlockHash':
|
||||
statusObject.getBestBlockHash(returnJsonp);
|
||||
break;
|
||||
case 'getInfo':
|
||||
default:
|
||||
statusObject.getInfo(returnJsonp);
|
||||
}
|
||||
};
|
||||
|
||||
exports.sync = function(req, res) {
|
||||
if (req.historicSync)
|
||||
res.jsonp(req.historicSync.info());
|
||||
};
|
||||
|
||||
exports.peer = function(req, res) {
|
||||
if (req.peerSync) {
|
||||
var info = req.peerSync.info();
|
||||
res.jsonp(info);
|
||||
}
|
||||
};
|
||||
@ -1,197 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
var Address = require('../models/Address');
|
||||
var async = require('async');
|
||||
var common = require('./common');
|
||||
var util = require('util');
|
||||
|
||||
var Rpc = require('../../lib/Rpc');
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var bitcore = require('bitcore');
|
||||
var RpcClient = bitcore.RpcClient;
|
||||
var config = require('../../config/config');
|
||||
var bitcoreRpc = imports.bitcoreRpc || new RpcClient(config.bitcoind);
|
||||
|
||||
var tDb = require('../../lib/TransactionDb').default();
|
||||
var bdb = require('../../lib/BlockDb').default();
|
||||
|
||||
exports.send = function(req, res) {
|
||||
Rpc.sendRawTransaction(req.body.rawtx, function(err, txid) {
|
||||
if (err) {
|
||||
var message;
|
||||
if(err.code == -25) {
|
||||
message = util.format(
|
||||
'Generic error %s (code %s)',
|
||||
err.message, err.code);
|
||||
} else if(err.code == -26) {
|
||||
message = util.format(
|
||||
'Transaction rejected by network (code %s). Reason: %s',
|
||||
err.code, err.message);
|
||||
} else {
|
||||
message = util.format('%s (code %s)', err.message, err.code);
|
||||
}
|
||||
return res.status(400).send(message);
|
||||
}
|
||||
res.json({'txid' : txid});
|
||||
});
|
||||
};
|
||||
|
||||
exports.rawTransaction = function (req, res, next, txid) {
|
||||
bitcoreRpc.getRawTransaction(txid, function (err, transaction) {
|
||||
if (err || !transaction)
|
||||
return common.handleErrors(err, res);
|
||||
else {
|
||||
req.rawTransaction = { 'rawtx': transaction.result };
|
||||
return next();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Find transaction by hash ...
|
||||
*/
|
||||
exports.transaction = function(req, res, next, txid) {
|
||||
|
||||
tDb.fromIdWithInfo(txid, function(err, tx) {
|
||||
if (err || ! tx)
|
||||
return common.handleErrors(err, res);
|
||||
|
||||
bdb.fillVinConfirmations(tx.info, function(err) {
|
||||
if (err)
|
||||
return common.handleErrors(err, res);
|
||||
|
||||
req.transaction = tx.info;
|
||||
return next();
|
||||
});
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Show transaction
|
||||
*/
|
||||
exports.show = function(req, res) {
|
||||
|
||||
if (req.transaction) {
|
||||
res.jsonp(req.transaction);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show raw transaction
|
||||
*/
|
||||
exports.showRaw = function(req, res) {
|
||||
|
||||
if (req.rawTransaction) {
|
||||
res.jsonp(req.rawTransaction);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var getTransaction = function(txid, cb) {
|
||||
|
||||
tDb.fromIdWithInfo(txid, function(err, tx) {
|
||||
if (err) console.log(err);
|
||||
|
||||
if (!tx || !tx.info) {
|
||||
console.log('[transactions.js.48]:: TXid %s not found in RPC. CHECK THIS.', txid);
|
||||
return ({ txid: txid });
|
||||
}
|
||||
|
||||
return cb(null, tx.info);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* List of transaction
|
||||
*/
|
||||
exports.list = function(req, res, next) {
|
||||
var bId = req.query.block;
|
||||
var addrStr = req.query.address;
|
||||
var page = req.query.pageNum;
|
||||
var pageLength = 10;
|
||||
var pagesTotal = 1;
|
||||
var txLength;
|
||||
var txs;
|
||||
|
||||
if (bId) {
|
||||
bdb.fromHashWithInfo(bId, function(err, block) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return res.status(500).send('Internal Server Error');
|
||||
}
|
||||
|
||||
if (! block) {
|
||||
return res.status(404).send('Not found');
|
||||
}
|
||||
|
||||
txLength = block.info.tx.length;
|
||||
|
||||
if (page) {
|
||||
var spliceInit = page * pageLength;
|
||||
txs = block.info.tx.splice(spliceInit, pageLength);
|
||||
pagesTotal = Math.ceil(txLength / pageLength);
|
||||
}
|
||||
else {
|
||||
txs = block.info.tx;
|
||||
}
|
||||
|
||||
async.mapSeries(txs, getTransaction, function(err, results) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
res.status(404).send('TX not found');
|
||||
}
|
||||
|
||||
res.jsonp({
|
||||
pagesTotal: pagesTotal,
|
||||
txs: results
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
else if (addrStr) {
|
||||
var a = new Address(addrStr);
|
||||
|
||||
a.update(function(err) {
|
||||
if (err && !a.totalReceivedSat) {
|
||||
console.log(err);
|
||||
res.status(404).send('Invalid address');
|
||||
return next();
|
||||
}
|
||||
|
||||
txLength = a.transactions.length;
|
||||
|
||||
if (page) {
|
||||
var spliceInit = page * pageLength;
|
||||
txs = a.transactions.splice(spliceInit, pageLength);
|
||||
pagesTotal = Math.ceil(txLength / pageLength);
|
||||
}
|
||||
else {
|
||||
txs = a.transactions;
|
||||
}
|
||||
|
||||
async.mapSeries(txs, getTransaction, function(err, results) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
res.status(404).send('TX not found');
|
||||
}
|
||||
|
||||
res.jsonp({
|
||||
pagesTotal: pagesTotal,
|
||||
txs: results
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.jsonp({
|
||||
txs: []
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -1,21 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var _ = require('lodash');
|
||||
|
||||
var Utils = require('../models/Utils');
|
||||
var common = require('./common');
|
||||
|
||||
exports.estimateFee = function(req, res) {
|
||||
var args = req.query.nbBlocks || '2';
|
||||
var nbBlocks = args.split(',');
|
||||
|
||||
var utilsObject = new Utils();
|
||||
utilsObject.estimateFee(nbBlocks, function(err, fees) {
|
||||
if (err) return common.handleErrors(err, res);
|
||||
res.jsonp(fees);
|
||||
});
|
||||
};
|
||||
32
app/e2e/app.e2e-spec.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { browser, element, by } from 'protractor';
|
||||
|
||||
describe('InsightApp', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should have a title', () => {
|
||||
expect(browser.getTitle()).toEqual('Insight');
|
||||
});
|
||||
|
||||
it('should have {nav}', () => {
|
||||
expect(element(by.css('ion-navbar')).isPresent()).toEqual(true);
|
||||
});
|
||||
|
||||
it('has a menu button that displays the left menu', () => {
|
||||
element(by.css('.bar-button-menutoggle')).click()
|
||||
.then(() => {
|
||||
browser.driver.sleep(2000); // wait for the animation
|
||||
expect(element(by.css('ion-menu')).isPresent()).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
it('the left menu has a link with title Home', () => {
|
||||
element(by.css('.bar-button-menutoggle')).click()
|
||||
.then(() => {
|
||||
browser.driver.sleep(2000); // wait for the animation
|
||||
expect(element.all(by.css('ion-label')).first().getText()).toEqual('Home');
|
||||
});
|
||||
});
|
||||
});
|
||||
22
app/e2e/broadcastTxPage.e2e-spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { browser, element, by, ElementFinder } from 'protractor';
|
||||
|
||||
describe('BroadcastTxPage', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should have an input field', () => {
|
||||
element(by.css('.bar-button-menutoggle')).click().then(() => {
|
||||
browser.driver.sleep(2000); // wait for the animation
|
||||
element.all(by.className('input-wrapper')).then((items) => {
|
||||
items[2].click();
|
||||
browser.driver.sleep(2000); // wait for the animation
|
||||
let theElem = element.all(by.css('ion-label')).first;
|
||||
console.log(theElem);
|
||||
expect(element.all(by.css('ion-input')).first().isPresent()).toEqual(true);
|
||||
return items[1];
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
12
app/e2e/tsconfig.e2e.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../tsconfig.ng-cli.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
14
app/ionic.config.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "insight",
|
||||
"app_id": "",
|
||||
"type": "ionic-angular",
|
||||
"integrations": {
|
||||
"cordova": {}
|
||||
},
|
||||
"proxies": [
|
||||
{
|
||||
"path": "/api",
|
||||
"proxyUrl": "https://bch-insight.bitpay.com/api"
|
||||
}
|
||||
]
|
||||
}
|
||||
44
app/karma.conf.js
Normal file
@ -0,0 +1,44 @@
|
||||
// Karma configuration file, see link for more information
|
||||
// https://karma-runner.github.io/0.13/config/configuration-file.html
|
||||
|
||||
module.exports = function (config) {
|
||||
config.set({
|
||||
basePath: '',
|
||||
frameworks: ['jasmine', '@angular/cli'],
|
||||
plugins: [
|
||||
require('karma-jasmine'),
|
||||
require('karma-chrome-launcher'),
|
||||
require('karma-jasmine-html-reporter'),
|
||||
require('karma-coverage-istanbul-reporter'),
|
||||
require('@angular/cli/plugins/karma')
|
||||
],
|
||||
client:{
|
||||
clearContext: false // leave Jasmine Spec Runner output visible in browser
|
||||
},
|
||||
files: [
|
||||
{ pattern: './src/test.ts', watched: false }
|
||||
],
|
||||
preprocessors: {
|
||||
'./src/test.ts': ['@angular/cli']
|
||||
},
|
||||
mime: {
|
||||
'text/x-typescript': ['ts','tsx']
|
||||
},
|
||||
coverageIstanbulReporter: {
|
||||
reports: [ 'html', 'lcovonly' ],
|
||||
fixWebpackSourcePaths: true
|
||||
},
|
||||
angularCli: {
|
||||
environment: 'dev'
|
||||
},
|
||||
reporters: config.angularCli && config.angularCli.codeCoverage
|
||||
? ['progress', 'coverage-istanbul']
|
||||
: ['progress', 'kjhtml'],
|
||||
port: 9876,
|
||||
colors: true,
|
||||
logLevel: config.LOG_INFO,
|
||||
autoWatch: true,
|
||||
browsers: ['Chrome'],
|
||||
singleRun: false
|
||||
});
|
||||
};
|
||||
@ -1,216 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var _ = require('lodash');
|
||||
var async = require('async');
|
||||
var bitcore = require('bitcore');
|
||||
var BitcoreAddress = bitcore.Address;
|
||||
var BitcoreTransaction = bitcore.Transaction;
|
||||
var BitcoreUtil = bitcore.util;
|
||||
var Parser = bitcore.BinaryParser;
|
||||
var Buffer = bitcore.Buffer;
|
||||
var TransactionDb = imports.TransactionDb || require('../../lib/TransactionDb').default();
|
||||
var BlockDb = imports.BlockDb || require('../../lib/BlockDb').default();
|
||||
var config = require('../../config/config');
|
||||
var CONCURRENCY = 5;
|
||||
|
||||
function Address(addrStr) {
|
||||
this.balanceSat = 0;
|
||||
this.totalReceivedSat = 0;
|
||||
this.totalSentSat = 0;
|
||||
|
||||
this.unconfirmedBalanceSat = 0;
|
||||
|
||||
this.txApperances = 0;
|
||||
this.unconfirmedTxApperances = 0;
|
||||
this.seen = {};
|
||||
|
||||
// TODO store only txids? +index? +all?
|
||||
this.transactions = [];
|
||||
this.unspent = [];
|
||||
|
||||
var a = new BitcoreAddress(addrStr);
|
||||
a.validate();
|
||||
this.addrStr = addrStr;
|
||||
|
||||
Object.defineProperty(this, 'totalSent', {
|
||||
get: function() {
|
||||
return parseFloat(this.totalSentSat) / parseFloat(BitcoreUtil.COIN);
|
||||
},
|
||||
set: function(i) {
|
||||
this.totalSentSat = i * BitcoreUtil.COIN;
|
||||
},
|
||||
enumerable: 1,
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'balance', {
|
||||
get: function() {
|
||||
return parseFloat(this.balanceSat) / parseFloat(BitcoreUtil.COIN);
|
||||
},
|
||||
set: function(i) {
|
||||
this.balance = i * BitcoreUtil.COIN;
|
||||
},
|
||||
enumerable: 1,
|
||||
});
|
||||
|
||||
Object.defineProperty(this, 'totalReceived', {
|
||||
get: function() {
|
||||
return parseFloat(this.totalReceivedSat) / parseFloat(BitcoreUtil.COIN);
|
||||
},
|
||||
set: function(i) {
|
||||
this.totalReceived = i * BitcoreUtil.COIN;
|
||||
},
|
||||
enumerable: 1,
|
||||
});
|
||||
|
||||
|
||||
Object.defineProperty(this, 'unconfirmedBalance', {
|
||||
get: function() {
|
||||
return parseFloat(this.unconfirmedBalanceSat) / parseFloat(BitcoreUtil.COIN);
|
||||
},
|
||||
set: function(i) {
|
||||
this.unconfirmedBalanceSat = i * BitcoreUtil.COIN;
|
||||
},
|
||||
enumerable: 1,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
Address.prototype.getObj = function() {
|
||||
// Normalize json address
|
||||
return {
|
||||
'addrStr': this.addrStr,
|
||||
'balance': this.balance,
|
||||
'balanceSat': this.balanceSat,
|
||||
'totalReceived': this.totalReceived,
|
||||
'totalReceivedSat': this.totalReceivedSat,
|
||||
'totalSent': this.totalSent,
|
||||
'totalSentSat': this.totalSentSat,
|
||||
'unconfirmedBalance': this.unconfirmedBalance,
|
||||
'unconfirmedBalanceSat': this.unconfirmedBalanceSat,
|
||||
'unconfirmedTxApperances': this.unconfirmedTxApperances,
|
||||
'txApperances': this.txApperances,
|
||||
'transactions': this.transactions
|
||||
};
|
||||
};
|
||||
|
||||
Address.prototype._addTxItem = function(txItem, txList, includeInfo) {
|
||||
function addTx(data) {
|
||||
if (!txList) return;
|
||||
if (includeInfo) {
|
||||
txList.push(data);
|
||||
} else {
|
||||
txList.push(data.txid);
|
||||
}
|
||||
};
|
||||
|
||||
var add = 0,
|
||||
addSpend = 0;
|
||||
var v = txItem.value_sat;
|
||||
var seen = this.seen;
|
||||
|
||||
// Founding tx
|
||||
if (!seen[txItem.txid]) {
|
||||
seen[txItem.txid] = 1;
|
||||
add = 1;
|
||||
|
||||
addTx({
|
||||
txid: txItem.txid,
|
||||
ts: txItem.ts,
|
||||
firstSeenTs: txItem.firstSeenTs,
|
||||
});
|
||||
}
|
||||
|
||||
// Spent tx
|
||||
if (txItem.spentTxId && !seen[txItem.spentTxId]) {
|
||||
addTx({
|
||||
txid: txItem.spentTxId,
|
||||
ts: txItem.spentTs
|
||||
});
|
||||
seen[txItem.spentTxId] = 1;
|
||||
addSpend = 1;
|
||||
}
|
||||
if (txItem.isConfirmed) {
|
||||
this.txApperances += add;
|
||||
this.totalReceivedSat += v;
|
||||
if (!txItem.spentTxId) {
|
||||
//unspent
|
||||
this.balanceSat += v;
|
||||
} else if (!txItem.spentIsConfirmed) {
|
||||
// unspent
|
||||
this.balanceSat += v;
|
||||
this.unconfirmedBalanceSat -= v;
|
||||
this.unconfirmedTxApperances += addSpend;
|
||||
} else {
|
||||
// spent
|
||||
this.totalSentSat += v;
|
||||
this.txApperances += addSpend;
|
||||
}
|
||||
} else {
|
||||
this.unconfirmedBalanceSat += v;
|
||||
this.unconfirmedTxApperances += add;
|
||||
}
|
||||
};
|
||||
|
||||
// opts are
|
||||
// .onlyUnspent
|
||||
// .txLimit (=0 -> no txs, => -1 no limit)
|
||||
// .includeTxInfo
|
||||
//
|
||||
Address.prototype.update = function(next, opts) {
|
||||
var self = this;
|
||||
if (!self.addrStr) return next();
|
||||
opts = opts || {};
|
||||
|
||||
if (!('ignoreCache' in opts))
|
||||
opts.ignoreCache = config.ignoreCache;
|
||||
|
||||
// should collect txList from address?
|
||||
var txList = opts.txLimit === 0 ? null : [];
|
||||
|
||||
var tDb = TransactionDb;
|
||||
var bDb = BlockDb;
|
||||
tDb.fromAddr(self.addrStr, opts, function(err, txOut) {
|
||||
if (err) return next(err);
|
||||
|
||||
bDb.fillConfirmations(txOut, function(err) {
|
||||
if (err) return next(err);
|
||||
|
||||
tDb.cacheConfirmations(txOut, function(err) {
|
||||
// console.log('[Address.js.161:txOut:]',txOut); //TODO
|
||||
if (err) return next(err);
|
||||
if (opts.onlyUnspent) {
|
||||
txOut = txOut.filter(function(x) {
|
||||
return !x.spentTxId;
|
||||
});
|
||||
tDb.fillScriptPubKey(txOut, function() {
|
||||
//_.filter will filterout unspend without scriptPubkey
|
||||
//(probably from double spends)
|
||||
self.unspent = _.filter(txOut.map(function(x) {
|
||||
return {
|
||||
address: self.addrStr,
|
||||
txid: x.txid,
|
||||
vout: x.index,
|
||||
ts: x.ts,
|
||||
scriptPubKey: x.scriptPubKey,
|
||||
amount: x.value_sat / BitcoreUtil.COIN,
|
||||
confirmations: x.isConfirmedCached ? (config.safeConfirmations) : x.confirmations,
|
||||
confirmationsFromCache: !!x.isConfirmedCached,
|
||||
};
|
||||
}), 'scriptPubKey');;
|
||||
return next();
|
||||
});
|
||||
} else {
|
||||
txOut.forEach(function(txItem) {
|
||||
self._addTxItem(txItem, txList, opts.includeTxInfo);
|
||||
});
|
||||
if (txList)
|
||||
self.transactions = txList;
|
||||
return next();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = require('soop')(Address);
|
||||
@ -1,105 +0,0 @@
|
||||
'use strict';
|
||||
//var imports = require('soop').imports();
|
||||
|
||||
var async = require('async');
|
||||
var bitcore = require('bitcore');
|
||||
var RpcClient = bitcore.RpcClient;
|
||||
var config = require('../../config/config');
|
||||
var rpc = new RpcClient(config.bitcoind);
|
||||
var bDb = require('../../lib/BlockDb').default();
|
||||
|
||||
function Status() {}
|
||||
|
||||
Status.prototype.getInfo = function(next) {
|
||||
var that = this;
|
||||
async.series([
|
||||
function (cb) {
|
||||
rpc.getInfo(function(err, info){
|
||||
if (err) return cb(err);
|
||||
|
||||
that.info = info.result;
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
], function (err) {
|
||||
return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
Status.prototype.getDifficulty = function(next) {
|
||||
var that = this;
|
||||
async.series([
|
||||
function (cb) {
|
||||
rpc.getDifficulty(function(err, df){
|
||||
if (err) return cb(err);
|
||||
|
||||
that.difficulty = df.result;
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
Status.prototype.getTxOutSetInfo = function(next) {
|
||||
var that = this;
|
||||
async.series([
|
||||
function (cb) {
|
||||
rpc.getTxOutSetInfo(function(err, txout){
|
||||
if (err) return cb(err);
|
||||
|
||||
that.txoutsetinfo = txout.result;
|
||||
return cb();
|
||||
});
|
||||
}
|
||||
], function (err) {
|
||||
return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
Status.prototype.getBestBlockHash = function(next) {
|
||||
var that = this;
|
||||
async.series([
|
||||
function (cb) {
|
||||
rpc.getBestBlockHash(function(err, bbh){
|
||||
if (err) return cb(err);
|
||||
|
||||
that.bestblockhash = bbh.result;
|
||||
return cb();
|
||||
});
|
||||
},
|
||||
|
||||
], function (err) {
|
||||
return next(err);
|
||||
});
|
||||
};
|
||||
|
||||
Status.prototype.getLastBlockHash = function(next) {
|
||||
var that = this;
|
||||
bDb.getTip(function(err,tip) {
|
||||
that.syncTipHash = tip;
|
||||
async.waterfall(
|
||||
[
|
||||
function(callback){
|
||||
rpc.getBlockCount(function(err, bc){
|
||||
if (err) return callback(err);
|
||||
callback(null, bc.result);
|
||||
});
|
||||
},
|
||||
function(bc, callback){
|
||||
rpc.getBlockHash(bc, function(err, bh){
|
||||
if (err) return callback(err);
|
||||
callback(null, bh.result);
|
||||
});
|
||||
}
|
||||
],
|
||||
function (err, result) {
|
||||
that.lastblockhash = result;
|
||||
return next();
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = require('soop')(Status);
|
||||
@ -1,26 +0,0 @@
|
||||
'use strict';
|
||||
//var imports = require('soop').imports();
|
||||
var _ = require('lodash');
|
||||
var async = require('async');
|
||||
|
||||
var bitcore = require('bitcore');
|
||||
var RpcClient = bitcore.RpcClient;
|
||||
var config = require('../../config/config');
|
||||
var rpc = new RpcClient(config.bitcoind);
|
||||
|
||||
function Utils() {}
|
||||
|
||||
Utils.prototype.estimateFee = function(nbBlocks, cb) {
|
||||
var that = this;
|
||||
|
||||
async.map([].concat(nbBlocks), function(n, next) {
|
||||
rpc.estimateFee(+n, function(err, info) {
|
||||
return next(err, [n, info.result]);
|
||||
});
|
||||
}, function(err, result) {
|
||||
if (err) return cb(err);
|
||||
return cb(null, _.zipObject(result));
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = require('soop')(Utils);
|
||||
14361
app/package-lock.json
generated
Normal file
70
app/package.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"name": "insight-app",
|
||||
"private": true,
|
||||
"insightConfig": {
|
||||
"apiPrefix": "api",
|
||||
"routePrefix": ""
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "ionic-app-scripts clean",
|
||||
"build": "ionic-app-scripts build",
|
||||
"ionic:build": "ionic-app-scripts build",
|
||||
"ionic:serve": "ionic-app-scripts serve",
|
||||
"e2e": "ionic-app-scripts build && protractor",
|
||||
"postinstall": "webdriver-manager update --gecko false",
|
||||
"start": "ionic serve",
|
||||
"test": "ng test --code-coverage",
|
||||
"test:unit-e2e": "ng test --watch=false --code-coverage --browsers ChromeHeadless && npm run e2e --browsers ChromeHeadless",
|
||||
"test:ci": "ng test --watch=false --code-coverage --browsers ChromeHeadless",
|
||||
"cov": "opn coverage/index.html"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/common": "4.1.3",
|
||||
"@angular/compiler": "4.1.3",
|
||||
"@angular/compiler-cli": "4.1.3",
|
||||
"@angular/core": "4.1.3",
|
||||
"@angular/forms": "4.1.3",
|
||||
"@angular/http": "4.1.3",
|
||||
"@angular/platform-browser": "4.1.3",
|
||||
"@angular/platform-browser-dynamic": "4.1.3",
|
||||
"@angular/router": "4.1.3",
|
||||
"@ionic-native/core": "3.10.2",
|
||||
"@ionic-native/splash-screen": "3.10.2",
|
||||
"@ionic-native/status-bar": "3.10.2",
|
||||
"@ionic/storage": "2.0.1",
|
||||
"angular2-moment": "^1.6.0",
|
||||
"angular2-qrcode": "^2.0.1",
|
||||
"ionic-angular": "3.4.2",
|
||||
"ionicons": "3.0.0",
|
||||
"rxjs": "5.4.0",
|
||||
"sw-toolbox": "3.6.0",
|
||||
"zone.js": "0.8.12"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular/cli": "^1.1.2",
|
||||
"@ionic/app-scripts": "1.3.7",
|
||||
"@types/jasmine": "2.5.41",
|
||||
"@types/node": "7.0.4",
|
||||
"codecov": "2.2.0",
|
||||
"ionic": "3.20.0",
|
||||
"jasmine-core": "2.5.2",
|
||||
"jasmine-spec-reporter": "3.2.0",
|
||||
"karma": "1.4.1",
|
||||
"karma-chrome-launcher": "2.1.1",
|
||||
"karma-cli": "1.0.1",
|
||||
"karma-coverage-istanbul-reporter": "1.2.1",
|
||||
"karma-jasmine": "1.1.0",
|
||||
"karma-jasmine-html-reporter": "0.2.2",
|
||||
"opn-cli": "^3.1.0",
|
||||
"protractor": "5.1.2",
|
||||
"serve-static": "1.12.3",
|
||||
"ts-node": "3.0.4",
|
||||
"tslint": "5.3.2",
|
||||
"tslint-eslint-rules": "4.1.1",
|
||||
"typescript": "2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"license": "MIT"
|
||||
}
|
||||
33
app/protractor.conf.js
Normal file
@ -0,0 +1,33 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
useAllAngular2AppRoots: true,
|
||||
beforeLaunch: function() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e/tsconfig.e2e.json'
|
||||
});
|
||||
|
||||
require('connect')().use(require('serve-static')('www')).listen(4200);
|
||||
},
|
||||
onPrepare() {
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
BIN
app/resources/android/icon/drawable-hdpi-icon.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/resources/android/icon/drawable-ldpi-icon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
app/resources/android/icon/drawable-mdpi-icon.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/resources/android/icon/drawable-xhdpi-icon.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/resources/android/icon/drawable-xxhdpi-icon.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
app/resources/android/icon/drawable-xxxhdpi-icon.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/resources/android/splash/drawable-land-hdpi-screen.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/resources/android/splash/drawable-land-ldpi-screen.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/resources/android/splash/drawable-land-mdpi-screen.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
app/resources/android/splash/drawable-land-xhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
app/resources/android/splash/drawable-land-xxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 58 KiB |
BIN
app/resources/android/splash/drawable-land-xxxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 87 KiB |
BIN
app/resources/android/splash/drawable-port-hdpi-screen.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/resources/android/splash/drawable-port-ldpi-screen.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/resources/android/splash/drawable-port-mdpi-screen.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
app/resources/android/splash/drawable-port-xhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
app/resources/android/splash/drawable-port-xxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
app/resources/android/splash/drawable-port-xxxhdpi-screen.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
app/resources/icon.png
Normal file
|
After Width: | Height: | Size: 59 KiB |
BIN
app/resources/ios/icon/icon-40.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/resources/ios/icon/icon-40@2x.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/resources/ios/icon/icon-40@3x.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
app/resources/ios/icon/icon-50.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/resources/ios/icon/icon-50@2x.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/resources/ios/icon/icon-60.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/resources/ios/icon/icon-60@2x.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
app/resources/ios/icon/icon-60@3x.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/resources/ios/icon/icon-72.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
app/resources/ios/icon/icon-72@2x.png
Normal file
|
After Width: | Height: | Size: 7.6 KiB |
BIN
app/resources/ios/icon/icon-76.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/resources/ios/icon/icon-76@2x.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
app/resources/ios/icon/icon-83.5@2x.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
app/resources/ios/icon/icon-small.png
Normal file
|
After Width: | Height: | Size: 818 B |
BIN
app/resources/ios/icon/icon-small@2x.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/resources/ios/icon/icon-small@3x.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
app/resources/ios/icon/icon.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/resources/ios/icon/icon@2x.png
Normal file
|
After Width: | Height: | Size: 5.6 KiB |
BIN
app/resources/ios/splash/Default-568h@2x~iphone.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
app/resources/ios/splash/Default-667h.png
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
app/resources/ios/splash/Default-736h.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
app/resources/ios/splash/Default-Landscape-736h.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
app/resources/ios/splash/Default-Landscape@2x~ipad.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
BIN
app/resources/ios/splash/Default-Landscape~ipad.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/resources/ios/splash/Default-Portrait@2x~ipad.png
Normal file
|
After Width: | Height: | Size: 97 KiB |
BIN
app/resources/ios/splash/Default-Portrait~ipad.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/resources/ios/splash/Default@2x~iphone.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
app/resources/ios/splash/Default~iphone.png
Normal file
|
After Width: | Height: | Size: 7.0 KiB |
BIN
app/resources/splash.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
62
app/src/app/app.component.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Platform, MenuController, Nav } from 'ionic-angular';
|
||||
import { StatusBar } from '@ionic-native/status-bar';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||
import {
|
||||
HomePage
|
||||
} from '../pages';
|
||||
|
||||
@Component({
|
||||
templateUrl: './app.html'
|
||||
})
|
||||
export class InsightApp {
|
||||
@ViewChild(Nav) public nav: Nav;
|
||||
|
||||
private menu: MenuController;
|
||||
private platform: Platform;
|
||||
private splash: SplashScreen;
|
||||
private status: StatusBar;
|
||||
|
||||
public rootPage: any;
|
||||
public pages: Array<{ title: string; component: any }>;
|
||||
|
||||
constructor(
|
||||
platform: Platform,
|
||||
menu: MenuController,
|
||||
splash: SplashScreen,
|
||||
status: StatusBar
|
||||
) {
|
||||
this.menu = menu;
|
||||
this.platform = platform;
|
||||
this.splash = splash;
|
||||
this.status = status;
|
||||
|
||||
this.rootPage = HomePage;
|
||||
this.initializeApp();
|
||||
|
||||
// set our app's pages
|
||||
this.pages = [
|
||||
{ title: 'Home', component: HomePage },
|
||||
{ title: 'Blocks', component: 'blocks' },
|
||||
{ title: 'Broadcast Transaction', component: 'BroadcastTxPage' }
|
||||
// { title: 'Verify Signed Message', component: VerifyMessagePage },
|
||||
// { title: 'Node Status', component: NodeStatusPage }
|
||||
];
|
||||
}
|
||||
|
||||
private initializeApp(): void {
|
||||
this.platform.ready().then(() => {
|
||||
// Okay, so the platform is ready and our plugins are available.
|
||||
// Here you can do any higher level native things you might need.
|
||||
this.status.styleDefault();
|
||||
this.splash.hide();
|
||||
});
|
||||
}
|
||||
|
||||
public openPage(page: any): void {
|
||||
// close the menu when clicking a link from the menu
|
||||
this.menu.close();
|
||||
// navigate to the new page if it is not the current page
|
||||
this.nav.setRoot(page.component);
|
||||
}
|
||||
}
|
||||
13
app/src/app/app.html
Normal file
@ -0,0 +1,13 @@
|
||||
<ion-menu [content]="content">
|
||||
|
||||
<ion-content>
|
||||
<ion-list>
|
||||
<button ion-item *ngFor="let p of pages" (click)="openPage(p)">
|
||||
{{p.title}}
|
||||
</button>
|
||||
</ion-list>
|
||||
</ion-content>
|
||||
|
||||
</ion-menu>
|
||||
|
||||
<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>
|
||||
44
app/src/app/app.module.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { NgModule, ErrorHandler } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
|
||||
import { StatusBar } from '@ionic-native/status-bar';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||
import { InsightApp } from './app.component';
|
||||
import { PagesModule, HomePage, BlocksPage, NodeStatusPage, VerifyMessagePage } from '../pages';
|
||||
import { BlocksService, StorageService } from '../services';
|
||||
import { ApiProvider } from '../providers/api/api';
|
||||
import { CurrencyProvider } from '../providers/currency/currency';
|
||||
import { BlocksProvider } from '../providers/blocks/blocks';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
InsightApp
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
HttpModule,
|
||||
PagesModule,
|
||||
IonicModule.forRoot(InsightApp)
|
||||
],
|
||||
bootstrap: [IonicApp],
|
||||
entryComponents: [
|
||||
InsightApp,
|
||||
HomePage,
|
||||
BlocksPage,
|
||||
NodeStatusPage,
|
||||
VerifyMessagePage
|
||||
],
|
||||
providers: [
|
||||
StatusBar,
|
||||
SplashScreen,
|
||||
StorageService,
|
||||
BlocksService,
|
||||
{provide: ErrorHandler, useClass: IonicErrorHandler},
|
||||
ApiProvider,
|
||||
CurrencyProvider,
|
||||
BlocksProvider
|
||||
]
|
||||
})
|
||||
|
||||
export class AppModule {}
|
||||
84
app/src/app/app.scss
Normal file
@ -0,0 +1,84 @@
|
||||
// http://ionicframework.com/docs/v2/theming/
|
||||
|
||||
// App Global Sass
|
||||
// --------------------------------------------------
|
||||
// Put style rules here that you want to apply globally. These
|
||||
// styles are for the entire app and not just one component.
|
||||
// Additionally, this file can be also used as an entry point
|
||||
// to import other Sass files to be included in the output CSS.
|
||||
|
||||
body {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.item-hash {
|
||||
@extend .ellipsis;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.list--summary {
|
||||
ion-label {
|
||||
color: #334;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-item {
|
||||
color: #99a;
|
||||
font-size: 1.125rem;
|
||||
@media screen and (min-width: $desktop-min-width) {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
ion-note {
|
||||
color: #99a;
|
||||
max-width: 80%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.grid--table {
|
||||
margin: 10px 0 20px;
|
||||
|
||||
.col {
|
||||
padding: 1em 0.6em;
|
||||
}
|
||||
|
||||
ion-row {
|
||||
border-top: 1px solid #dde;
|
||||
|
||||
&:first-child {
|
||||
background-color: #fff;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #f8f8f9;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared Sass variables, which can be used to adjust Ionic's
|
||||
// default Sass variables, belong in "theme/variables.scss".
|
||||
//
|
||||
// To declare rules for a specific mode, create a child rule
|
||||
// for the .md, .ios, or .wp mode classes. The mode class is
|
||||
// automatically applied to the <body> element in the app.
|
||||
44
app/src/app/app.spec.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { InsightApp } from './app.component';
|
||||
import { TestBed, getTestBed } from '@angular/core/testing';
|
||||
import { Platform } from 'ionic-angular';
|
||||
import { NavMock } from '../mocks';
|
||||
import { PopoverController, MenuController } from 'ionic-angular';
|
||||
import { StatusBar } from '@ionic-native/status-bar';
|
||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||
|
||||
describe('InsightApp', () => {
|
||||
let injector: TestBed;
|
||||
let app: InsightApp;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [
|
||||
PopoverController,
|
||||
InsightApp,
|
||||
Platform,
|
||||
MenuController,
|
||||
SplashScreen,
|
||||
StatusBar
|
||||
]
|
||||
});
|
||||
injector = getTestBed();
|
||||
app = injector.get(InsightApp);
|
||||
|
||||
app['nav'] = (<any>new NavMock());
|
||||
});
|
||||
|
||||
it('initializes with three possible pages', () => {
|
||||
expect(app['pages'].length).toEqual(3);
|
||||
});
|
||||
|
||||
it('initializes with a root page', () => {
|
||||
expect(app['rootPage']).not.toBe(null);
|
||||
});
|
||||
|
||||
it('opens a page', () => {
|
||||
spyOn(app['menu'], 'close');
|
||||
spyOn(app['nav'], 'setRoot');
|
||||
app.openPage(app['pages'][1]);
|
||||
expect(app['menu']['close']).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
5
app/src/app/main.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
BIN
app/src/assets/icon/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/assets/img/angularjs.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
29
app/src/assets/img/bitcore.svg
Normal file
|
After Width: | Height: | Size: 15 KiB |
14
app/src/assets/img/currency_logos/bch.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="49px" height="30px" viewBox="0 0 49 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>cash</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Invoice---Details---toggle-13" transform="translate(-15.000000, -66.000000)" fill-rule="nonzero" fill="#70C559">
|
||||
<g id="cash" transform="translate(15.000000, 66.000000)">
|
||||
<path d="M13.8417063,-9.23705556e-14 C10.3638666,2.45824779 7.71855988,6.11178429 6.60498615,10.5439372 C4.70445891,18.0998581 7.80802299,25.7315444 13.8323317,30 L0,30 L0,6.67235157e-12 L13.8417063,6.67174941e-12 L13.8417063,-9.23705556e-14 Z M35.4255188,2.16006456e-11 L49,2.16000551e-11 L49,30 L35.4177702,30 C38.8953546,27.5421934 41.5405004,23.8894637 42.6545185,19.458134 C44.5543534,11.900549 41.4505764,4.26854241 35.4255188,2.1236346e-11 L35.4255188,2.16006456e-11 Z M39.3004107,18.6286839 C37.2802686,26.6643836 29.0735016,31.5548028 20.9697739,29.5509168 C12.8693548,27.5474997 7.93811007,19.4082073 9.95919725,11.3729763 C11.9783938,3.33633912 20.185161,-1.55454875 28.2865254,0.448868418 C36.3897804,2.45228559 41.3205524,10.5925155 39.3004107,18.6286839 Z M25.404827,8.31840135 L24.822017,5.61021006 L23.1646362,5.95837059 L23.7321322,8.59517337 C23.2964321,8.68671195 22.8528909,8.78878287 22.412286,8.89047021 L21.8413778,6.23613729 L20.1848384,6.58389075 L20.7668159,9.29144802 C20.4095444,9.37407702 20.0585751,9.45469416 19.7150084,9.52706466 L17.4268281,9.99789864 L17.8061139,11.7584155 C17.8061139,11.7584155 19.0250623,11.4782322 19.0095416,11.5049211 C19.6809284,11.3641687 19.9828396,11.7059324 20.1183742,12.0270932 L21.7146405,19.4461225 C21.7303963,19.6624491 21.6782983,20.0235742 21.2094313,20.1228629 C21.2346595,20.1370971 20.0045444,20.375508 20.0045444,20.375508 L20.0988542,22.4133317 L22.2557355,21.9603946 C22.6572213,21.8765944 23.053614,21.8004421 23.44186,21.7214914 L24.0319506,24.4603751 L25.6876485,24.1130287 L25.1044355,21.4029588 C25.5610391,21.31686 26.0017437,21.2280845 26.4311504,21.1375167 L27.0109069,23.835262 L28.6682877,23.4871014 L28.0804623,20.7528244 C30.8327059,20.0087698 32.6327287,18.9010983 32.3148875,16.247723 C32.0594765,14.1110581 30.8671217,13.3840872 29.2040862,13.3384078 C30.0603063,12.6499773 30.4765778,11.6663412 30.0286414,10.2671807 C29.4197843,8.35437249 27.5843909,8.05563042 25.404827,8.31840135 Z M28.8925748,16.4400896 C29.3339492,18.4867945 25.7599728,18.9938427 24.6369247,19.2307446 L23.8564384,15.6015987 C24.9801043,15.3659532 28.4333066,14.3051001 28.8925748,16.4400896 Z M23.3654016,14.0925774 L22.6572494,10.8011688 C23.5928708,10.6046501 26.4682721,9.69163557 26.886443,11.6339622 C27.2866494,13.4963329 24.3010229,13.8960587 23.3654016,14.0925774 Z" id="Cash"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
15
app/src/assets/img/currency_logos/btc.svg
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>bitcoin</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Invoice---Details---toggle-12" transform="translate(-14.000000, -65.000000)" fill-rule="nonzero">
|
||||
<g id="bitcoin" transform="translate(14.000000, 65.000000)">
|
||||
<path d="M29.488125,18.7144615 C27.4907812,26.6007692 19.3776562,31.4141538 11.3475,29.448 C3.3384375,27.4813846 -1.550625,19.4930769 0.44671875,11.6067692 C2.44359375,3.72092308 10.5571875,-1.09246154 18.56625,0.873692308 C26.5964062,2.81953846 31.485,10.8286154 29.488125,18.7144615" id="Shape" fill="#F8AE2A"></path>
|
||||
<path d="M18.8160938,13.2456923 C18.3792187,15.0687692 15.508125,14.1876923 14.5720313,13.9624615 L15.3628125,10.7261538 C16.2778125,10.9513846 19.2735937,11.3409231 18.8160937,13.2456923 L18.8160938,13.2456923 Z M18.358125,18.4689231 C17.88,20.4761538 14.42625,19.4316923 13.32375,19.1653846 L14.176875,15.6013846 C15.3,15.8676923 18.8573438,16.3592308 18.358125,18.4689231 Z M22.06125,13.2456923 C22.3317187,11.2790769 20.8129687,10.2549231 18.7326562,9.55846154 L19.3776562,6.89584615 L17.7342188,6.50676923 L17.1103125,9.108 C16.6734375,9.00553846 16.2365625,8.90307692 15.7996875,8.82138462 L16.4235937,6.21969231 L14.7595313,5.83061538 L14.1145313,8.49369231 C13.760625,8.41153846 13.4071875,8.35015385 13.0532813,8.268 L10.7859375,7.73584615 L10.3696875,9.45646154 C10.3696875,9.45646154 11.5973438,9.72230769 11.555625,9.74307692 C12.22125,9.90692308 12.3459375,10.3370769 12.3253125,10.6647692 L11.5973438,13.6961538 C11.6385938,13.7169231 11.7014063,13.7169231 11.76375,13.7575385 C11.7014063,13.7372308 11.6596875,13.7372308 11.5973438,13.7169231 L10.5984375,17.9566154 C10.5154688,18.1615385 10.3073438,18.4481538 9.84984375,18.3461538 C9.87046875,18.3664615 8.64328125,18.0590769 8.64328125,18.0590769 L7.8525,19.9232308 L9.99515625,20.4147692 C10.3907812,20.5172308 10.7859375,20.5989231 11.1604687,20.7013846 L10.5154688,23.3847692 L12.1589062,23.7738462 L12.8039062,21.1112308 C13.2614063,21.234 13.6982813,21.3364615 14.1145312,21.4389231 L13.4695312,24.0812308 L15.1129687,24.4703077 L15.7579687,21.7873846 C18.56625,22.2789231 20.6671875,22.0532308 21.5203125,19.5341538 C22.2070312,17.5264615 21.4579687,16.3795385 19.9603125,15.6216923 C21.0628125,15.3756923 21.853125,14.6796923 22.06125,13.2456923 L22.06125,13.2456923 Z" id="Shape" fill="#FFFFFF"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
16
app/src/assets/img/currency_logos/ltc.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="30px" height="30px" viewBox="0 0 30 30" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>litecoin</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Exploration" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="select-currency" transform="translate(-135.000000, -516.000000)" fill-rule="nonzero" fill="#BEBEBE">
|
||||
<g id="LTC" transform="translate(135.000000, 514.000000)">
|
||||
<g id="litecoin" transform="translate(0.000000, 2.000000)">
|
||||
<path d="M14.9978166,0.0793957695 C6.7571516,0.0793957695 0.076944392,6.75969362 0.076944392,15.0002674 C0.076944392,23.240932 6.7571516,29.9213166 14.9978166,29.9213166 C23.2383906,29.9213166 29.9186882,23.240932 29.9186882,15.0002674 C29.9186882,6.75969362 23.2383,0.0793957695 14.9978166,0.0793957695 Z M12.6942498,6.99841486 L17.3093483,6.99841486 L15.6819997,13.1259761 L17.9406036,12.3008837 L17.9600755,12.35399 L17.3893613,14.4987689 L15.0948236,15.3369607 L14.1258136,18.986236 L21.793452,18.986236 L21.0060653,21.9194637 L8.72315148,21.9194637 L9.97751782,17.2066495 L8.20200344,17.8552523 L8.78918003,15.6440909 L10.5659338,14.9949571 L12.6942498,6.99841486 Z" id="Fill-1"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/assets/img/leveldb.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/assets/img/nodejs.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
19
app/src/components/components.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
FormsModule,
|
||||
IonicModule,
|
||||
ReactiveFormsModule
|
||||
],
|
||||
exports: [
|
||||
],
|
||||
entryComponents: [],
|
||||
providers: []
|
||||
})
|
||||
|
||||
export class ComponentsModule {}
|
||||
21
app/src/components/denomination/denomination.html
Normal file
@ -0,0 +1,21 @@
|
||||
<ion-list radio-group [(ngModel)]="currencyProvider.currencySymbol" (ionChange)="currencyProvider.setCurrency(currencyProvider.currencySymbol)">
|
||||
<ion-list-header *ngIf="switcherOn">
|
||||
Blockchain
|
||||
</ion-list-header>
|
||||
|
||||
<ion-row *ngIf="switcherOn">
|
||||
<ion-col *ngFor="let explorer of currencyProvider.explorers">
|
||||
<button (click)="changeExplorer(explorer)" ion-item detail-none class="text-button" text-center>
|
||||
<img src="assets/img/currency_logos/{{ explorer.ticker.toLowerCase() }}.svg" class="logo"/></button>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
<ion-list-header>
|
||||
Units
|
||||
</ion-list-header>
|
||||
|
||||
<ion-item *ngFor="let unit of units">
|
||||
<ion-label color="dark">{{ unit }}</ion-label>
|
||||
<ion-radio value="{{ unit }}" (click)="close()"></ion-radio>
|
||||
</ion-item>
|
||||
</ion-list>
|
||||
19
app/src/components/denomination/denomination.module.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { DenominationComponent } from './denomination';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
DenominationComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule
|
||||
],
|
||||
exports: [
|
||||
DenominationComponent
|
||||
],
|
||||
entryComponents: [
|
||||
DenominationComponent
|
||||
]
|
||||
})
|
||||
export class DenominationComponentModule {}
|
||||
3
app/src/components/denomination/denomination.scss
Normal file
@ -0,0 +1,3 @@
|
||||
denomination {
|
||||
|
||||
}
|
||||
41
app/src/components/denomination/denomination.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { CurrencyProvider } from '../../providers/currency/currency';
|
||||
import { ViewController } from 'ionic-angular';
|
||||
import { Http } from '@angular/http';
|
||||
import { ApiProvider } from '../../providers/api/api';
|
||||
|
||||
@Component({
|
||||
selector: 'denomination',
|
||||
templateUrl: 'denomination.html'
|
||||
})
|
||||
export class DenominationComponent {
|
||||
|
||||
public switcherOn: boolean;
|
||||
public units: any = [];
|
||||
|
||||
constructor(
|
||||
public currencyProvider: CurrencyProvider,
|
||||
public viewCtrl: ViewController,
|
||||
public http: Http,
|
||||
public api: ApiProvider
|
||||
) {
|
||||
this.units = [
|
||||
'USD',
|
||||
this.currencyProvider.defaultCurrency,
|
||||
'm' + this.currencyProvider.defaultCurrency,
|
||||
'bits'
|
||||
];
|
||||
|
||||
this.switcherOn = currencyProvider.explorers.length > 1;
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
public changeExplorer(explorer: any): void {
|
||||
this.close();
|
||||
let theUrl: string = explorer.url;
|
||||
window.location.href = theUrl;
|
||||
}
|
||||
}
|
||||
19
app/src/components/head-nav/head-nav.html
Normal file
@ -0,0 +1,19 @@
|
||||
<ion-navbar color="brand">
|
||||
<button ion-button menuToggle>
|
||||
<ion-icon name="menu"></ion-icon>
|
||||
</button>
|
||||
<ion-title>{{title}}</ion-title>
|
||||
<ion-buttons end>
|
||||
<button ion-button (click)="changeCurrency($event)">
|
||||
<ion-icon name="logo-bitcoin" *ngIf="currency.currencySymbol !== 'USD'"></ion-icon>
|
||||
<ion-icon name="logo-usd" *ngIf="currency.currencySymbol === 'USD'"></ion-icon> {{ currency.currencySymbol }}
|
||||
</button>
|
||||
<button ion-button icon-only (click)="toggleSearch()">
|
||||
<ion-icon name="search"></ion-icon>
|
||||
</button>
|
||||
</ion-buttons>
|
||||
</ion-navbar>
|
||||
|
||||
<form (ngSubmit)="search($event)">
|
||||
<ion-searchbar [hidden]="!showSearch" placeholder="{{ 'Search for block, transaction or address' }}" [(ngModel)]="q" name="search"></ion-searchbar>
|
||||
</form>
|
||||
18
app/src/components/head-nav/head-nav.module.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { HeadNavComponent } from './head-nav';
|
||||
import { DenominationComponentModule } from '../denomination/denomination.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
HeadNavComponent
|
||||
],
|
||||
imports: [
|
||||
IonicModule,
|
||||
DenominationComponentModule
|
||||
],
|
||||
exports: [
|
||||
HeadNavComponent
|
||||
]
|
||||
})
|
||||
export class HeadNavComponentModule {}
|
||||