mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service.git
synced 2026-09-21 16:52:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41235b5919 | ||
|
|
4854eae545 | ||
|
|
a3ceb872a5 | ||
|
|
0390ce7e29 | ||
|
|
c571423326 | ||
|
|
e8d0f1fcb8 | ||
|
|
57f49ff31f | ||
|
|
8b04a54fe1 | ||
|
|
88135669cb | ||
|
|
83898742f1 | ||
|
|
1795918719 | ||
|
|
6894217078 | ||
|
|
3ffbed27b5 | ||
|
|
b372e3d260 | ||
|
|
707b385b8a | ||
|
|
6cb140a491 | ||
|
|
c43ee8c25a | ||
|
|
0a7ec11510 | ||
|
|
80b9d17b67 | ||
|
|
f7aaba71d1 | ||
|
|
418a2e2ad2 | ||
|
|
aeb234869f | ||
|
|
a1023e8224 | ||
|
|
a32ed63d47 | ||
|
|
934fb46ffc | ||
|
|
b27136d03b | ||
|
|
60a3d67c0b | ||
|
|
a03c5d8c91 | ||
|
|
bcc90da49b | ||
|
|
b473a3803c | ||
|
|
9614f186b4 | ||
|
|
0390f2d956 | ||
|
|
bff9db0e8f | ||
|
|
12f746ae80 | ||
|
|
e590a9e5f0 | ||
|
|
0f0964e367 | ||
|
|
9a4379c03e | ||
|
|
ba5ead998d | ||
|
|
409a0f1049 | ||
|
|
bd2f27bb64 | ||
|
|
55d8d131cf | ||
|
|
5a6e3247e8 | ||
|
|
2a522775eb | ||
|
|
d677c83795 | ||
|
|
67b61fc80c | ||
|
|
9509634651 | ||
|
|
62ac11c861 | ||
|
|
316c5d6399 | ||
|
|
e1fbd6bce9 | ||
|
|
7cffd9f377 | ||
|
|
0a406cb9a4 | ||
|
|
53d84300ec | ||
|
|
3fb0379175 | ||
|
|
f172a51016 | ||
|
|
00d53659b1 | ||
|
|
b0797d92f5 | ||
|
|
48484ed7af | ||
|
|
6a4aadde8a | ||
|
|
79e3aba26e | ||
|
|
bf61359bc7 | ||
|
|
00ff744726 | ||
|
|
abd33c4ebd | ||
|
|
f091e088c4 | ||
|
|
4cf8129b9d | ||
|
|
d8f2dd5755 | ||
|
|
a2232f7147 | ||
|
|
a79d152993 | ||
|
|
ce7c3a0e2f | ||
|
|
e6b0580209 | ||
|
|
1f3c639100 | ||
|
|
e6efc4f49b | ||
|
|
dbbd321029 | ||
|
|
439dbc54ae | ||
|
|
547e59f52f | ||
|
|
a0d385de73 | ||
|
|
bec136009f | ||
|
|
98b7fa7734 | ||
|
|
d7bb476858 | ||
|
|
18c71c383f | ||
|
|
de85a933c4 | ||
|
|
16247372f1 | ||
|
|
c6c8b6418c | ||
|
|
10c4e7380f | ||
|
|
4933dce472 | ||
|
|
80c2822716 | ||
|
|
f48ff9a67e | ||
|
|
4bd2385359 | ||
|
|
be71492773 | ||
|
|
bcb70edf2b | ||
|
|
07071032bc | ||
|
|
f476e119a2 | ||
|
|
0fed232428 | ||
|
|
0273510485 | ||
|
|
a62dee37f4 | ||
|
|
8d6af3e1b7 | ||
|
|
ee0f2da641 | ||
|
|
44499d9652 | ||
|
|
365a5dcd4b | ||
|
|
7f5b456018 | ||
|
|
c7217486bd | ||
|
|
050141d31e | ||
|
|
b697f37199 | ||
|
|
ceb58caba1 | ||
|
|
2724b500ee | ||
|
|
1b84e7beb8 | ||
|
|
383900d8b5 | ||
|
|
ba806d86b5 | ||
|
|
4d7c7350a0 | ||
|
|
adc6500aed | ||
|
|
eb9fce73eb | ||
|
|
ec3ed165e4 | ||
|
|
d41d20cc81 | ||
|
|
5f965e7146 | ||
|
|
f382bb4578 | ||
|
|
ba37f87f80 | ||
|
|
eaa9c23f55 | ||
|
|
ac3730dbd9 | ||
|
|
581b8eb626 | ||
|
|
cd4ec2711e | ||
|
|
e9e3dbad0d | ||
|
|
295b361431 | ||
|
|
ad60df9d51 | ||
|
|
6ae635c21d | ||
|
|
61807a2f3d | ||
|
|
16ba5a8980 | ||
|
|
99b817549e | ||
|
|
60a8c335c8 | ||
|
|
1783768c32 | ||
|
|
b3630eaa06 | ||
|
|
d691016911 | ||
|
|
6506aead8c | ||
|
|
ddfc83d026 | ||
|
|
fe42c1e0e3 | ||
|
|
928a211a63 | ||
|
|
8bff69a4ca | ||
|
|
5fced7d501 | ||
|
|
7cdf622ad6 | ||
|
|
afd8ceac7b | ||
|
|
5d63db3662 | ||
|
|
9f6cd7dbf1 | ||
|
|
0725aa3ada | ||
|
|
3eb88d4076 | ||
|
|
1c61f150b1 | ||
|
|
4c74b287d8 | ||
|
|
264d762f74 | ||
|
|
3c02461c6b | ||
|
|
a30f2be51f | ||
|
|
0690a9fb65 | ||
|
|
4fa103765e | ||
|
|
b0d6ce40a9 | ||
|
|
c57a3d935b | ||
|
|
82b5943601 | ||
|
|
41b76967f5 | ||
|
|
e4c2ee44c1 | ||
|
|
c05f022995 | ||
|
|
7723c3a201 | ||
|
|
8e338818ca | ||
|
|
ea51aa420c | ||
|
|
bc410678b4 | ||
|
|
a42c15c969 | ||
|
|
a3102a0278 | ||
|
|
f4f142dc48 | ||
|
|
e03a70c9a3 | ||
|
|
d47b8797d4 | ||
|
|
56ba0aad02 | ||
|
|
825bc6d2de | ||
|
|
18fe63a5ad | ||
|
|
d6e0e7b903 | ||
|
|
8759e1af5e | ||
|
|
b78f3fb08b | ||
|
|
5fa0b2e1c8 | ||
|
|
6a88d0559a | ||
|
|
314fdc2bb0 | ||
|
|
077c60bd34 | ||
|
|
6485537207 | ||
|
|
286f079679 | ||
|
|
075818d551 | ||
|
|
6f620a7323 | ||
|
|
d0a23608e0 | ||
|
|
6cc5196a78 | ||
|
|
9de2caba94 | ||
|
|
a45fb3ad08 | ||
|
|
666f954529 | ||
|
|
7d2e02fe64 | ||
|
|
9912ed7af7 | ||
|
|
441d898ae7 | ||
|
|
553a7ff86a | ||
|
|
349d070273 | ||
|
|
7b1aba9da8 | ||
|
|
3539f0af50 | ||
|
|
b6dcb9c177 | ||
|
|
8aff3a6568 | ||
|
|
9e885b8bc3 | ||
|
|
d6b924a5f4 | ||
|
|
97691a0dd9 | ||
|
|
3781af6305 | ||
|
|
cb5d5a603d | ||
|
|
34fa039ced | ||
|
|
f42ccafffa | ||
|
|
842e6ead69 | ||
|
|
62ef3e0a93 | ||
|
|
c9ab4b5ef8 | ||
|
|
f338f26593 | ||
|
|
dc302066f0 | ||
|
|
fe5c95078b | ||
|
|
c21554008c | ||
|
|
e2a900d3a0 | ||
|
|
c870b54cae | ||
|
|
81713bfebe | ||
|
|
0f8a6b9299 | ||
|
|
4f2c87bbd4 | ||
|
|
dd7db3814a | ||
|
|
fdb3b9c3ac | ||
|
|
f022951680 | ||
|
|
942a8f20ec | ||
|
|
683c1e4b2b | ||
|
|
953690baa5 | ||
|
|
c13a5d1593 | ||
|
|
6d7b3db370 | ||
|
|
33d06e99e8 | ||
|
|
3203e56a4d | ||
|
|
ff47f1b356 | ||
|
|
d9377ea78b | ||
|
|
d266bcc54b | ||
|
|
a033fb1042 | ||
|
|
569dcbd576 | ||
|
|
b0a2d73540 | ||
|
|
0d4c8cd56e | ||
|
|
b37dae60d1 | ||
|
|
448b028bb4 | ||
|
|
da637df44a | ||
|
|
9315e6f166 | ||
|
|
7e965e8415 | ||
|
|
a5916c95f9 | ||
|
|
708f97d107 | ||
|
|
637738bfb9 | ||
|
|
d8e82c5ad2 | ||
|
|
80a65c4e03 | ||
|
|
63ecd21734 | ||
|
|
ae957a835a | ||
|
|
c61feba1e6 | ||
|
|
8a05f1f063 | ||
|
|
b23acd3cdc | ||
|
|
c71fda38ff | ||
|
|
f99f49d840 | ||
|
|
2edd924c57 | ||
|
|
689c8fd8d9 | ||
|
|
ffd6c452c9 | ||
|
|
fc8e160874 | ||
|
|
d2f83307e6 | ||
|
|
1a09fcd26f | ||
|
|
d095d40b3f | ||
|
|
fac9f12072 | ||
|
|
6b00513db7 | ||
|
|
4387c9a758 | ||
|
|
469f1e05e8 | ||
|
|
3b836a7a35 | ||
|
|
fd1b13a917 | ||
|
|
e2c5821dbe | ||
|
|
9967d5145b | ||
|
|
9d16291855 | ||
|
|
1518d1a12b | ||
|
|
b68a143071 | ||
|
|
5c25ebeeca | ||
|
|
3b17ba8e8b | ||
|
|
434a4d7bd6 | ||
|
|
dd36ebc7a0 | ||
|
|
f117fe15df | ||
|
|
0333f77f5a | ||
|
|
3c908d1a4d | ||
|
|
eb46a4c130 | ||
|
|
d0efdcfa07 | ||
|
|
62a7fcb16d | ||
|
|
d8addd5b27 | ||
|
|
bf6f1cf62d | ||
|
|
b7df7c47a5 | ||
|
|
ae260db4af | ||
|
|
778098ca12 | ||
|
|
0c4bb9c4a1 | ||
|
|
9cb975656d | ||
|
|
4f6564423e | ||
|
|
54e7e74ae5 | ||
|
|
324c372119 | ||
|
|
da62f1bbe3 | ||
|
|
129047fe88 | ||
|
|
fb182ced58 | ||
|
|
bec7c11e46 | ||
|
|
fb10e10226 | ||
|
|
5b37e47747 | ||
|
|
a3cea782ba | ||
|
|
14ffdae194 | ||
|
|
79918f6376 | ||
|
|
da5942b28f | ||
|
|
d54cd3889b | ||
|
|
5df54feb5e | ||
|
|
fb841df43e | ||
|
|
a763ec640f | ||
|
|
e74e063b0d | ||
|
|
7e3ce1564d | ||
|
|
8154dd96f9 | ||
|
|
e37223cc88 | ||
|
|
5002ef2b90 | ||
|
|
5d95109ad4 | ||
|
|
13b3150f62 | ||
|
|
b3a4a7c1ab | ||
|
|
142cdb17a6 | ||
|
|
a129988538 | ||
|
|
6bf85b4639 | ||
|
|
733e8fd405 | ||
|
|
baee72c1c6 | ||
|
|
5d12749043 | ||
|
|
2ebd8b998f | ||
|
|
11618a796f | ||
|
|
025ae20704 | ||
|
|
b9146bbff9 | ||
|
|
47f1b84dd8 | ||
|
|
fdf73610eb | ||
|
|
b09e77c079 | ||
|
|
5050816052 | ||
|
|
7193b9d048 | ||
|
|
c2977c5264 | ||
|
|
68ad2561b1 | ||
|
|
791d11e597 | ||
|
|
5489e76f55 | ||
|
|
2ad10180d0 | ||
|
|
dd42f5da16 | ||
|
|
496a1dd07c | ||
|
|
a7b8ca9ad5 | ||
|
|
6eb16eb11a | ||
|
|
553957a173 | ||
|
|
99a737c7e7 | ||
|
|
ffb87d09c2 | ||
|
|
65ead3568a | ||
|
|
b4b2aefac6 | ||
|
|
1844f30dc5 | ||
|
|
b6ab2f02f4 | ||
|
|
b5c76aa5fe | ||
|
|
7648c96a70 | ||
|
|
79f5adaf6b | ||
|
|
e2f84d661f | ||
|
|
31091a50a8 | ||
|
|
512775c68f | ||
|
|
c53394e547 | ||
|
|
79083502fb | ||
|
|
6049c6566f | ||
|
|
99a52ec605 | ||
|
|
33e7bc2b52 | ||
|
|
a20f30ec1e | ||
|
|
b0b12c590c | ||
|
|
286092f2e3 | ||
|
|
5be93afc38 | ||
|
|
63cb107a25 | ||
|
|
c54fb73c7d | ||
|
|
8063c0e922 | ||
|
|
b729c7ed80 | ||
|
|
5a3d91e3b0 | ||
|
|
d9fa44328a | ||
|
|
efaffa8e0f | ||
|
|
27a9e9f3f3 | ||
|
|
41a5071467 | ||
|
|
c6c3bb5d9c | ||
|
|
e4977a4351 | ||
|
|
fe3b45f6be | ||
|
|
d25b240112 | ||
|
|
7a89a06ddb | ||
|
|
74fb65df72 | ||
|
|
0b38247f5a | ||
|
|
473aaae307 | ||
|
|
284f4e4478 | ||
|
|
56f6dc273a | ||
|
|
4bd3387bde | ||
|
|
7e7e91460a | ||
|
|
e5f6f24570 | ||
|
|
818c85e3d4 | ||
|
|
4101615a4a | ||
|
|
34a4072384 | ||
|
|
5fc72be10c | ||
|
|
651c4a1346 | ||
|
|
4b90ad5009 | ||
|
|
05ddf8521a | ||
|
|
88d1805685 | ||
|
|
ea104b5586 | ||
|
|
4251e2a02d | ||
|
|
5e0eb017ef | ||
|
|
86946968fe | ||
|
|
9597f1963f | ||
|
|
67d80056ff | ||
|
|
199677e4c6 | ||
|
|
86ea5c6354 | ||
|
|
7b112a13d4 | ||
|
|
fe5c573291 | ||
|
|
463d7df582 | ||
|
|
5a1ec640a4 | ||
|
|
8bb8dbcd23 | ||
|
|
ba212b0997 | ||
|
|
803496fbc3 | ||
|
|
c49ab4f58b | ||
|
|
4670bd22af | ||
|
|
b3e7c63e61 | ||
|
|
421ca359f2 | ||
|
|
4e6559ac96 | ||
|
|
1fcc92e10d | ||
|
|
f332aece98 | ||
|
|
4112b34205 | ||
|
|
30d0a62455 | ||
|
|
89f24e9940 | ||
|
|
25a75dc369 | ||
|
|
f36ce14087 | ||
|
|
df45792f47 | ||
|
|
9e71e33ccd | ||
|
|
c7b0b0c1d5 | ||
|
|
e475e5f4c3 | ||
|
|
7fff41b702 | ||
|
|
a7f7fc4e14 | ||
|
|
83ad0d8bfa | ||
|
|
ac4aaca66e | ||
|
|
0748106637 | ||
|
|
27dcc219b9 | ||
|
|
1362e64c42 | ||
|
|
7aad5db594 | ||
|
|
b231686a64 | ||
|
|
60668430f5 | ||
|
|
cf0fb0f572 | ||
|
|
68b0117c3d | ||
|
|
e73440f342 | ||
|
|
b9680508db | ||
|
|
acebfdef09 | ||
|
|
c3ee71a54f | ||
|
|
0d9a32488e | ||
|
|
de7629c93d | ||
|
|
3979694109 | ||
|
|
ae888d094b | ||
|
|
9b8db2adaf | ||
|
|
c8539372ae | ||
|
|
e8a3234dcd | ||
|
|
fc717e0dcd | ||
|
|
02512a8183 | ||
|
|
09a58a78ef | ||
|
|
f4708d9a49 | ||
|
|
18cec75f41 | ||
|
|
7621842952 | ||
|
|
01da1b44da | ||
|
|
b8f262b01b | ||
|
|
0e9a57a1da | ||
|
|
e374522725 | ||
|
|
e402f33132 | ||
|
|
e85655c0ec | ||
|
|
227ffbc4bc | ||
|
|
b41f819783 | ||
|
|
d6c3635c6a | ||
|
|
37e98e4248 | ||
|
|
d2f4369f8d | ||
|
|
bd33314c4f | ||
|
|
e6941f7f7f | ||
|
|
4fdf35a55f | ||
|
|
e6441a9c8c | ||
|
|
8a3265bff2 | ||
|
|
d871d8b392 | ||
|
|
f1fbe34b58 | ||
|
|
d232b6404d | ||
|
|
ea724f7ed8 | ||
|
|
2b04bae260 | ||
|
|
84f1e25648 | ||
|
|
ef8c6fbcec | ||
|
|
12d462b89d | ||
|
|
53065d8365 | ||
|
|
1bf8f756e5 | ||
|
|
1e513cc133 | ||
|
|
7ce53439b1 | ||
|
|
e90d664761 | ||
|
|
fc11e701e4 | ||
|
|
3c7ebef8a7 | ||
|
|
5d70c89f2d | ||
|
|
7149c0a824 | ||
|
|
74c6bc5737 | ||
|
|
bac6c8b1e9 | ||
|
|
9e7d955bc4 | ||
|
|
af7c4464bb | ||
|
|
cfb128f3c8 | ||
|
|
451a828a05 | ||
|
|
678bc7015c | ||
|
|
013aa97143 | ||
|
|
45b7e480b7 | ||
|
|
09772bae29 | ||
|
|
c01130fcc7 | ||
|
|
2d4159b4cf | ||
|
|
6dcd1ce5e7 | ||
|
|
6c913b2c36 | ||
|
|
c453705a80 | ||
|
|
bb9a2a2408 | ||
|
|
54709a8c70 | ||
|
|
64135b55a7 | ||
|
|
1ff4ba440d | ||
|
|
72da6203a1 | ||
|
|
e2bc560c7d | ||
|
|
e40241bac8 | ||
|
|
01c8c5c194 | ||
|
|
5eb5ef204e | ||
|
|
ee90bc0730 | ||
|
|
5af068e0d6 | ||
|
|
7e228b729e | ||
|
|
ec4c61f38e | ||
|
|
9ed610e3a4 | ||
|
|
a9e7fc616e | ||
|
|
9d5ffbe26e | ||
|
|
3829e90844 | ||
|
|
f181d8b095 | ||
|
|
a2270e70af | ||
|
|
34604d53b0 | ||
|
|
6257b96bc3 | ||
|
|
1d8912f60d | ||
|
|
3c9b63d009 | ||
|
|
e6f9f64b4a | ||
|
|
a8ad4149e0 | ||
|
|
3924812985 | ||
|
|
fef0f3a987 | ||
|
|
1c012322fd | ||
|
|
545e8aa670 | ||
|
|
449bd500c2 | ||
|
|
ea295f2f41 | ||
|
|
9175b8abe8 | ||
|
|
9d4a61ea99 | ||
|
|
cb50fdc70d | ||
|
|
cf488e1440 | ||
|
|
4fc6def1cd | ||
|
|
e0b2fd2525 | ||
|
|
8e08201b16 | ||
|
|
c347a2d99a | ||
|
|
5b7d8d4db4 | ||
|
|
dd0692e91f | ||
|
|
576da66194 | ||
|
|
f4851d6771 | ||
|
|
96abb0d56e | ||
|
|
fdf3141c91 | ||
|
|
6692d234da | ||
|
|
b8655e31ca | ||
|
|
e118fe4d6e | ||
|
|
86283195c5 | ||
|
|
a037646aca | ||
|
|
ae6bdcdd5f | ||
|
|
9fe822024c | ||
|
|
fc94041d17 | ||
|
|
a3f780b1f5 | ||
|
|
9487f19233 | ||
|
|
2988374769 | ||
|
|
22ab7260f4 | ||
|
|
27f6f62f8c | ||
|
|
33ab787a95 | ||
|
|
4f2cde2599 | ||
|
|
d419084216 | ||
|
|
553e0081fa | ||
|
|
0716922b7c | ||
|
|
7e227d994b | ||
|
|
dd1b702966 | ||
|
|
0963f89049 | ||
|
|
cda3f1c0a7 | ||
|
|
1247bcb8c0 | ||
|
|
36476b32ab | ||
|
|
630135ac1c | ||
|
|
0a86c7dc92 | ||
|
|
a8a08f8978 | ||
|
|
8b8d35573d | ||
|
|
36ed80212d | ||
|
|
131e6e1460 | ||
|
|
5c799a8197 | ||
|
|
488c5d3c22 | ||
|
|
e93ffc4c77 | ||
|
|
e4c4e324b8 | ||
|
|
e5e012a700 | ||
|
|
9bddcf1c8e | ||
|
|
ba2fdbc836 | ||
|
|
3f809d38fb | ||
|
|
d66d80bb0c | ||
|
|
4c707cf5b9 | ||
|
|
8670bdb588 | ||
|
|
4c5bdd089e | ||
|
|
c3eb1b1ad3 | ||
|
|
1e1e1558b4 | ||
|
|
d4abd693de | ||
|
|
d38718dcdf | ||
|
|
3a3376c303 | ||
|
|
05043ac74d | ||
|
|
5f89bfcbd8 | ||
|
|
d7d56d9ebf | ||
|
|
1592eadd8c | ||
|
|
be1a4c750a | ||
|
|
4572ce3d6b | ||
|
|
fb7d4e6074 | ||
|
|
5467c991de | ||
|
|
ba93c6cc46 | ||
|
|
60b37e4f72 | ||
|
|
02a543323c | ||
|
|
c28bc22084 | ||
|
|
f796dd6076 | ||
|
|
5d624f5c23 | ||
|
|
3eacfb27a6 | ||
|
|
29b3d32743 | ||
|
|
a58e237122 | ||
|
|
c237b33677 | ||
|
|
b48b618d4c | ||
|
|
252834d727 | ||
|
|
04bcd9fd3a | ||
|
|
cc5aba7896 | ||
|
|
50bc5c2d02 | ||
|
|
3251b0dc58 | ||
|
|
090f524b9d | ||
|
|
1846b5da64 | ||
|
|
643dfd8fc0 | ||
|
|
944d721296 | ||
|
|
96e4afd6c8 | ||
|
|
002e6657ff | ||
|
|
fb215b11b7 | ||
|
|
1618289078 | ||
|
|
2ebe42d5f4 | ||
|
|
326271f703 | ||
|
|
0480f54287 | ||
|
|
f9df8c7fab | ||
|
|
2ae550c027 | ||
|
|
eca131fefc | ||
|
|
596645ad6b | ||
|
|
2a2d940e7c | ||
|
|
46b0c595f9 | ||
|
|
eab7a30926 | ||
|
|
85a8efb5bf | ||
|
|
e33ee49879 | ||
|
|
9c53204a91 | ||
|
|
6faf1ccf86 | ||
|
|
8fafe96e01 | ||
|
|
d06c995174 | ||
|
|
2d02904ed1 | ||
|
|
51d7ddb362 | ||
|
|
ee1fbad02a | ||
|
|
f5ae98cb9c | ||
|
|
e416478522 | ||
|
|
ce85dffc97 | ||
|
|
c9f7a43339 | ||
|
|
a885fb7edf | ||
|
|
93d1542522 | ||
|
|
62fadbb394 | ||
|
|
ae37b6533b | ||
|
|
c711fd0ce1 | ||
|
|
05af23b87c | ||
|
|
54b505449b | ||
|
|
8e5f5f2dbd | ||
|
|
c06a9ec874 | ||
|
|
8639a3d363 | ||
|
|
a9ef1ebd66 | ||
|
|
1053191835 | ||
|
|
7d0b536f73 | ||
|
|
cf45a1fc7e | ||
|
|
cc2ada5169 | ||
|
|
cf4029ff00 | ||
|
|
76e4feb1ac | ||
|
|
4a5103d760 | ||
|
|
0b72895102 | ||
|
|
f41cac9fc7 | ||
|
|
8ad1400d3c | ||
|
|
e48a9213c5 | ||
|
|
60e6a8b24f | ||
|
|
85af08b1aa | ||
|
|
2d4bbd9ab0 | ||
|
|
06631a81ad | ||
|
|
24684ac25a | ||
|
|
758de6b088 | ||
|
|
0346898501 | ||
|
|
2e2d092b18 | ||
|
|
c8aefae0cc | ||
|
|
17e465b3ab | ||
|
|
40bb75bf33 | ||
|
|
02201afc59 | ||
|
|
bae98bba28 | ||
|
|
0eb7aeaed1 | ||
|
|
dbd5022c4c | ||
|
|
72ac73c410 | ||
|
|
9b52c81b25 | ||
|
|
7cc13b9ba9 | ||
|
|
3ba866d2d6 | ||
|
|
3d058d9164 | ||
|
|
fde8b16fef | ||
|
|
c5a1eb6f81 | ||
|
|
74f8b60472 | ||
|
|
53846762f0 | ||
|
|
788d5ba4ee | ||
|
|
d57085949c | ||
|
|
9dc40a417a | ||
|
|
81e5592b81 | ||
|
|
44867f0173 | ||
|
|
c46f7357c3 | ||
|
|
47b8dce966 | ||
|
|
63b08d1acb | ||
|
|
fa05fcf3d4 | ||
|
|
f9846e95fd | ||
|
|
def4f9003c | ||
|
|
06cdc03287 | ||
|
|
c3bfbbb6e1 | ||
|
|
c7ed9eb3fd | ||
|
|
a023202703 |
@@ -7,6 +7,7 @@
|
||||
#logs
|
||||
logs/*.json
|
||||
*.log
|
||||
*.log.*
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
@@ -64,6 +65,12 @@ database/
|
||||
system-user-*.json
|
||||
orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
ipfs-service-provider.sh
|
||||
start-bch-wallet-service.sh
|
||||
data/
|
||||
run-dev.sh
|
||||
|
||||
!README.md
|
||||
|
||||
wallet.json
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"srcDir": "",
|
||||
"destDir": "",
|
||||
"files": "**/*.js",
|
||||
"command": "npm run lint"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,93 @@
|
||||
# Permissionless Software Foundation Community Contributing Guide 1.0
|
||||
|
||||
This document describes a very simple process suitable for most projects under
|
||||
the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md).
|
||||
Projects are encouraged to adopt this whether they
|
||||
are hosted under the PSF or not.
|
||||
|
||||
The goal of this document is to create a contribution process that:
|
||||
|
||||
* Encourages new contributions.
|
||||
* Encourages contributors to remain involved.
|
||||
* Avoids unnecessary processes and bureaucracy whenever possible.
|
||||
* Creates a transparent decision making process which makes it clear how
|
||||
contributors can be involved in decision making.
|
||||
|
||||
This document is based on much prior art in the Node.js community, io.js,
|
||||
and the Node.js project.
|
||||
|
||||
Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software).
|
||||
|
||||
## Vocabulary
|
||||
|
||||
* A **Contributor** is any individual creating or commenting on an issue or pull request.
|
||||
* A **Committer** is a subset of contributors who have been given write access to the repository.
|
||||
* A **TC (Technical Committee)** is a group of committers representing the required technical
|
||||
expertise to resolve rare disputes.
|
||||
|
||||
# Logging Issues
|
||||
|
||||
Log an issue for any question or problem you might have. When in doubt, log an issue,
|
||||
any additional policies about what to include will be provided in the responses. The only
|
||||
exception is security disclosures which should be sent privately.
|
||||
|
||||
Committers may direct you to another repository, ask for additional clarifications, and
|
||||
add appropriate metadata before the issue is addressed.
|
||||
|
||||
Please be courteous, respectful, and every participant is expected to follow the
|
||||
project's Code of Conduct.
|
||||
|
||||
# Contributions
|
||||
|
||||
Any change to resources in this repository must be through pull requests. This applies to all changes
|
||||
to documentation, code, binary files, etc. Even long term committers and TC members must use
|
||||
pull requests.
|
||||
|
||||
No pull request can be merged without being reviewed.
|
||||
|
||||
For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
|
||||
contributors in other timezones have time to review. Consideration should also be given to
|
||||
weekends and other holiday periods to ensure active committers all have reasonable time to
|
||||
become involved in the discussion and review process if they wish.
|
||||
|
||||
The default for each contribution is that it is accepted once no committer has an objection.
|
||||
During review committers may also request that a specific contributor who is most versed in a
|
||||
particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
|
||||
process for contributions to land. Once all issues brought by committers are addressed it can
|
||||
be landed by any committer.
|
||||
|
||||
In the case of an objection being raised in a pull request by another committer, all involved
|
||||
committers should seek to arrive at a consensus by way of addressing concerns being expressed
|
||||
by discussion, compromise on the proposed change, or withdrawal of the proposed change.
|
||||
|
||||
If a contribution is controversial and committers cannot agree about how to get it to land
|
||||
or if it should land then it should be escalated to the TC. TC members should regularly
|
||||
discuss pending contributions in order to find a resolution. It is expected that only a
|
||||
small minority of issues be brought to the TC for resolution and that discussion and
|
||||
compromise among committers be the default resolution mechanism.
|
||||
|
||||
# Becoming a Committer
|
||||
|
||||
All contributors who land a non-trivial contribution should be on-boarded in a timely manner,
|
||||
and added as a committer, and be given write access to the repository.
|
||||
|
||||
Committers are expected to follow this policy and continue to send pull requests, go through
|
||||
proper review, and have other committers merge their pull requests.
|
||||
|
||||
# TC Process
|
||||
|
||||
The TC uses a "consensus seeking" process for issues that are escalated to the TC.
|
||||
The group tries to find a resolution that has no open objections among TC members.
|
||||
If a consensus cannot be reached that has no objections then a majority wins vote
|
||||
is called. It is also expected that the majority of decisions made by the TC are via
|
||||
a consensus seeking process and that voting is only used as a last-resort.
|
||||
|
||||
Resolution may involve returning the issue to committers with suggestions on how to
|
||||
move forward towards a consensus. It is not expected that a meeting of the TC
|
||||
will resolve all issues on its agenda during that meeting and may prefer to continue
|
||||
the discussion happening among the committers.
|
||||
|
||||
Members can be added to the TC at any time. Any committer can nominate another committer
|
||||
to the TC and the TC uses its standard consensus seeking process to evaluate whether or
|
||||
not to add this new member. Members who do not participate consistently at the level of
|
||||
a majority of the other members are expected to resign.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2021 Permissionless Software Foundation
|
||||
Copyright (c) 2021-2024 Permissionless Software Foundation
|
||||
|
||||
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:
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pedigree
|
||||
|
||||
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
This is a censorship-resistant, IPFS-based microservice that provides access for wallets to access the Bitcoin Cash (BCH) blockchain. It leverages [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js).
|
||||
|
||||
- [Decentralized Blockchain Service Providers](https://youtu.be/m_33rRXEats) YouTube video demos this software.
|
||||
- [JSON RPC and REST API Documentation](https://ipfs-bch-wallet-service.fullstack.cash/)
|
||||
|
||||
## About This Repository
|
||||
|
||||
This repository is forked from the [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider) repository. That code has been customized to provide BCH blockchain access for wallets. This server-side node.js app provides both REST API over HTTP and JSON RPC over IPFS endpoints for wallets to access the blockchain through.
|
||||
@@ -21,35 +24,74 @@ This repository is one step in a longer path. Here are the major milestones of t
|
||||
|
||||
## Requirements
|
||||
|
||||
- node **^14.17.0**
|
||||
- npm **^7.13.0**
|
||||
- node **^16.20.2**
|
||||
- npm **^8.19.4**
|
||||
- Docker **^20.10.8**
|
||||
- Docker Compose **^1.27.4**
|
||||
|
||||
## Installation
|
||||
|
||||
### Development Environment
|
||||
|
||||
**Note:** This software now uses an external go-ipfs IPFS node. The instructions below have not been updated to reflect this.
|
||||
|
||||
A development environment will allow you modify the code on-the-fly and contribute to the code base of this repository. [PM2](https://www.npmjs.com/package/pm2) is recommended for running this code base as an IPFS Circuit Relay.
|
||||
|
||||
- [Video: Installing ipfs-service-provider](https://youtu.be/Z0NsboIVN44)
|
||||
- [Step-by-step installation instructions](https://gist.github.com/christroutner/3304a71d4c12a3a3e1664a438f64d9d0)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service
|
||||
cd ipfs-bch-wallet-service
|
||||
./install-mongo.sh
|
||||
npm install
|
||||
npm start
|
||||
cd shell-scripts
|
||||
./ipfs-bch-wallet-service.sh
|
||||
```
|
||||
|
||||
## Structure
|
||||
### Production Environment
|
||||
|
||||
The file layout of this repository differs from the koa-api-boilerplate. Instead, it follows the file layout of [Clean Architecture](troutsblog.com/blog/clean-architecture).
|
||||
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment.
|
||||
|
||||
```
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
- You can bring the containers down with `docker-compose down`
|
||||
- You can bring the containers back up with `docker-compose up -d`.
|
||||
|
||||
### Configuration
|
||||
|
||||
This program is intended to started with a Bash shell script. In that script, you can set the following environment variables:
|
||||
|
||||
```bash
|
||||
# The instance of bch-api to connect to.
|
||||
export APISERVER=http://172.17.0.1:3000/v5/
|
||||
```
|
||||
|
||||
### Configuration
|
||||
|
||||
This app is intended to be started via a bash shell script. See the environment variables used to configure this app in the [config/env/common.js file](./config/env/common.js).
|
||||
|
||||
## File Structure
|
||||
|
||||
The file layout of this repository follows the file layout of [Clean Architecture](https://christroutner.github.io/trouts-blog/blog/clean-architecture). Understaning the principles laid out this article will help developers navigate the code base.
|
||||
|
||||
## Usage
|
||||
|
||||
- `npm start` Start server on live mode
|
||||
- `npm run docs` Generate API documentation
|
||||
- `npm test` Run mocha tests
|
||||
- `docker-compose build` Build a 'production' Docker container
|
||||
- `docker-compose up` Run the docker container
|
||||
|
||||
## Documentation
|
||||
|
||||
API documentation is written inline and generated by [apidoc](http://apidocjs.com/).
|
||||
API documentation is written inline and generated by [apidoc](http://apidocjs.com/). Docs can be generated with this command:
|
||||
- `npm run docs`
|
||||
|
||||
Visit `http://localhost:5000/docs/` to view docs
|
||||
Visit `http://localhost:5020/` to view docs
|
||||
|
||||
There is additional developer documentation in the [dev-docs directory](./dev-docs).
|
||||
|
||||
## Dependencies
|
||||
|
||||
@@ -74,3 +116,5 @@ Snapshots pinned to IPFS will be listed here.
|
||||
## License
|
||||
|
||||
[MIT](./LICENSE.md)
|
||||
|
||||
a
|
||||
|
||||
+137
-80
@@ -1,89 +1,146 @@
|
||||
/*
|
||||
This Koa server has two interfaces:
|
||||
- REST API over HTTP
|
||||
- JSON RPC over IPFS
|
||||
|
||||
The architecture of the code follows the Clean Architecture pattern:
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
const Koa = require('koa')
|
||||
const bodyParser = require('koa-bodyparser')
|
||||
const convert = require('koa-convert')
|
||||
const logger = require('koa-logger')
|
||||
const mongoose = require('mongoose')
|
||||
const session = require('koa-generic-session')
|
||||
const passport = require('koa-passport')
|
||||
const mount = require('koa-mount')
|
||||
const serve = require('koa-static')
|
||||
const cors = require('kcors')
|
||||
import Koa from 'koa'
|
||||
|
||||
import bodyParser from 'koa-bodyparser'
|
||||
import convert from 'koa-convert'
|
||||
import logger from 'koa-logger'
|
||||
import mongoose from 'mongoose'
|
||||
import session from 'koa-generic-session'
|
||||
import passport from 'koa-passport'
|
||||
import mount from 'koa-mount'
|
||||
import serve from 'koa-static'
|
||||
import cors from 'kcors'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../config') // this first.
|
||||
// const IPFSLib = require('../src/lib/ipfs')
|
||||
const AdminLib = require('../src/adapters/admin')
|
||||
const adminLib = new AdminLib()
|
||||
// const JSONRPC = require('../src/rpc')
|
||||
// const rpc = new JSONRPC()
|
||||
import config from '../config/index.js' // this first.
|
||||
|
||||
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
|
||||
const { wlogger } = require('../src/adapters/wlogger')
|
||||
import AdminLib from '../src/adapters/admin.js'
|
||||
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
||||
import wlogger from '../src/adapters/wlogger.js'
|
||||
import Controllers from '../src/controllers/index.js'
|
||||
import { applyPassportMods } from '../config/passport.js'
|
||||
|
||||
async function startServer () {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
app.use(convert(logger()))
|
||||
app.use(bodyParser())
|
||||
app.use(session())
|
||||
app.use(errorMiddleware())
|
||||
|
||||
// Used to generate the docs.
|
||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||
|
||||
// Mount the page for displaying logs.
|
||||
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
|
||||
|
||||
// User Authentication
|
||||
require('../config/passport')
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const controllers = require('../src/controllers')
|
||||
await controllers.attachControllers(app)
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
console.log(`Running server in environment: ${config.env}`)
|
||||
wlogger.info(`Running server in environment: ${config.env}`)
|
||||
|
||||
await app.listen(config.port)
|
||||
console.log(`Server started on ${config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
try {
|
||||
const success = await adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Error trying to create system admin. Perhaps one already exists?'
|
||||
)
|
||||
class Server {
|
||||
constructor () {
|
||||
// Encapsulate dependencies
|
||||
this.adminLib = new AdminLib()
|
||||
this.controllers = new Controllers()
|
||||
this.mongoose = mongoose
|
||||
this.config = config
|
||||
this.process = process
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
// startServer()
|
||||
async startServer () {
|
||||
try {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [this.config.session]
|
||||
|
||||
// export default app
|
||||
// module.exports = app
|
||||
module.exports = {
|
||||
startServer
|
||||
if (!this.config.noMongo) {
|
||||
// Connect to the Mongo Database.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`Starting environment: ${this.config.env}`)
|
||||
console.log(`Debug level: ${this.config.debugLevel}`)
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
app.use(convert(logger()))
|
||||
app.use(bodyParser())
|
||||
app.use(session())
|
||||
app.use(errorMiddleware())
|
||||
|
||||
// Used to generate the docs.
|
||||
app.use(mount('/', serve(`${process.cwd()}/docs`)))
|
||||
|
||||
// Mount the page for displaying logs.
|
||||
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
|
||||
|
||||
// User Authentication
|
||||
// require('../config/passport')
|
||||
applyPassportMods(passport)
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// Wait for any adapters to initialize.
|
||||
await this.controllers.initAdapters()
|
||||
|
||||
// Wait for any use-libraries to initialize.
|
||||
await this.controllers.initUseCases()
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
await this.controllers.attachRESTControllers(app)
|
||||
|
||||
app.controllers = this.controllers
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
console.log(`Running server in environment: ${this.config.env}`)
|
||||
wlogger.info(`Running server in environment: ${this.config.env}`)
|
||||
|
||||
this.server = await app.listen(this.config.port)
|
||||
console.log(`Server started on ${this.config.port}`)
|
||||
|
||||
if (!this.config.noMongo) {
|
||||
try {
|
||||
// Create the system admin user.
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
} catch (err) {
|
||||
console.log('Could not create system admin user.')
|
||||
}
|
||||
}
|
||||
|
||||
// Attach the other IPFS controllers.
|
||||
// Skip if this is a test environment.
|
||||
if (this.config.env !== 'test') {
|
||||
await this.controllers.attachControllers(app)
|
||||
}
|
||||
|
||||
// Display configuration settings
|
||||
console.log('\nConfiguration:')
|
||||
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
|
||||
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
|
||||
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
|
||||
|
||||
return app
|
||||
} catch (err) {
|
||||
console.error('Could not start server. Error: ', err)
|
||||
|
||||
console.log(
|
||||
'Exiting after 5 seconds. Depending on process manager to restart.'
|
||||
)
|
||||
await this.sleep(5000)
|
||||
this.process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sleep (ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
|
||||
export default Server
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
This file should be customized for your app. The string in this file is
|
||||
displayed when a user makes the 'about' JSON RPC call.
|
||||
*/
|
||||
|
||||
module.exports = `
|
||||
This is the about string for the ipfs-service-provider repository:
|
||||
https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
|
||||
Brought to you by the Permissionless Software Foundation:
|
||||
https://PSFoundation.cash
|
||||
|
||||
Documentation:
|
||||
https://ipfs-service-provider.fullstack.cash/
|
||||
`
|
||||
Vendored
+108
-13
@@ -1,13 +1,34 @@
|
||||
/*
|
||||
This file is used to store unsecure, application-specific data common to all
|
||||
environments.
|
||||
|
||||
Additional Environent Variables:
|
||||
- CONNECT_PREF: should have a value of 'cr' (default), or 'direct'. This is
|
||||
used by helia-coord to select a connection preference between peers. Servers
|
||||
with an ip4 or ip6 address should use 'direct'.
|
||||
*/
|
||||
|
||||
/* eslint no-unneeded-ternary:0 */
|
||||
|
||||
module.exports = {
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
// Get the version from the package.json file.
|
||||
import { readFileSync } from 'fs'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../package.json`))
|
||||
const version = pkgInfo.version
|
||||
|
||||
const ipfsCoordName = process.env.COORD_NAME
|
||||
? process.env.COORD_NAME
|
||||
: 'ipfs-bch-wallet-service'
|
||||
|
||||
console.log('GET_JWT_AT_STARTUP: ', process.env.GET_JWT_AT_STARTUP)
|
||||
|
||||
export default {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5001,
|
||||
port: process.env.PORT || 5032,
|
||||
|
||||
// Password for HTML UI that displays logs.
|
||||
logPass: 'test',
|
||||
@@ -23,29 +44,80 @@ module.exports = {
|
||||
? process.env.EMAILPASS
|
||||
: 'emailpassword',
|
||||
|
||||
// Enable or Disable the usage of Mongo DB.
|
||||
noMongo: process.env.NO_MONGO ? true : false,
|
||||
|
||||
// BEGIN WALLET CONFIGURATION
|
||||
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||
|
||||
// (Optional) use a wallet file generated by psf-bch-wallet.
|
||||
walletFile: process.env.WALLET_FILE
|
||||
? process.env.WALLET_FILE
|
||||
: `${__dirname.toString()}/../../wallet.json`,
|
||||
|
||||
// Wallet Configuration
|
||||
// Default value: web3 - connect to bch-api through web 3 JSON RPC over IPFS.
|
||||
// Alternate value: web2 - connect to bch-api through a web 2 REST API over HTTP.
|
||||
walletInterface: process.env.WALLET_INTERFACE ? process.env.WALLET_INTERFACE : 'web3',
|
||||
|
||||
// Wallet API Server
|
||||
// Default web2 server: https://api.fullstack.cash/v5/
|
||||
// Default web3 server: https://free-bch.fullstack.cash/
|
||||
apiServer: process.env.WALLET_INTERFACE === 'web2'
|
||||
? (
|
||||
process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/'
|
||||
)
|
||||
: 'https://free-bch.fullstack.cash/',
|
||||
|
||||
// Basic Authentication Password for connecting to bch-api through a web 2
|
||||
// REST API over HTTP.
|
||||
authPass: process.env.WALLET_AUTH_PASS
|
||||
? process.env.WALLET_AUTH_PASS
|
||||
: '',
|
||||
|
||||
// FullStack.cash account information, used for automatic JWT handling.
|
||||
AUTHSERVER: process.env.AUTHSERVER
|
||||
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
|
||||
authServer: process.env.AUTHSERVER
|
||||
? process.env.AUTHSERVER
|
||||
: 'https://auth.fullstack.cash',
|
||||
APISERVER: process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/',
|
||||
FULLSTACKLOGIN: process.env.FULLSTACKLOGIN
|
||||
fullstackLogin: process.env.FULLSTACKLOGIN
|
||||
? process.env.FULLSTACKLOGIN
|
||||
: 'demo@demo.com',
|
||||
FULLSTACKPASS: process.env.FULLSTACKPASS ? process.env.FULLSTACKPASS : 'demo',
|
||||
fullstackPassword: process.env.FULLSTACKPASS
|
||||
? process.env.FULLSTACKPASS
|
||||
: 'demo',
|
||||
|
||||
// END WALLET CONFIGURATION
|
||||
|
||||
// BEGIN IPFS CONFIGURATION
|
||||
|
||||
// Debug verbosity level of helia-coord.
|
||||
// 0 = no debug logs. 3 = maximum logs.
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
|
||||
// Enable/Disable the IPFS node at startup. Enabled by default.
|
||||
useIpfs: process.env.DISABLE_IPFS ? false : true,
|
||||
|
||||
// IPFS settings.
|
||||
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
||||
// SSL domain used for websocket connection via browsers.
|
||||
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
|
||||
|
||||
// Information passed to other IPFS peers about this node.
|
||||
apiInfo: 'https://ipfs-service-provider.fullstack.cash/',
|
||||
apiInfo: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
|
||||
ipfsCoordName,
|
||||
|
||||
// JSON-LD and Schema.org schema with info about this app.
|
||||
announceJsonLd: {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'ipfs-bch-wallet-service',
|
||||
name: ipfsCoordName,
|
||||
version,
|
||||
protocol: 'ipfs-bch-wallet-service',
|
||||
description:
|
||||
'IPFS service providing BCH blockchain access needed by a wallet.',
|
||||
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
@@ -53,10 +125,33 @@ module.exports = {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
}
|
||||
},
|
||||
|
||||
// If this node has an IP4 address or domain name used to provide a REST API.
|
||||
web2Api: process.env.WEB2_API ? process.env.WEB2_API : null
|
||||
},
|
||||
|
||||
// IPFS Ports
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 5668,
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 5669
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 5669,
|
||||
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
||||
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
||||
ipfsApiPort: process.env.IPFS_API_PORT
|
||||
? parseInt(process.env.IPFS_API_PORT)
|
||||
: 5001,
|
||||
|
||||
chatPubSubChan: 'psf-ipfs-chat-001',
|
||||
|
||||
// This can add specific Circuit Relay v2 servers to connect to.
|
||||
bootstrapRelays: [
|
||||
// v2 Circuit Relay (Token Tiger)
|
||||
// '/ip4/137.184.93.145/tcp/8001/p2p/12D3KooWGMEKkdJfyZbwdH9EafZbRTtMn7FnhWPrE4MhRty2763g',
|
||||
|
||||
// v2 Circuit Relay server (FullStack.cash)
|
||||
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
|
||||
]
|
||||
|
||||
// END IPFS CONFIGURATION
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
specified.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/bch-service-dev',
|
||||
|
||||
Vendored
+2
-2
@@ -7,11 +7,11 @@
|
||||
so as not to conflict with the default host port of 27017 for MongoDB.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: process.env.DBURL
|
||||
? process.env.DBURL
|
||||
: 'mongodb://172.17.0.1:5555/bch-service-prod',
|
||||
: 'mongodb://172.17.0.1:5557/bch-service-prod',
|
||||
env: 'prod'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
This is the environment run by the test suite.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/bch-service-test',
|
||||
|
||||
+14
-3
@@ -1,6 +1,17 @@
|
||||
const common = require('./env/common')
|
||||
import common from './env/common.js'
|
||||
|
||||
import development from './env/development.js'
|
||||
import production from './env/production.js'
|
||||
import test from './env/test.js'
|
||||
|
||||
const env = process.env.SVC_ENV || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
console.log(`Loading config for this environment: ${env}`)
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
let config = development
|
||||
if (env === 'test') {
|
||||
config = test
|
||||
} else if (env === 'prod') {
|
||||
config = production
|
||||
}
|
||||
|
||||
export default Object.assign({}, common, config)
|
||||
|
||||
+32
-29
@@ -1,30 +1,6 @@
|
||||
const passport = require('koa-passport')
|
||||
const User = require('../src/adapters/localdb/models/users')
|
||||
const Strategy = require('passport-local')
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use(
|
||||
'local',
|
||||
new Strategy(
|
||||
{
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},
|
||||
passportCallback
|
||||
)
|
||||
)
|
||||
// import passport from 'koa-passport';
|
||||
import User from '../src/adapters/localdb/models/users.js'
|
||||
import Strategy from 'passport-local'
|
||||
|
||||
async function passportCallback (email, password, done) {
|
||||
try {
|
||||
@@ -49,5 +25,32 @@ async function passportCallback (email, password, done) {
|
||||
}
|
||||
}
|
||||
|
||||
// For testing
|
||||
module.exports = { passport, passportCallback }
|
||||
function applyPassportMods (passport) {
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use(
|
||||
'local',
|
||||
new Strategy(
|
||||
{
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},
|
||||
passportCallback
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export { applyPassportMods, passportCallback }
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
This is where the mongodb docker container stores its db files.
|
||||
@@ -0,0 +1,10 @@
|
||||
# Developer Documentation
|
||||
|
||||
This directory needs to be fleshed out with more information. The markdown files in this directory are intended to provide additional documentation for software developers that use this boilerplate to run their own forks.
|
||||
|
||||
## Startup
|
||||
This software requires an IPFS node in order to operate the JSON RPC and ipfs-coord features. There are two ways to incorporate an IPFS node:
|
||||
|
||||
- js-ipfs is integrated into this repository. If you simply run `npm start`, then this app will default to the js-ipfs IPFS node. However, js-ipfs is not as full featured as go-ipfs. It has memory leaks and lags considerably behind go-ipfs in terms of features.
|
||||
|
||||
- go-ipfs is the preferred way to run a node. It can be run externally by following the guidence on the [IPFS homepage](https://ipfs.io). Once the IPFS node is running, this app can take control of it. Use the [local-external-ipfs-node.sh](../shell-scripts/local-external-ipfs-node.sh) shell script to start this app and attach it to the go-ipfs node.
|
||||
@@ -1,3 +1,4 @@
|
||||
const server = require('./bin/server.js')
|
||||
import Server from './bin/server.js'
|
||||
const server = new Server()
|
||||
|
||||
server.startServer()
|
||||
|
||||
Generated
+19083
-40660
File diff suppressed because it is too large
Load Diff
+63
-46
@@ -1,19 +1,21 @@
|
||||
{
|
||||
"name": "ipfs-bch-wallet-service",
|
||||
"version": "1.0.0",
|
||||
"version": "3.1.0",
|
||||
"description": "IPFS and REST service providing blockchain access needed by a wallet. ",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export SVC_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:all": "export SVC_ENV=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export SVC_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 30000 test/e2e/automated/",
|
||||
"test:integration": "export SVC_ENV=test && mocha --timeout 30000 --recursive test/integration/",
|
||||
"test:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"lint": "standard --env mocha --fix",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export SVC_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
"coverage": "c8 report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export SVC_ENV=test && c8 --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
},
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -23,50 +25,65 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-service",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "^4.20.2",
|
||||
"axios": "^0.21.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"glob": "^7.1.6",
|
||||
"ipfs": "^0.54.4",
|
||||
"ipfs-coord": "^4.0.1",
|
||||
"jsonrpc-lite": "^2.2.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwt-bch-lib": "^1.3.0",
|
||||
"kcors": "^2.2.2",
|
||||
"koa": "^2.13.1",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
"koa-convert": "^2.0.0",
|
||||
"koa-generic-session": "^2.1.1",
|
||||
"koa-logger": "^3.2.1",
|
||||
"koa-mount": "^4.0.0",
|
||||
"koa-passport": "^4.1.3",
|
||||
"koa-router": "^10.0.0",
|
||||
"koa-static": "^5.0.0",
|
||||
"koa2-ratelimit": "^0.9.0",
|
||||
"line-reader": "^0.4.0",
|
||||
"mongoose": "^5.11.15",
|
||||
"nodemailer": "^6.4.17",
|
||||
"passport-local": "^1.0.0",
|
||||
"winston": "^3.3.3",
|
||||
"winston-daily-rotate-file": "^4.5.0"
|
||||
"@chainsafe/libp2p-gossipsub": "13.1.1",
|
||||
"@chainsafe/libp2p-noise": "15.1.1",
|
||||
"@chainsafe/libp2p-yamux": "6.0.2",
|
||||
"@helia/unixfs": "3.0.7",
|
||||
"@libp2p/bootstrap": "10.1.5",
|
||||
"@libp2p/circuit-relay-v2": "1.1.5",
|
||||
"@libp2p/identify": "2.1.5",
|
||||
"@libp2p/keychain": "4.1.5",
|
||||
"@libp2p/logger": "4.0.20",
|
||||
"@libp2p/mplex": "10.0.16",
|
||||
"@libp2p/tcp": "9.1.5",
|
||||
"@libp2p/webrtc": "4.0.34",
|
||||
"@libp2p/websockets": "8.2.0",
|
||||
"@multiformats/multiaddr": "12.3.0",
|
||||
"@psf/bch-js": "6.7.3",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blockstore-fs": "2.0.0",
|
||||
"datastore-fs": "9.1.7",
|
||||
"glob": "7.1.6",
|
||||
"helia": "4.2.5",
|
||||
"helia-coord": "1.5.11",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
"kcors": "2.2.2",
|
||||
"koa": "2.13.1",
|
||||
"koa-bodyparser": "4.3.0",
|
||||
"koa-convert": "2.0.0",
|
||||
"koa-generic-session": "2.1.1",
|
||||
"koa-logger": "3.2.1",
|
||||
"koa-mount": "4.0.0",
|
||||
"koa-passport": "4.1.3",
|
||||
"koa-router": "10.0.0",
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "1.9.1",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "5.11.1",
|
||||
"mongoose": "5.13.14",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.7.5",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "6.0.1",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "^0.26.0",
|
||||
"chai": "^4.3.0",
|
||||
"coveralls": "^3.1.0",
|
||||
"eslint": "7.19.0",
|
||||
"eslint-config-prettier": "^7.2.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"husky": "^4.3.8",
|
||||
"mocha": "^8.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"semantic-release": "^17.4.4",
|
||||
"sinon": "^9.2.4",
|
||||
"standard": "^16.0.3",
|
||||
"uuid": "^8.3.2"
|
||||
"apidoc": "0.51.1",
|
||||
"c8": "7.12.0",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "3.1.0",
|
||||
"husky": "4.3.8",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"mocha": "10.0.0",
|
||||
"semantic-release": "19.0.3",
|
||||
"sinon": "9.2.4",
|
||||
"standard": "17.0.0",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"release": {
|
||||
"publish": [
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# Create a Dockerized API server
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
|
||||
FROM christroutner/ct-base-ubuntu
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
#RUN useradd -ms /bin/bash safeuser
|
||||
#RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'password' change value below if you want a different password
|
||||
#RUN echo safeuser:password | chpasswd
|
||||
|
||||
#Set the working directory to be the home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install
|
||||
#RUN mkdir /home/safeuser/.npm-global
|
||||
#RUN chown -R safeuser .npm-global
|
||||
#RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Switch to user account.
|
||||
USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service
|
||||
# RUN mv koa-api-boilerplate koa
|
||||
# RUN mkdir keys
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/ipfs-bch-wallet-service
|
||||
|
||||
# For development: switch to unstable branch
|
||||
# RUN git checkout unstable
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install
|
||||
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
# Calling out the directorys that should be mounted as external volumes.
|
||||
RUN mkdir /home/safeuser/ipfsdata
|
||||
RUN mkdir /home/safeuser/orbitdb
|
||||
VOLUME /home/safeuser/ipfsdata
|
||||
VOLUME /home/safeuser/orbitdb
|
||||
|
||||
# Start the application.
|
||||
COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
@@ -1,28 +0,0 @@
|
||||
# Start the testnet server with the command 'docker-compose up -d'
|
||||
|
||||
bch-wallet-mongodb:
|
||||
image: mongo
|
||||
container_name: mongo-bch-service
|
||||
ports:
|
||||
- '5555:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
- ./database:/data/db
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
bch-wallet-service:
|
||||
build: ./
|
||||
dockerfile: Dockerfile
|
||||
container_name: bch-wallet-service
|
||||
links:
|
||||
- bch-wallet-mongodb
|
||||
ports:
|
||||
# <host port>:<container port>
|
||||
- '5001:5001' # REST API
|
||||
- '5668:5668' # IPFS TCP Port
|
||||
- '5669:5669' # IPFS WS Port
|
||||
volumes:
|
||||
- ../ipfsdata:/home/safeuser/ipfsdata
|
||||
- ../orbitdb:/home/safeuser/orbitdb
|
||||
|
||||
restart: always
|
||||
@@ -0,0 +1 @@
|
||||
COMPOSE_PROJECT_NAME=bch-wallet-service
|
||||
@@ -0,0 +1,80 @@
|
||||
# Create a Dockerized API server
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
FROM ubuntu:20.04
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN sudo apt-get install -y ca-certificates curl gnupg
|
||||
RUN sudo mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN sudo apt-get update
|
||||
RUN sudo apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
RUN useradd -ms /bin/bash safeuser
|
||||
RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'password' change value below if you want a different password
|
||||
RUN echo safeuser:password | chpasswd
|
||||
|
||||
#Set the working directory to be the home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install
|
||||
RUN mkdir /home/safeuser/.npm-global
|
||||
RUN chown -R safeuser .npm-global
|
||||
RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
# Working with npm@7.21.1
|
||||
#RUN npm install -g npm
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
#RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Switch to user account.
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/ipfs-bch-wallet-service
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
|
||||
# Install dependencies
|
||||
#RUN npm install -g @mapbox/node-pre-gyp
|
||||
RUN npm install
|
||||
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
# Calling out the directorys that should be mounted as external volumes.
|
||||
RUN mkdir /home/safeuser/.ipfsdata
|
||||
#RUN mkdir /home/safeuser/orbitdb
|
||||
#VOLUME /home/safeuser/ipfsdata
|
||||
#VOLUME /home/safeuser/orbitdb
|
||||
|
||||
# Start the application.
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
@@ -0,0 +1,61 @@
|
||||
# Docker Containers
|
||||
|
||||
The 'production' environment is assumed to be a set of Docker containers orchestrated with Docker Compose. The files in this directory will stand up three Docker containers:
|
||||
|
||||
1 An instance of go-ipfs.
|
||||
2 An instance of MongoDB.
|
||||
3 The JavaScript software in this repository.
|
||||
|
||||
The software in this repository depends on the first two containers, so if they aren't running correctly, the application won't run correctly either.
|
||||
|
||||
## IPFS
|
||||
|
||||
IPFS can be a little tricky to set up. By default, the container uses the following ports:
|
||||
|
||||
- 4001 for TCP connections, exposed publicly.
|
||||
- 5001 for control by the application, exposed privately.
|
||||
- 8080 for an IPFS gateway, consumed by the application, exposed privately.
|
||||
|
||||
If you already have an IPFS node running on a the computer, you will need to change the ports to avaid a conflict. To change the ports from the default, you'll need to perform a series of steps, and the order of the steps matter.
|
||||
|
||||
1. Edit the `docker-compose.yml` file and change the ports. Then save the file. Here is an example:
|
||||
|
||||
```
|
||||
ports:
|
||||
- 4101:4101
|
||||
- 172.17.0.1:5101:5101
|
||||
- 172.17.0.1:8180:8180
|
||||
```
|
||||
|
||||
2. Bring the Docker containers up, and then back down. This will allow the IPFS container to create the config file that you'll need to edit.
|
||||
|
||||
- `docker-compose up -d`
|
||||
- Wait a few seconds.
|
||||
- `docker-compose down`
|
||||
|
||||
3. Update the generated config file at `../data/go-ipfs/data/config`, to update the ports in the config file, like this:
|
||||
|
||||
```
|
||||
"Addresses": {
|
||||
"API": "/ip4/0.0.0.0/tcp/5101",
|
||||
"Announce": [],
|
||||
"AppendAnnounce": [],
|
||||
"Gateway": "/ip4/0.0.0.0/tcp/8180",
|
||||
"NoAnnounce": [],
|
||||
"Swarm": [
|
||||
"/ip4/0.0.0.0/tcp/4101",
|
||||
"/ip6/::/tcp/4101",
|
||||
"/ip4/0.0.0.0/udp/4101/quic",
|
||||
"/ip6/::/udp/4101/quic"
|
||||
]
|
||||
},
|
||||
|
||||
```
|
||||
|
||||
4. Update the port changes in the `start-production.sh` shell script. This tells the application which ports to use, in order to control the IPFS node, are are used when signaling other nodes.
|
||||
|
||||
5. Quickly rebuild the containers, to add the modified `start-production.sh` shell script to the application Docker container:
|
||||
|
||||
- `docker-compose build`
|
||||
|
||||
6. Now start the containers, and the port changes to IPFS should be complete.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Start the service with the command 'docker-compose up -d'
|
||||
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo-bch-service:
|
||||
image: mongo:4.2.0
|
||||
container_name: mongo-bch-service
|
||||
ports:
|
||||
- '5557:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../data/mongodb:/data/db
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
ipfs-bch-wallet-service:
|
||||
build: .
|
||||
#image: christroutner/ipfs-service-provider:v5.0.1
|
||||
container_name: ipfs-bch-wallet-service
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 1000mb
|
||||
links:
|
||||
- mongo-bch-service
|
||||
ports:
|
||||
- '5032:5032' # <host port>:<container port>
|
||||
- '5668:5668'
|
||||
- '5669:5669'
|
||||
volumes:
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-service/.ipfsdata
|
||||
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-service/start-production.sh
|
||||
restart: always
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
# BEGIN: Optional configuration settings
|
||||
|
||||
# This mnemonic is used to set up persistent public key for e2ee
|
||||
# Replace this with your own 12-word mnemonic.
|
||||
# You can get one at https://wallet.fullstack.cash.
|
||||
export MNEMONIC="olive two muscle bottom coral ancient wait legend bronze useful process session"
|
||||
|
||||
# The human readable name this IPFS node identifies as.
|
||||
export COORD_NAME=bch-wallet-service-generic
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=1
|
||||
# For browsers to use your circuit realy, you must set up a domain, SSL certificate,
|
||||
# and you must forward that subdomain to the IPFS_WS_PORT.
|
||||
#export CR_DOMAIN=subdomain.yourdomain.com
|
||||
|
||||
# BCH Wallet Service Settings
|
||||
# Change APISERVER to point to your own installation of bch-api
|
||||
export APISERVER=https://api.fullstack.cash/v5/
|
||||
# These settings are used to proxy a subscription to FullStack.cash.
|
||||
#export GET_JWT_AT_STARTUP=1
|
||||
export AUTHSERVER=https://auth.fullstack.cash
|
||||
export FULLSTACKLOGIN=demo@demo.com
|
||||
export FULLSTACKPASS=demo
|
||||
|
||||
# END: Optional configuration settings
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5557/ipfs-service-prod
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5010
|
||||
|
||||
# Production settings using external go-ipfs node.
|
||||
export SVC_ENV=prod
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=5668
|
||||
export IPFS_WS_PORT=5669
|
||||
|
||||
# Set the debug level for helia-coord. 0-3.
|
||||
# 0 = no debug logs. 3 = maximum debug logs.
|
||||
export DEBUG_LEVEL=0
|
||||
|
||||
npm start
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Customize these environment variables for your own installation.
|
||||
export AUTHSERVER=https://auth.fullstack.cash
|
||||
export APISERVER=https://api.fullstack.cash/v5/
|
||||
export FULLSTACKLOGIN=demo@demo.com
|
||||
export FULLSTACKPASS=demo
|
||||
|
||||
# Ports
|
||||
export PORT=5001 # REST API
|
||||
export IPFS_TCP_PORT=5668 # IPSF TCP Port
|
||||
export IPFS_WS_PORT=5669 # IPFS WS Port
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
|
||||
|
||||
#export ENABLE_CIRCUIT_RELAY=1
|
||||
|
||||
export SVC_ENV=production
|
||||
npm start
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a generic ipfs-service-provider instance
|
||||
# using a local installation of bch-api.
|
||||
|
||||
# Ports
|
||||
export PORT=5002 # REST API port
|
||||
export IPFS_TCP_PORT=5268
|
||||
export IPFS_WS_PORT=5269
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# MongoDB connection string.
|
||||
#export DBURL=mongodb://localhost:27017/bch-service-dev
|
||||
|
||||
# Customize these environment variables for your own installation.
|
||||
export GET_JWT_AT_STARTUP=0
|
||||
export APISERVER=http://localhost:3000/v5/
|
||||
|
||||
|
||||
npm start
|
||||
Executable
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a generic ipfs-service-provider instance.
|
||||
|
||||
# Ports
|
||||
export PORT=5002 # REST API port
|
||||
export IPFS_TCP_PORT=5268
|
||||
export IPFS_WS_PORT=5269
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# MongoDB connection string.
|
||||
#export DBURL=mongodb://localhost:27017/bch-service-dev
|
||||
|
||||
npm start
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a generic ipfs-service-provider instance.
|
||||
|
||||
# Ports
|
||||
export PORT=5001 # REST API port
|
||||
export IPFS_TCP_PORT=5268
|
||||
export IPFS_WS_PORT=5269
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# MongoDB connection string.
|
||||
#export DBURL=mongodb://localhost:27017/bch-service-dev
|
||||
|
||||
npm start
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a ipfs-service-provider as a Circuit Relay.
|
||||
# Circuit Relays are help other nodes on the network communicate. They are
|
||||
# critical for reliable functioning of the network, and for circumventing
|
||||
# censorship.
|
||||
|
||||
# Ports
|
||||
export PORT=5001 # REST API port
|
||||
export IPFS_TCP_PORT=5268
|
||||
export IPFS_WS_PORT=5269
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# MongoDB connection string.
|
||||
#export DBURL=mongodb://localhost:27017/bch-service-dev
|
||||
|
||||
# Comment to disable circuit relay functionality. Or set to 1 to enable.
|
||||
export ENABLE_CIRCUIT_RELAY=1
|
||||
# For browsers to use your circuit realy, you must set up a domain, SSL certificate,
|
||||
# and you must forward that subdomain to the IPFS_WS_PORT.
|
||||
#export CR_DOMAIN=subdomain.yourdomain.com
|
||||
|
||||
npm start
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a production environment, which is
|
||||
# defined by running an external go-ipfs node.
|
||||
|
||||
# Ports
|
||||
export PORT=5010 # REST API port
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=ipfs-bch-service-generic
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=2
|
||||
|
||||
# Production settings that use external IPFS node.
|
||||
# https://github.com/christroutner/docker-ipfs
|
||||
export SVC_ENV=production
|
||||
export IPFS_HOST=localhost
|
||||
#export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
|
||||
# Configure IPFS ports
|
||||
export IPFS_TCP_PORT=4001
|
||||
#export IPFS_WS_PORT=5269
|
||||
|
||||
# MongoDB connection string.
|
||||
export DBURL=mongodb://localhost:27017/ipfs-service-dev
|
||||
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
|
||||
npm start
|
||||
Executable
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script is an example for running a production environment, which is
|
||||
# defined by running an external go-ipfs node.
|
||||
|
||||
# Ports
|
||||
export PORT=5010 # REST API port
|
||||
|
||||
# The human-readible name that is used when displaying data about this node.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
|
||||
# This is used for end-to-end encryption (e2ee).
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=0
|
||||
|
||||
# Production settings that use external IPFS node.
|
||||
# https://github.com/christroutner/docker-ipfs
|
||||
export SVC_ENV=production
|
||||
export IPFS_HOST=localhost
|
||||
# export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
|
||||
# Configure IPFS ports
|
||||
export IPFS_TCP_PORT=4001
|
||||
#export IPFS_WS_PORT=5269
|
||||
|
||||
# MongoDB connection string.
|
||||
export DBURL=mongodb://localhost:27017/ipfs-service-dev
|
||||
|
||||
npm start
|
||||
+18
-8
@@ -13,16 +13,24 @@
|
||||
in the Adapter directory.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
const axios = require('axios').default
|
||||
const mongoose = require('mongoose')
|
||||
const User = require('../adapters/localdb/models/users')
|
||||
const config = require('../../config')
|
||||
const JsonFiles = require('../adapters/json-files')
|
||||
// Global npm libraries
|
||||
import axios from 'axios'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
// Local libraries
|
||||
import User from '../adapters/localdb/models/users.js'
|
||||
import config from '../../config/index.js'
|
||||
import JsonFiles from '../adapters/json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const jsonFiles = new JsonFiles()
|
||||
|
||||
const JSON_FILE = `system-user-${config.env}.json`
|
||||
const JSON_PATH = `${__dirname.toString()}/../../config/${JSON_FILE}`
|
||||
const JSON_PATH = `${__dirname.toString()}../../config/${JSON_FILE}`
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
const context = {}
|
||||
@@ -82,6 +90,8 @@ class Admin {
|
||||
// applications like the Task Manager and the test scripts can access.
|
||||
|
||||
await jsonFiles.writeJSON(context, JSON_PATH)
|
||||
// console.log('context: ', context)
|
||||
// console.log('JSON_PATH: ', JSON_PATH)
|
||||
|
||||
return context
|
||||
} catch (err) {
|
||||
@@ -169,4 +179,4 @@ class Admin {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Admin
|
||||
export default Admin
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
import config from '../../config/index.js'
|
||||
import NodeMailer from '../adapters/nodemailer.js'
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const NodeMailer = require('../adapters/nodemailer')
|
||||
const nodemailer = new NodeMailer()
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -60,4 +59,4 @@ class ContactLib {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ContactLib
|
||||
export default ContactLib
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
for this file.
|
||||
*/
|
||||
|
||||
const JwtLib = require('jwt-bch-lib')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
import JwtLib from 'jwt-bch-lib'
|
||||
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
class FullStackJWT {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -23,13 +24,13 @@ class FullStackJWT {
|
||||
'Must pass a url for the API server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.login = localConfig.login
|
||||
this.login = localConfig.fullstackLogin
|
||||
if (!this.login || typeof this.login !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.password = localConfig.password
|
||||
this.password = localConfig.fullstackPassword
|
||||
if (!this.password || typeof this.password !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
|
||||
@@ -39,7 +40,7 @@ class FullStackJWT {
|
||||
// Encapsulate dependencies
|
||||
this.jwtLib = new JwtLib({
|
||||
// Overwrite default values with the values in the config file.
|
||||
server: this.server,
|
||||
server: this.authServer,
|
||||
login: this.login,
|
||||
password: this.password
|
||||
})
|
||||
@@ -52,12 +53,6 @@ class FullStackJWT {
|
||||
// Get's a JWT token from FullStack.cash.
|
||||
async getJWT () {
|
||||
try {
|
||||
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
|
||||
if (process.env.E2ETEST) {
|
||||
this.apiToken = 'faketoken'
|
||||
return this.apiToken
|
||||
}
|
||||
|
||||
// Log into the auth server.
|
||||
await this.jwtLib.register()
|
||||
|
||||
@@ -104,4 +99,4 @@ class FullStackJWT {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FullStackJWT
|
||||
export default FullStackJWT
|
||||
|
||||
+79
-35
@@ -4,44 +4,88 @@
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
// Load the config file
|
||||
const config = require('../../config')
|
||||
// Public NPM libraries
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
// Load individual adapter libraries.
|
||||
const IPFSAdapter = require('./ipfs')
|
||||
const LocalDB = require('./localdb')
|
||||
const LogsAPI = require('./logapi')
|
||||
const Passport = require('./passport')
|
||||
const Nodemailer = require('./nodemailer')
|
||||
const { wlogger } = require('./wlogger')
|
||||
const JSONFiles = require('./json-files')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
import IPFSAdapter from './ipfs/index.js'
|
||||
import LocalDB from './localdb/index.js'
|
||||
import LogsAPI from './logapi.js'
|
||||
import Passport from './passport.js'
|
||||
import Nodemailer from './nodemailer.js'
|
||||
// const { wlogger } = require('./wlogger')
|
||||
import JSONFiles from './json-files.js'
|
||||
import FullStackJWT from './fullstack-jwt.js'
|
||||
import config from '../../config/index.js'
|
||||
import Wallet from './wallet.adapter.js'
|
||||
|
||||
// Instantiate adapter libraries.
|
||||
const ipfs = new IPFSAdapter()
|
||||
const localdb = new LocalDB()
|
||||
const logapi = new LogsAPI()
|
||||
const passport = new Passport()
|
||||
const nodemailer = new Nodemailer()
|
||||
const jsonFiles = new JSONFiles()
|
||||
class Adapters {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
this.ipfs = new IPFSAdapter()
|
||||
this.localdb = new LocalDB()
|
||||
this.logapi = new LogsAPI()
|
||||
this.passport = new Passport()
|
||||
this.nodemailer = new Nodemailer()
|
||||
this.jsonFiles = new JSONFiles()
|
||||
this.bchjs = new BCHJS({ restURL: config.apiServer })
|
||||
this.config = config
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
const fullStackJwt = new FullStackJWT({
|
||||
authServer: config.AUTHSERVER,
|
||||
apiServer: config.APISERVER,
|
||||
login: config.FULLSTACKLOGIN,
|
||||
password: config.FULLSTACKPASS
|
||||
})
|
||||
const bchjs = {} // Placeholder.
|
||||
const walletConfig = {}
|
||||
if (config.walletInterface === 'web2') {
|
||||
walletConfig.restURL = config.apiServer
|
||||
walletConfig.interface = 'rest-api'
|
||||
}
|
||||
this.wallet = new Wallet(walletConfig)
|
||||
|
||||
module.exports = {
|
||||
ipfs,
|
||||
localdb,
|
||||
logapi,
|
||||
passport,
|
||||
nodemailer,
|
||||
wlogger,
|
||||
jsonFiles,
|
||||
fullStackJwt,
|
||||
bchjs
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
}
|
||||
|
||||
async start () {
|
||||
try {
|
||||
let apiToken
|
||||
if (this.config.getJwtAtStartup) {
|
||||
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
||||
// to all the rest of the apps controllers and adapters.
|
||||
apiToken = await this.fullStackJwt.getJWT()
|
||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||
this.bchjs = await this.fullStackJwt.instanceBchjs()
|
||||
} else {
|
||||
// console.log(
|
||||
// `Initializing bchjs with this restURL: ${this.config.apiServer}`
|
||||
// )
|
||||
this.bchjs = new BCHJS({ restURL: this.config.apiServer })
|
||||
}
|
||||
|
||||
// Create a default instance of minimal-slp-wallet without initializing it
|
||||
// (without retrieving the wallets UTXOs). This instance will be overwritten
|
||||
// if the operator has configured BCH payments.
|
||||
console.log('\nCreating default startup wallet. This wallet may be overwritten.')
|
||||
await this.wallet.instanceWalletWithoutInitialization({}, { apiToken })
|
||||
this.bchjs = this.wallet.bchWallet.bchjs
|
||||
// console.log('adapters/index.js this.bchjs.restURL: ', this.bchjs.restURL)
|
||||
|
||||
// Start the IPFS node.
|
||||
// Do not start these adapters if this is an e2e test.
|
||||
if (this.config.env !== 'test') {
|
||||
if (this.config.useIpfs) {
|
||||
await this.ipfs.start({ bchjs: this.bchjs })
|
||||
}
|
||||
} else {
|
||||
// These lines are here to ensure code coverage hits 100%.
|
||||
console.log('Not starting IPFS node since this is an e2e test.')
|
||||
}
|
||||
|
||||
console.log('Async Adapters have been started.')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/index.js/start()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Adapters
|
||||
|
||||
+156
-4
@@ -2,14 +2,19 @@
|
||||
top-level IPFS library that combines the individual IPFS-based libraries.
|
||||
*/
|
||||
|
||||
const IpfsAdapter = require('./ipfs')
|
||||
const IpfsCoordAdapter = require('./ipfs-coord')
|
||||
// Local libraries
|
||||
import IpfsAdapter from './ipfs.js'
|
||||
|
||||
import IpfsCoordAdapter from './ipfs-coord.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
class IPFS {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.ipfsAdapter = new IpfsAdapter()
|
||||
this.IpfsCoordAdapter = IpfsCoordAdapter
|
||||
this.process = process
|
||||
this.config = config
|
||||
|
||||
this.ipfsCoordAdapter = {} // placeholder
|
||||
|
||||
@@ -38,17 +43,164 @@ class IPFS {
|
||||
// Start ipfs-coord
|
||||
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
|
||||
ipfs: this.ipfs,
|
||||
bchjs
|
||||
bchjs,
|
||||
tcpPort: this.config.ipfsTcpPort,
|
||||
wsPort: this.config.ipfsWsPort
|
||||
})
|
||||
await this.ipfsCoordAdapter.start()
|
||||
console.log('ipfs-coord is ready.')
|
||||
|
||||
// Subscribe to the chat pubsub channel
|
||||
await this.ipfsCoordAdapter.subscribeToChat()
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/index.js/start()')
|
||||
|
||||
// If error is due to a lock file issue. Kill the process, so that
|
||||
// Docker or pm2 has a chance to restart the service.
|
||||
if (err.message.includes('Lock already being held')) {
|
||||
console.log('lock being held. Exiting.')
|
||||
this.process.exit(1)
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of this IPFS node.
|
||||
// Create an object with different metrics about this IPFS node, then return
|
||||
// the object.
|
||||
getStatus () {
|
||||
try {
|
||||
const statusObj = {
|
||||
ipfsId: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsId,
|
||||
multiAddrs: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsMultiaddrs,
|
||||
bchAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.bchAddr,
|
||||
slpAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.slpAddr,
|
||||
pubKey: this.ipfsCoordAdapter.ipfsCoord.thisNode.pubKey,
|
||||
peers: this.ipfsCoordAdapter.ipfsCoord.thisNode.peerList.length,
|
||||
relays: this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData.length
|
||||
}
|
||||
|
||||
return statusObj
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-coord.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get details on the other peers this node is connected to.
|
||||
async getPeers (showAll) {
|
||||
try {
|
||||
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
let ipfsPeers =
|
||||
await this.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.getPeers()
|
||||
// console.log('ipfsPeers: ', ipfsPeers)
|
||||
|
||||
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
|
||||
// console.log('filtered ipfsPeers: ', ipfsPeers)
|
||||
|
||||
const peerDataOut = []
|
||||
|
||||
// Loop through each IPFS peer and hydrate it with data from the peerData.
|
||||
for (let i = 0; i < ipfsPeers.length; i++) {
|
||||
const thisPeer = {
|
||||
peer: ipfsPeers[i]
|
||||
}
|
||||
|
||||
// if (!showAll) {
|
||||
// // Delete properties that don't contain good info.
|
||||
// delete thisPeer.muxer
|
||||
// delete thisPeer.latency
|
||||
// delete thisPeer.streams
|
||||
// }
|
||||
|
||||
// Get the ipfs-coord peer data for this peer.
|
||||
let thisPeerData = peerData.filter((x) =>
|
||||
x.from.includes(thisPeer.peer)
|
||||
)
|
||||
thisPeerData = thisPeerData[0]
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
|
||||
// Skip if peerData for this IPFS peer could not be found.
|
||||
// if (!thisPeerData) continue
|
||||
|
||||
try {
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
|
||||
// Add data to the IPFS peer data.
|
||||
thisPeer.name = thisPeerData.data.jsonLd.name
|
||||
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
|
||||
thisPeer.version = thisPeerData.data.jsonLd.version
|
||||
thisPeer.connectionAddr = thisPeerData.data.connectionAddr
|
||||
|
||||
if (showAll) {
|
||||
// Add all the peer data.
|
||||
thisPeer.peerData = thisPeerData
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`warning: ${thisPeer.peer}: ${err.message}`
|
||||
)
|
||||
}
|
||||
|
||||
peerDataOut.push(thisPeer)
|
||||
}
|
||||
|
||||
return peerDataOut
|
||||
} catch (err) {
|
||||
console.error('Error in getPeers(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Expects an array of peers and returns an array of peers with duplicates
|
||||
// removed.
|
||||
// This function is used by getPeers()
|
||||
_removeDuplicatePeers (arr) {
|
||||
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
|
||||
return arr.filter((v, i, a) => a.findIndex((t) => t === v) === i)
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
getRelays () {
|
||||
try {
|
||||
const relayData = this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData
|
||||
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`relayData: ${JSON.stringify(relayData, null, 2)}`)
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < relayData.length; i++) {
|
||||
const thisRelay = relayData[i]
|
||||
|
||||
// Find the peer that corresponds to this relay.
|
||||
const thisPeer = peerData.filter((x) =>
|
||||
x.from.includes(thisRelay.ipfsId)
|
||||
)
|
||||
// console.log('thisPeer: ', thisPeer)
|
||||
|
||||
// If the peer couldn't be found, skip.
|
||||
if (!thisPeer.length) {
|
||||
thisRelay.name = ''
|
||||
thisRelay.description = ''
|
||||
continue
|
||||
}
|
||||
|
||||
thisRelay.name = thisPeer[0].data.jsonLd.name
|
||||
thisRelay.description = thisPeer[0].data.jsonLd.description
|
||||
}
|
||||
|
||||
const v1Relays = this.config.v1Relays
|
||||
|
||||
return { v2Relays: relayData, v1Relays }
|
||||
} catch (err) {
|
||||
console.error('Error in getRelays(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IPFS
|
||||
export default IPFS
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const IpfsCoord = require('ipfs-coord')
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
// import IpfsCoord from 'ipfs-coord-esm'
|
||||
import IpfsCoord from 'helia-coord'
|
||||
|
||||
// import BCHJS from '@psf/bch-js';
|
||||
import SlpWallet from 'minimal-slp-wallet'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// const JSONRPC = require('../../controllers/json-rpc/')
|
||||
|
||||
let _this
|
||||
@@ -33,8 +38,10 @@ class IpfsCoordAdapter {
|
||||
// Encapsulate dependencies
|
||||
this.IpfsCoord = IpfsCoord
|
||||
this.ipfsCoord = {}
|
||||
// this.rpc = new JSONRPC()
|
||||
// this.bchjs = new BCHJS()
|
||||
this.wallet = new SlpWallet()
|
||||
this.config = config
|
||||
this.publicIp = publicIpv4
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
@@ -43,24 +50,56 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
|
||||
async start (localConfig = {}) {
|
||||
this.ipfsCoord = new this.IpfsCoord({
|
||||
const circuitRelayInfo = {}
|
||||
|
||||
// Wait for the BCH wallet to create the wallet.
|
||||
await this.wallet.walletInfoPromise
|
||||
|
||||
// If configured as a Circuit Relay, get the public IP addresses for this node.
|
||||
if (this.config.isCircuitRelay) {
|
||||
try {
|
||||
const ip4 = await this.publicIp()
|
||||
// const ip6 = await publicIp.v6()
|
||||
|
||||
circuitRelayInfo.ip4 = ip4
|
||||
circuitRelayInfo.tcpPort = this.config.ipfsTcpPort
|
||||
|
||||
// Domain used by browser-based secure websocket connections.
|
||||
circuitRelayInfo.crDomain = this.config.crDomain
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
}
|
||||
}
|
||||
|
||||
const nullLog = () => {}
|
||||
|
||||
const ipfsCoordOptions = {
|
||||
ipfs: this.ipfs,
|
||||
type: 'node.js',
|
||||
// type: 'browser',
|
||||
bchjs: this.bchjs,
|
||||
privateLog: console.log, // Default to console.log
|
||||
wallet: this.wallet,
|
||||
// privateLog: console.log, // Default to console.log
|
||||
privateLog: nullLog,
|
||||
isCircuitRelay: this.config.isCircuitRelay,
|
||||
circuitRelayInfo,
|
||||
apiInfo: this.config.apiInfo,
|
||||
announceJsonLd: this.config.announceJsonLd
|
||||
})
|
||||
|
||||
// Skip connecting ipfs-coord to the network if this is an E2E test.
|
||||
if (!process.env.E2ETEST) {
|
||||
// Wait for the ipfs-coord library to signal that it is ready.
|
||||
await this.ipfsCoord.ipfs.start()
|
||||
await this.ipfsCoord.isReady()
|
||||
announceJsonLd: this.config.announceJsonLd,
|
||||
mnemonic: this.config.mnemonic,
|
||||
debugLevel: this.config.debugLevel,
|
||||
v1Relays: this.config.v1Relays,
|
||||
tcpPort: this.config.ipfsTcpPort
|
||||
}
|
||||
|
||||
// Production env uses external go-ipfs node.
|
||||
// if (this.config.isProduction) {
|
||||
// ipfsCoordOptions.nodeType = 'external'
|
||||
// }
|
||||
|
||||
this.ipfsCoord = new this.IpfsCoord(ipfsCoordOptions)
|
||||
|
||||
// Wait for the ipfs-coord library to signal that it is ready.
|
||||
await this.ipfsCoord.start({ type: 'node.js' })
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
|
||||
@@ -72,12 +111,24 @@ class IpfsCoordAdapter {
|
||||
attachRPCRouter (router) {
|
||||
try {
|
||||
_this.ipfsCoord.privateLog = router
|
||||
_this.ipfsCoord.ipfs.orbitdb.privateLog = router
|
||||
_this.ipfsCoord.adapters.pubsub.privateLog = router
|
||||
} catch (err) {
|
||||
console.error('Error in attachRPCRouter()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to the chat pubsub channel
|
||||
async subscribeToChat () {
|
||||
// TODO: Allow user to replace nullog with their own log handler at startup.
|
||||
const nullLog = () => {}
|
||||
|
||||
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
|
||||
this.config.chatPubSubChan,
|
||||
nullLog,
|
||||
this.ipfsCoord.thisNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
export default IpfsCoordAdapter
|
||||
|
||||
+258
-44
@@ -2,80 +2,294 @@
|
||||
Clean Architecture Adapter for IPFS.
|
||||
This library deals with IPFS so that the apps business logic doesn't need
|
||||
to have any specific knowledge of the js-ipfs library.
|
||||
|
||||
TODO: Add the external IP address to the list of multiaddrs advertised by
|
||||
this node. See this GitHub Issue for details:
|
||||
https://github.com/Permissionless-Software-Foundation/ipfs-service-provider/issues/38
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const IPFS = require('ipfs')
|
||||
import { createHelia } from 'helia'
|
||||
import fs from 'fs'
|
||||
import { FsBlockstore } from 'blockstore-fs'
|
||||
import { FsDatastore } from 'datastore-fs'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { tcp } from '@libp2p/tcp'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
// import { bootstrap } from '@libp2p/bootstrap'
|
||||
// import { identifyService } from 'libp2p/identify'
|
||||
import { identify } from '@libp2p/identify'
|
||||
// import { circuitRelayServer, circuitRelayTransport } from 'libp2p/circuit-relay'
|
||||
import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
import { webRTC } from '@libp2p/webrtc'
|
||||
import { keychain } from '@libp2p/keychain'
|
||||
import { defaultLogger } from '@libp2p/logger'
|
||||
import { unixfs } from '@helia/unixfs'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
import JsonFiles from '../json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
// console.log('__dirname: ', __dirname)
|
||||
|
||||
const ROOT_DIR = `${__dirname}../../../`
|
||||
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
|
||||
|
||||
class IpfsAdapter {
|
||||
constructor (localConfig) {
|
||||
// Encapsulate dependencies
|
||||
this.IPFS = IPFS
|
||||
this.config = config
|
||||
this.fs = fs
|
||||
this.createLibp2p = createLibp2p
|
||||
this.publicIp = publicIpv4
|
||||
this.multiaddr = multiaddr
|
||||
this.jsonFiles = new JsonFiles()
|
||||
this.keychain = keychain
|
||||
this.createHelia = createHelia
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
this.config = config
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.start = this.start.bind(this)
|
||||
this.createNode = this.createNode.bind(this)
|
||||
this.stop = this.stop.bind(this)
|
||||
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
||||
this.getSeed = this.getSeed.bind(this)
|
||||
this.getKeychain = this.getKeychain.bind(this)
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
async start () {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptions = {
|
||||
repo: './ipfsdata',
|
||||
start: true,
|
||||
config: {
|
||||
relay: {
|
||||
enabled: true, // enable circuit relay dialer and listener
|
||||
hop: {
|
||||
enabled: true // enable circuit relay HOP (make this node a relay)
|
||||
}
|
||||
},
|
||||
pubsub: true, // enable pubsub
|
||||
Swarm: {
|
||||
ConnMgr: {
|
||||
HighWater: 30,
|
||||
LowWater: 10
|
||||
}
|
||||
},
|
||||
Addresses: {
|
||||
Swarm: [
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure the directory structure exists that is needed by the IPFS node to store data.
|
||||
this.ensureBlocksDir()
|
||||
|
||||
// Create a new IPFS node.
|
||||
this.ipfs = await this.IPFS.create(ipfsOptions)
|
||||
// Create an IPFS node
|
||||
const ipfs = await this.createNode()
|
||||
// console.log('ipfs: ', ipfs)
|
||||
|
||||
// Set the 'server' profile so the node does not scan private networks.
|
||||
await this.ipfs.config.profiles.apply('server')
|
||||
this.id = ipfs.libp2p.peerId.toString()
|
||||
console.log('IPFS ID: ', this.id)
|
||||
|
||||
// const nodeConfig = await this.ipfs.config.getAll()
|
||||
// console.log(
|
||||
// `IPFS node configuration: ${JSON.stringify(nodeConfig, null, 2)}`
|
||||
// )
|
||||
// Attempt to guess our ip4 IP address.
|
||||
const ip4 = await this.publicIp()
|
||||
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
|
||||
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
|
||||
|
||||
// Stop the IPFS node if we're running tests.
|
||||
if (this.config.env === 'test') {
|
||||
console.log('Stopping IPFS for tests.')
|
||||
await this.ipfs.stop()
|
||||
}
|
||||
// Get the multiaddrs for the node.
|
||||
const multiaddrs = ipfs.libp2p.getMultiaddrs()
|
||||
multiaddrs.push(detectedMultiaddr)
|
||||
console.log('Multiaddrs: ', multiaddrs)
|
||||
|
||||
this.multiaddrs = multiaddrs
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
|
||||
this.ipfs = ipfs
|
||||
|
||||
return this.ipfs
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs.js/start()')
|
||||
|
||||
// If IPFS crashes because the /blocks directory is full, wipe the directory.
|
||||
// if (err.message.includes('No space left on device')) {
|
||||
// this.rmBlocksDir()
|
||||
// }
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getKeychain (datastore) {
|
||||
const keychainInit = {
|
||||
pass: await this.getSeed()
|
||||
}
|
||||
|
||||
const chain = this.keychain(keychainInit)({
|
||||
datastore,
|
||||
logger: defaultLogger()
|
||||
})
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// This function creates an IPFS node using Helia.
|
||||
// It returns the node as an object.
|
||||
async createNode () {
|
||||
try {
|
||||
// Create block and data stores.
|
||||
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
||||
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// const keychainInit = {
|
||||
// pass: await this.getSeed()
|
||||
// }
|
||||
|
||||
// Create an identity
|
||||
let peerId
|
||||
// console.log('this.keychain: ', this.keychain)
|
||||
// const chain = this.keychain(keychainInit)({
|
||||
// datastore,
|
||||
// logger: defaultLogger()
|
||||
// })
|
||||
const chain = await this.getKeychain(datastore)
|
||||
try {
|
||||
peerId = await chain.exportPeerId('myKey')
|
||||
} catch (err) {
|
||||
await chain.createKey('myKey', 'Ed25519', 4096)
|
||||
peerId = await chain.exportPeerId('myKey')
|
||||
}
|
||||
|
||||
// Configure services
|
||||
const services = {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true })
|
||||
}
|
||||
if (this.config.isCircuitRelay) {
|
||||
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
||||
services.relay = circuitRelayServer({ // makes the node function as a relay server
|
||||
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
|
||||
advertise: true,
|
||||
reservations: {
|
||||
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
|
||||
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
|
||||
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
|
||||
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
|
||||
defaultDataLimit: BigInt(2 << 7), // the default maximum number of bytes that can be transferred over a relayed connection
|
||||
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
|
||||
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
||||
}
|
||||
|
||||
// Configure transports
|
||||
let transports
|
||||
if (process.env.CONNECT_PREF === 'direct') {
|
||||
transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
webRTC()
|
||||
]
|
||||
} else {
|
||||
transports = [
|
||||
tcp(),
|
||||
webSockets(),
|
||||
circuitRelayTransport({
|
||||
discoverRelays: 3,
|
||||
reservationConcurrency: 3
|
||||
}),
|
||||
webRTC()
|
||||
]
|
||||
}
|
||||
|
||||
// libp2p is the networking layer that underpins Helia
|
||||
const libp2p = await this.createLibp2p({
|
||||
peerId,
|
||||
datastore,
|
||||
addresses: {
|
||||
listen: [
|
||||
'/ip4/127.0.0.1/tcp/0',
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`,
|
||||
'/webrtc'
|
||||
]
|
||||
},
|
||||
transports,
|
||||
connectionEncryption: [
|
||||
noise()
|
||||
],
|
||||
streamMuxers: [
|
||||
yamux()
|
||||
],
|
||||
services
|
||||
})
|
||||
|
||||
// create a Helia node
|
||||
const helia = await this.createHelia({
|
||||
blockstore,
|
||||
datastore,
|
||||
libp2p
|
||||
})
|
||||
|
||||
// Attach IPFS file system.
|
||||
const fs = unixfs(helia)
|
||||
helia.fs = fs
|
||||
|
||||
return helia
|
||||
} catch (err) {
|
||||
console.error('Error creating Helia node: ', err)
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await this.ipfs.stop()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Ensure that the directories exist to store blocks from the IPFS network.
|
||||
// This function is called at startup, before the IPFS node is started.
|
||||
ensureBlocksDir () {
|
||||
try {
|
||||
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function opens the seed used to generate the key for this IPFS peer.
|
||||
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
|
||||
async getSeed () {
|
||||
try {
|
||||
let seed
|
||||
|
||||
const filename = `${IPFS_DIR}/seed.json`
|
||||
|
||||
try {
|
||||
// Try to read the JSON file containing the seed.
|
||||
seed = await this.jsonFiles.readJSON(filename)
|
||||
} catch (err) {
|
||||
const seedNum = Math.floor(Math.random() * 1000000000000000000000)
|
||||
seed = seedNum.toString()
|
||||
|
||||
// Save the newly generated seed
|
||||
await this.jsonFiles.writeJSON(seed, filename)
|
||||
}
|
||||
|
||||
// console.log('getSeed() seed: ', seed)
|
||||
|
||||
return seed
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
export default IpfsAdapter
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
A utility file for reading and writing JSON files.
|
||||
*/
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
|
||||
import fs from 'fs'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -57,7 +57,8 @@ class JsonFiles {
|
||||
console.log(`err: ${JSON.stringify(err, null, 2)}`)
|
||||
}
|
||||
|
||||
throw err
|
||||
// throw err
|
||||
return reject(err)
|
||||
}
|
||||
|
||||
const obj = JSON.parse(data)
|
||||
@@ -72,4 +73,4 @@ class JsonFiles {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonFiles
|
||||
export default JsonFiles
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
// Load Mongoose models.
|
||||
const Users = require('./models/users')
|
||||
import Users from './models/users.js'
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
@@ -12,4 +12,4 @@ class LocalDB {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalDB
|
||||
export default LocalDB
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const mongoose = require('mongoose')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const config = require('../../../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
// Global npm libraries
|
||||
import mongoose from 'mongoose'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
const User = new mongoose.Schema({
|
||||
type: { type: String, default: 'user' },
|
||||
@@ -51,4 +54,4 @@ User.methods.generateToken = function generateToken () {
|
||||
}
|
||||
|
||||
// export default mongoose.model('user', User)
|
||||
module.exports = mongoose.model('user', User)
|
||||
export default mongoose.model('user', User)
|
||||
|
||||
+12
-5
@@ -1,9 +1,14 @@
|
||||
const lineReader = require('line-reader')
|
||||
const fs = require('fs')
|
||||
import lineReader from 'line-reader'
|
||||
import fs from 'fs'
|
||||
|
||||
const config = require('../../config')
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
let _this
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
class LogsApi {
|
||||
constructor () {
|
||||
@@ -135,8 +140,10 @@ class LogsApi {
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
throw new Error('filename must be a string')
|
||||
}
|
||||
// Throw an error if the file does not exist.
|
||||
|
||||
console.log('readLines() filename: ', filename)
|
||||
|
||||
// Throw an error if the file does not exist.
|
||||
if (!_this.fs.existsSync(filename)) {
|
||||
throw new Error('file does not exist')
|
||||
}
|
||||
@@ -167,4 +174,4 @@ class LogsApi {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsApi
|
||||
export default LogsApi
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
const nodemailer = require('nodemailer')
|
||||
import nodemailer from 'nodemailer'
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const { wlogger } = require('./wlogger')
|
||||
import config from '../../config/index.js'
|
||||
import wlogger from './wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -150,4 +149,4 @@ class NodeMailer {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NodeMailer
|
||||
export default NodeMailer
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
koa-passport is an authorization library used for different authentication schemes.
|
||||
*/
|
||||
|
||||
const passport = require('koa-passport')
|
||||
import passport from 'koa-passport'
|
||||
|
||||
let _this
|
||||
class Passport {
|
||||
@@ -32,4 +32,4 @@ class Passport {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Passport
|
||||
export default Passport
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
Wallet adapter. This library instantiates minimal-slp-wallet based on different
|
||||
configuration settings. That single instance of minimal-slp-wallet is then
|
||||
easily passed around the rest of this application.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import BchWallet from 'minimal-slp-wallet'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
import JsonFiles from './json-files.js'
|
||||
|
||||
class Wallet {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
this.BchWallet = BchWallet
|
||||
this.jsonFiles = new JsonFiles()
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
|
||||
}
|
||||
|
||||
// This is used for initializing the wallet, without waiting to update the wallet
|
||||
// UTXOs from the blockchain.
|
||||
// This is useful when the wallet is simply needed to make calls to the blockchain,
|
||||
// and there is no need to hydrate it with UTXO data.
|
||||
async instanceWalletWithoutInitialization (walletData = {}, advancedConfig = {}) {
|
||||
try {
|
||||
// Use the apiToken from the config settings, if one is not passed-in at
|
||||
// run-time.
|
||||
if (!advancedConfig.apiToken) {
|
||||
advancedConfig.apiToken = this.config.apiToken
|
||||
}
|
||||
|
||||
// If an authentication password is passed in the config, add it.
|
||||
if (this.config.authPass) {
|
||||
advancedConfig.authPass = this.config.authPass
|
||||
}
|
||||
|
||||
// Detect and configure different blockchain infrastructure settings.
|
||||
if (this.config.walletInterface === 'web2') {
|
||||
advancedConfig.interface = 'rest-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
} else {
|
||||
// By default use the web3 Cash Stack (https://cashstack.info)
|
||||
advancedConfig.interface = 'consumer-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
}
|
||||
|
||||
// console.log('advancedConfig setting when creating wallet: ', advancedConfig)
|
||||
|
||||
// Instantiate minimal-slp-wallet.
|
||||
if (walletData.mnemonic) {
|
||||
// Instance the wallet using the mnemonic passed in to this function.
|
||||
console.log('Mnemonic provided to wallet library from calling function.')
|
||||
this.bchWallet = await this._instanceWallet(walletData.mnemonic, advancedConfig)
|
||||
} else if (this.config.mnemonic) {
|
||||
// Otherwise use the mnemonic in the config setting or passed as an environment variable.
|
||||
console.log('Mnemonic retrieved from config file or environment variable.')
|
||||
this.bchWallet = await this._instanceWallet(this.config.mnemonic, advancedConfig)
|
||||
} else {
|
||||
// If no mnemonic is provided, then generate a new mnemonic to create the wallet.
|
||||
console.log('New mnemonic generated.')
|
||||
this.bchWallet = await this._instanceWallet(undefined, advancedConfig)
|
||||
}
|
||||
|
||||
// Wait for wallet to initialize.
|
||||
await this.bchWallet.walletInfoPromise
|
||||
console.log('BCH wallet initialized.')
|
||||
console.log(`Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
|
||||
console.log(`Wallet mnemonic: ${this.bchWallet.walletInfo.mnemonic}`)
|
||||
console.log(`Wallet backend: ${this.bchWallet.bchjs.restURL}`)
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
console.error('Error in instanceWalletWithoutInitialization()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used for easier mocking of unit tests.
|
||||
async _instanceWallet (mnemonic, config) {
|
||||
const wallet = new this.BchWallet(mnemonic, config)
|
||||
await wallet.walletInfoPromise
|
||||
|
||||
return wallet
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async openWallet (advancedConfig = {}) {
|
||||
try {
|
||||
let walletData
|
||||
|
||||
// Try to open the wallet.json file.
|
||||
try {
|
||||
walletData = await this.jsonFiles.readJSON(this.config.walletFile)
|
||||
walletData = walletData.wallet
|
||||
} catch (err) {
|
||||
// Create a new wallet file if one does not already exist.
|
||||
console.log('Wallet file not found. Creating new wallet.json file.')
|
||||
|
||||
// Create a new wallet.
|
||||
// No-Update flag creates wallet without making any network calls.
|
||||
const walletInstance = await this.instanceWalletWithoutInitialization({}, advancedConfig)
|
||||
|
||||
walletData = walletInstance.walletInfo
|
||||
|
||||
// Add the nextAddress property
|
||||
walletData.nextAddress = 1
|
||||
|
||||
// Write the wallet data to the JSON file.
|
||||
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
|
||||
}
|
||||
|
||||
// console.log('walletData: ', walletData)
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Create an instance of minimal-slp-wallet. Same as
|
||||
// instanceWalletWithoutInitialization(), but waits for the wallet to initialize
|
||||
// its UTXOs (wallet balance and tokens).
|
||||
async instanceWallet (walletData = {}, advancedConfig = {}) {
|
||||
try {
|
||||
// Instance the wallet without initialization.
|
||||
await this.instanceWalletWithoutInitialization(walletData, advancedConfig)
|
||||
|
||||
// Initialize the wallet
|
||||
await this.bchWallet.initialize()
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/instanceWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Increments the 'nextAddress' property in the wallet file. This property
|
||||
// indicates the HD index that should be used to generate a key pair for
|
||||
// storing funds for Offers.
|
||||
// This function opens the wallet file, increments the nextAddress property,
|
||||
// then saves the change to the wallet file.
|
||||
async incrementNextAddress () {
|
||||
try {
|
||||
const walletData = await this.openWallet()
|
||||
|
||||
await this.instanceWalletWithoutInitialization(walletData)
|
||||
|
||||
walletData.nextAddress++
|
||||
// console.log('walletData finish: ', walletData)
|
||||
|
||||
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
|
||||
|
||||
// Update the working instance of the wallet.
|
||||
this.bchWallet.walletInfo.nextAddress++
|
||||
// console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo)
|
||||
|
||||
return walletData.nextAddress
|
||||
} catch (err) {
|
||||
console.error('Error in incrementNextAddress()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This method returns an object that contains a private key WIF, public address,
|
||||
// and the index of the HD wallet that the key pair was generated from.
|
||||
// TODO: Allow input integer. If input is used, use that as the index. If no
|
||||
// input is provided, then call incrementNextAddress().
|
||||
async getKeyPair (hdIndex = 0) {
|
||||
try {
|
||||
if (!hdIndex) {
|
||||
// Increment the HD index and generate a new key pair.
|
||||
hdIndex = await this.incrementNextAddress()
|
||||
}
|
||||
|
||||
const mnemonic = this.bchWallet.walletInfo.mnemonic
|
||||
|
||||
// root seed buffer
|
||||
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
|
||||
const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
// HDNode of BIP44 account
|
||||
// const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'")
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode)
|
||||
console.log('Generating a new key pair for cashAddress: ', cashAddress)
|
||||
|
||||
const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
const outObj = {
|
||||
cashAddress,
|
||||
wif,
|
||||
hdIndex
|
||||
}
|
||||
return outObj
|
||||
} catch (err) {
|
||||
console.error('Error in getKeyPair()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize the wallet by consolidating the UTXOs.
|
||||
async optimize () {
|
||||
const UTXO_THREASHOLD = 7
|
||||
// Do a dry-run first to see if there are enough UTXOs worth consolidating.
|
||||
const dryRunOut = await this.bchWallet.optimize(true)
|
||||
if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) {
|
||||
// Consolidate BCH UTXOs if the count is above the threashold.
|
||||
const txids = await this.bchWallet.optimize()
|
||||
console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the balance of the wallet in sats and PSF tokens.
|
||||
// This function is called by the GET /entry/balance controller.
|
||||
async getBalance () {
|
||||
const balance = await this.bchWallet.getBalance()
|
||||
// console.log('balance: ', balance)
|
||||
const tokens = await this.bchWallet.listTokens()
|
||||
// console.log('tokens: ', tokens)
|
||||
// Find the array entry for the PSF token
|
||||
const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0')
|
||||
// console.log('psfTokens: ', psfTokens)
|
||||
let psfBalance = 0
|
||||
if (psfTokens) {
|
||||
psfBalance = psfTokens.qty
|
||||
}
|
||||
const outObj = {
|
||||
satBalance: balance,
|
||||
psfBalance,
|
||||
success: true
|
||||
}
|
||||
return outObj
|
||||
}
|
||||
}
|
||||
|
||||
export default Wallet
|
||||
+64
-46
@@ -6,57 +6,75 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const winston = require('winston')
|
||||
require('winston-daily-rotate-file')
|
||||
// Global npm libraries
|
||||
import winston from 'winston'
|
||||
import 'winston-daily-rotate-file'
|
||||
|
||||
const config = require('../../config')
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Configure daily-rotation transport.
|
||||
const transport = new winston.transports.DailyRotateFile({
|
||||
filename: `${__dirname.toString()}/../../logs/koa-${config.env}-%DATE%.log`,
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: false,
|
||||
maxSize: '1m', // 1 megabyte
|
||||
maxFiles: '5d', // 5 days
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
)
|
||||
})
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
transport.on('rotate', notifyRotation)
|
||||
class Wlogger {
|
||||
constructor (localConfig = {}) {
|
||||
this.config = config
|
||||
|
||||
function notifyRotation (oldFilename, newFilename) {
|
||||
wlogger.info('Rotating log files')
|
||||
}
|
||||
|
||||
// This controls what goes into the log FILES
|
||||
const wlogger = winston.createLogger({
|
||||
level: 'verbose',
|
||||
format: winston.format.json(),
|
||||
transports: [
|
||||
//
|
||||
// - Write to all logs with level `info` and below to `combined.log`
|
||||
// - Write all logs error (and below) to `error.log`.
|
||||
//
|
||||
// new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
||||
// new winston.transports.File({ filename: 'logs/combined.log' })
|
||||
transport
|
||||
]
|
||||
})
|
||||
|
||||
function outputToConsole () {
|
||||
wlogger.add(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.simple(),
|
||||
level: 'info'
|
||||
// Configure daily-rotation transport.
|
||||
this.transport = new winston.transports.DailyRotateFile({
|
||||
filename: `${__dirname.toString()}/../../logs/koa-${
|
||||
this.config.env
|
||||
}-%DATE%.log`,
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: false,
|
||||
maxSize: '1m', // 1 megabyte
|
||||
maxFiles: '5d', // 5 days
|
||||
format: winston.format.combine(
|
||||
winston.format.timestamp(),
|
||||
winston.format.json()
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
this.transport.on('rotate', this.notifyRotation)
|
||||
|
||||
// This controls what goes into the log FILES
|
||||
this.wlogger = winston.createLogger({
|
||||
level: 'verbose',
|
||||
format: winston.format.json(),
|
||||
transports: [
|
||||
//
|
||||
// - Write to all logs with level `info` and below to `combined.log`
|
||||
// - Write all logs error (and below) to `error.log`.
|
||||
//
|
||||
// new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
||||
// new winston.transports.File({ filename: 'logs/combined.log' })
|
||||
this.transport
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
notifyRotation (oldFilename, newFilename) {
|
||||
this.wlogger.info('Rotating log files')
|
||||
}
|
||||
|
||||
outputToConsole () {
|
||||
this.wlogger.add(
|
||||
new winston.transports.Console({
|
||||
format: winston.format.simple(),
|
||||
level: 'info'
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// This controls the logs to CONSOLE
|
||||
// if (config.env !== 'test') {
|
||||
// outputToConsole()
|
||||
// }
|
||||
const logger = new Wlogger()
|
||||
|
||||
module.exports = { wlogger, notifyRotation, outputToConsole }
|
||||
// Allow the logger to write to the console.
|
||||
logger.outputToConsole()
|
||||
|
||||
const wlogger = logger.wlogger
|
||||
|
||||
export { wlogger as default, Wlogger }
|
||||
// export default wlogger
|
||||
|
||||
+58
-45
@@ -6,58 +6,71 @@
|
||||
|
||||
// Public npm libraries.
|
||||
|
||||
// Load the Clean Architecture Adapters library
|
||||
const adapters = require('../adapters')
|
||||
// Local libraries
|
||||
import Adapters from '../adapters/index.js'
|
||||
import JSONRPC from './json-rpc/index.js'
|
||||
import UseCases from '../use-cases/index.js'
|
||||
import RESTControllers from './rest-api/index.js'
|
||||
import TimerControllers from './timer-controllers.js'
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Load the JSON RPC Controller.
|
||||
const JSONRPC = require('./json-rpc')
|
||||
class Controllers {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.adapters = new Adapters(localConfig)
|
||||
this.useCases = new UseCases({ adapters: this.adapters })
|
||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||
this.config = config
|
||||
}
|
||||
|
||||
// Load the Clean Architecture Use Case libraries.
|
||||
const UseCases = require('../use-cases')
|
||||
const useCases = new UseCases({ adapters })
|
||||
// Spin up any adapter libraries that have async startup needs.
|
||||
async initAdapters () {
|
||||
await this.adapters.start()
|
||||
}
|
||||
|
||||
// Load the REST API Controllers.
|
||||
const RESTControllers = require('./rest-api')
|
||||
// Run any Use Cases to startup the app.
|
||||
async initUseCases () {
|
||||
await this.useCases.start()
|
||||
}
|
||||
|
||||
// Top-level function for this library.
|
||||
// Start the various Controllers and attach them to the app.
|
||||
async function attachControllers (app) {
|
||||
try {
|
||||
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
||||
// to all the rest of the apps controllers and adapters.
|
||||
await adapters.fullStackJwt.getJWT()
|
||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||
adapters.bchjs = await adapters.fullStackJwt.instanceBchjs()
|
||||
// Top-level function for this library.
|
||||
// Start the various Controllers and attach them to the app.
|
||||
attachRESTControllers (app) {
|
||||
const restControllers = new RESTControllers({
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
})
|
||||
|
||||
// Attach the REST controllers to the Koa app.
|
||||
attachRESTControllers(app)
|
||||
// Attach the REST API Controllers associated with the boilerplate code to the Koa app.
|
||||
restControllers.attachRESTControllers(app)
|
||||
}
|
||||
|
||||
// Start IPFS.
|
||||
await adapters.ipfs.start({ bchjs: adapters.bchjs })
|
||||
// Attach any other controllers other than REST API controllers.
|
||||
async attachControllers (app) {
|
||||
// Wait for any startup processes to complete for the Adapters libraries.
|
||||
// await this.adapters.start()
|
||||
|
||||
attachRPCControllers()
|
||||
} catch (err) {
|
||||
console.error('Error in attachControllers()')
|
||||
throw err
|
||||
if (this.config.useIpfs) {
|
||||
// Attach JSON RPC controllers
|
||||
this.attachRPCControllers()
|
||||
}
|
||||
|
||||
// Attach and start the timer controllers
|
||||
this.timerControllers.startTimers()
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
attachRPCControllers () {
|
||||
const jsonRpcController = new JSONRPC({
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
})
|
||||
|
||||
// Attach the input of the JSON RPC router to the output of ipfs-coord.
|
||||
this.adapters.ipfs.ipfsCoordAdapter.attachRPCRouter(
|
||||
jsonRpcController.router
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function attachRESTControllers (app) {
|
||||
const rESTControllers = new RESTControllers({
|
||||
adapters,
|
||||
useCases
|
||||
})
|
||||
|
||||
// Attach the REST API Controllers associated with the boilerplate code to the Koa app.
|
||||
rESTControllers.attachRESTControllers(app)
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
function attachRPCControllers () {
|
||||
const jsonRpcController = new JSONRPC({ adapters, useCases })
|
||||
|
||||
// Attach the input of the JSON RPC router to the output of ipfs-coord.
|
||||
adapters.ipfs.ipfsCoordAdapter.attachRPCRouter(jsonRpcController.router)
|
||||
}
|
||||
|
||||
module.exports = { attachControllers }
|
||||
export default Controllers
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
/*
|
||||
This is the JSON RPC router for the users API
|
||||
|
||||
CT 3/5/22: This library can probably be deleted. Handling of the /about endpoint
|
||||
is now directly controlled by the ipfs-coord library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
const aboutStr = require('../../../../config/about')
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
class AboutRPC {
|
||||
constructor (localConfig) {
|
||||
@@ -33,13 +36,16 @@ class AboutRPC {
|
||||
// This is a bit different than other router libraries, because there is
|
||||
// only one response, which is a string about this node.
|
||||
async aboutRouter (rpcData) {
|
||||
console.log('debugging: aboutRouter from ipfs-service-provider triggered')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
message: aboutStr,
|
||||
// message: aboutStr,
|
||||
message: JSON.stringify(config.announceJsonLd),
|
||||
endpoint: 'about'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AboutRPC
|
||||
export default AboutRPC
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
// const AuthLib = require('../../lib/auth')
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class AuthRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -173,4 +174,4 @@ class AuthRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRPC
|
||||
export default AuthRPC
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
|
||||
// Local libraries
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const Validators = require('../validators')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import Validators from '../validators.js'
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class BCHRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -32,7 +32,6 @@ class BCHRPC {
|
||||
this.jsonrpc = jsonrpc
|
||||
this.validators = new Validators(localConfig)
|
||||
this.rateLimit = new RateLimit()
|
||||
// this.bchjs = new BCHJS()
|
||||
this.bchjs = this.adapters.bchjs
|
||||
}
|
||||
|
||||
@@ -49,9 +48,9 @@ class BCHRPC {
|
||||
|
||||
// Route the call based on the value of the method property.
|
||||
switch (endpoint) {
|
||||
case 'transactions':
|
||||
case 'txHistory':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.transactions(rpcData)
|
||||
return await this.txHistory(rpcData)
|
||||
|
||||
case 'balance':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
@@ -61,13 +60,33 @@ class BCHRPC {
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.utxos(rpcData)
|
||||
|
||||
case 'utxosBulk':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.utxosBulk(rpcData)
|
||||
|
||||
case 'broadcast':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.broadcast(rpcData)
|
||||
|
||||
case 'transaction':
|
||||
case 'txData':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.transaction(rpcData)
|
||||
return await this.txData(rpcData)
|
||||
|
||||
case 'pubkey':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.pubKey(rpcData)
|
||||
|
||||
case 'utxoIsValid':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.utxoIsValid(rpcData)
|
||||
|
||||
case 'getTokenData':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.getTokenData(rpcData)
|
||||
|
||||
case 'getTokenData2':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.getTokenData2(rpcData)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in BCHRPC/rpcRouter()')
|
||||
@@ -83,31 +102,70 @@ class BCHRPC {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch Transactions
|
||||
* @api {JSON} /bch TX History
|
||||
* @apiPermission public
|
||||
* @apiName Transactions
|
||||
* @apiName txHistory
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
|
||||
* It returns the transaction history for an address. This list of TXIDs is
|
||||
* sorted and paginated.
|
||||
*
|
||||
* There are three possible inputs:
|
||||
* - address: (required) the address to query for a transaction history
|
||||
* - sortOrder: (optional) will sort results in 'DECENDING' (default) or 'ASCENDING' order.
|
||||
* - page: (optional) will return a 'page' of 100 results. Default is 0
|
||||
*
|
||||
* Given the 'address' property this endpoint returns an object with the following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - method: "" - Method used in the petition
|
||||
* - receiver: "" - Receiver address
|
||||
* - value: {} - Final result value of the petition
|
||||
* - success : - Petition status
|
||||
* - txs : [] - Transactions of the provided address
|
||||
* - height : - Reference to the blockchain size
|
||||
* - tx_hash: "" - Hash of the transaction
|
||||
* - address : "" - Address asociated to the transactions
|
||||
* - status: - HTTP Status Code
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transactions", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"]}}
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "txHistory", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"], "sortOrder": "DESCENDING", "page": 0}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* "success":true,
|
||||
* "txs":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
|
||||
* }
|
||||
* ],
|
||||
* "status":200
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async transactions (rpcData) {
|
||||
async txHistory (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
// console.log('transactions rpcData: ', rpcData)
|
||||
|
||||
const addrs = rpcData.payload.params.addresses
|
||||
// const addr = rpcData.payload.params.address
|
||||
// const sortOrder = rpcData.payload.params.sortOrder
|
||||
|
||||
const data = await this.bchjs.Electrumx.transactions(addrs)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
const data = await this.useCases.bch.getTransactions(rpcData)
|
||||
|
||||
const retObj = data
|
||||
retObj.status = 200
|
||||
|
||||
return retObj
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
console.error('Error in JSON RPC BCH transactions()')
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
@@ -115,7 +173,7 @@ class BCHRPC {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'transactions'
|
||||
endpoint: 'txHistory'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,9 +185,48 @@ class BCHRPC {
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription This endpoint wraps the bchjs.Electrumx.balance([]) function.
|
||||
*
|
||||
* Given the 'addresses' property it returns an object
|
||||
* with the following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - method: "" - Method used in the petition
|
||||
* - receiver: "" - Receiver address
|
||||
* - value: {} - Final result value of the petition
|
||||
* - success : - Petition status
|
||||
* - balances : [] - Balance of the provided addresses
|
||||
* - balance : {} - Object with the balance types of an address
|
||||
* - confirmed : - Confirmed balance
|
||||
* - unconfirmed : - Unconfirmed balance
|
||||
* - address : "" - Address related to the balance
|
||||
* - status: - HTTP Status Code
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "balance", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"]}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success":true,
|
||||
* "balances":[
|
||||
* {
|
||||
* "balance":{
|
||||
* "confirmed":1000,
|
||||
* "unconfirmed":0
|
||||
* },
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* }
|
||||
* ],
|
||||
* "status":200
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async balance (rpcData) {
|
||||
try {
|
||||
@@ -145,7 +242,7 @@ class BCHRPC {
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
console.error('Error in JSON RPC BCH balance(): ', err)
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
@@ -167,15 +264,89 @@ class BCHRPC {
|
||||
* endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
*
|
||||
* Given an address this endpoint will return an object
|
||||
* with the following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - method: "" - Method used in the petition
|
||||
* - receiver: "" - Receiver address
|
||||
* - value: [] - Final result value of the petition
|
||||
* - address: "" - the address these UTXOs are associated with
|
||||
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
|
||||
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
|
||||
* not spent, to be safe.
|
||||
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
|
||||
* - type1: {}
|
||||
* - tokens: [] - SLP token Type 1 tokens.
|
||||
* - mintBatons: [] - SLP token Type 1 mint batons.
|
||||
* - nft: {}
|
||||
* - tokens: [] - NFT tokens
|
||||
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
|
||||
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxos", "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
*
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":[
|
||||
* {
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj",
|
||||
* "bchUtxos":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "tx_pos":0,
|
||||
* "value":1000,
|
||||
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "vout":0,
|
||||
* "isValid":false
|
||||
* }
|
||||
* ],
|
||||
* "nullUtxos":[
|
||||
*
|
||||
* ],
|
||||
* "slpUtxos":{
|
||||
* "type1":{
|
||||
* "mintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* },
|
||||
* "nft":{
|
||||
* "groupMintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "groupTokens":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async utxos (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const addr = rpcData.payload.params.address
|
||||
// console.log('addr: ', addr)
|
||||
|
||||
const data = await this.bchjs.Utxo.get(addr)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
@@ -185,7 +356,8 @@ class BCHRPC {
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
console.error('Error in JSON RPC BCH utxos()')
|
||||
console.error('Error in JSON RPC utxos(): ', err)
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
@@ -198,6 +370,145 @@ class BCHRPC {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch UTXOs Bulk
|
||||
* @apiPermission public
|
||||
* @apiName UTXOs Bulk
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription This endpoint provides the same data as as the utxos
|
||||
* endpoint, but it allows retrieval of UTXOs for up to 20 addresses at
|
||||
* a time. This reduces the number of JSON RPC calls, and is very handy
|
||||
* for HD wallets that need to quickly scan a lot of addresses.
|
||||
*
|
||||
* Given an array of addresses, this endpoint will return an object
|
||||
* with the following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the request with the RPC information
|
||||
* - method: "" - Method used in the request
|
||||
* - receiver: "" - Receiver address
|
||||
* - value: [] - Final result value of the request
|
||||
* - address: "" - the address these UTXOs are associated with
|
||||
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
|
||||
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
|
||||
* not spent, to be safe.
|
||||
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
|
||||
* - type1: {}
|
||||
* - tokens: [] - SLP token Type 1 tokens.
|
||||
* - mintBatons: [] - SLP token Type 1 mint batons.
|
||||
* - nft: {}
|
||||
* - tokens: [] - NFT tokens
|
||||
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
|
||||
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxos", "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
*
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":[
|
||||
* {
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj",
|
||||
* "bchUtxos":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "tx_pos":0,
|
||||
* "value":1000,
|
||||
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "vout":0,
|
||||
* "isValid":false
|
||||
* }
|
||||
* ],
|
||||
* "nullUtxos":[
|
||||
*
|
||||
* ],
|
||||
* "slpUtxos":{
|
||||
* "type1":{
|
||||
* "mintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* },
|
||||
* "nft":{
|
||||
* "groupMintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "groupTokens":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
*
|
||||
*/
|
||||
async utxosBulk (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const addrs = rpcData.payload.params.addresses
|
||||
// console.log('addrs: ', addrs)
|
||||
|
||||
// Input validation
|
||||
if (!Array.isArray(addrs)) {
|
||||
throw new Error('addresses parameter must be an array')
|
||||
}
|
||||
if (addrs.length > 20) {
|
||||
throw new Error('addresses parameter must not exceed 20 elements')
|
||||
}
|
||||
|
||||
const result = []
|
||||
for (let i = 0; i < addrs.length; i++) {
|
||||
const thisAddr = addrs[i]
|
||||
|
||||
const data = await this.bchjs.Utxo.get(thisAddr)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
result.push({
|
||||
address: thisAddr,
|
||||
utxos: data
|
||||
})
|
||||
}
|
||||
// console.log('result: ', result)
|
||||
|
||||
const retObj = {}
|
||||
retObj.status = 200
|
||||
retObj.success = true
|
||||
retObj.data = result
|
||||
|
||||
// console.log('retObj: ', retObj)
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH utxosBulk()')
|
||||
console.error('Error in JSON RPC utxosBulk(): ', err)
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'utxosBulk'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch Broadcast
|
||||
* @apiPermission public
|
||||
@@ -206,9 +517,27 @@ class BCHRPC {
|
||||
* @apiDescription Broadcast a transaction to the BCH network.
|
||||
* The transaction should be encoded as a hexidecimal string.
|
||||
*
|
||||
* This endpoint will return an object with the following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - method: "" - Method used in the petition
|
||||
* - receiver: "" - Receiver address
|
||||
* - value: "" - Final result value of the petition
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "broadcast", "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
*
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value": "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
|
||||
*
|
||||
*/
|
||||
async broadcast (rpcData) {
|
||||
try {
|
||||
@@ -219,61 +548,456 @@ class BCHRPC {
|
||||
const data = await this.bchjs.RawTransactions.sendRawTransaction(hex)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
const retObj = data
|
||||
retObj.status = 200
|
||||
const retObj = {
|
||||
success: true,
|
||||
status: 200,
|
||||
endpoint: 'broadcast',
|
||||
txid: data
|
||||
}
|
||||
// retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
console.error('Error in JSON RPC BCH broadcast()')
|
||||
console.log('error: ', err)
|
||||
// throw err
|
||||
|
||||
// bch-api will sometimes return an error message, not an error object.
|
||||
let message = ''
|
||||
if (!err.message && err.error) {
|
||||
// Full node error
|
||||
message = err.error
|
||||
} else {
|
||||
// Normal error
|
||||
message = err.message
|
||||
}
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
message,
|
||||
endpoint: 'broadcast'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch Transaction
|
||||
* @api {JSON} /bch Transaction Data
|
||||
* @apiPermission public
|
||||
* @apiName Transaction
|
||||
* @apiName txData
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Get data about a specific transaction.
|
||||
* @apiDescription Get expanded transaction data for an array of transaction
|
||||
* IDs. Each call is limited to 20 TXIDs or less.
|
||||
*
|
||||
* Given a transaction ID, the endpoint will return an object with the
|
||||
* following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - method: "" - Method used in the petition
|
||||
* - receiver: "" - Receiver address
|
||||
* - value: {} - Final result value of the petition
|
||||
* - txid: "" - Transaction ID
|
||||
* - hash: "" - Transaction hash
|
||||
* - version: - Version number
|
||||
* - size: - Transaction size
|
||||
* - locktime: -
|
||||
* - vin: [] - Transaction inputs
|
||||
* - vout: [] - Transaction outputs
|
||||
* - hex: "" - hexadecimal script
|
||||
* - blockhash: "" - Reference to the block register
|
||||
* - confirmations : "" - Transaction confirmations
|
||||
* - time: - Execution time
|
||||
* - blocktime: - Execution time
|
||||
* - isValidSLPTx: - Determines if the transaction was under SLP
|
||||
* - status: - HTTP Status Code
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transaction", "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"}}
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "txData", "txids": ["01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"]}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "txData": [{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "version":1,
|
||||
* "size":272,
|
||||
* "locktime":0,
|
||||
* "vin":[
|
||||
* {
|
||||
* "txid":"4deef6de4b973706cd6e8fc8105a41a84be349e4e9717225ee5e7c63538e95e8",
|
||||
* "vout":1,
|
||||
* "scriptSig":{
|
||||
* "asm":"3045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f2496[ALL|FORKID] 0467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3",
|
||||
* "hex":"483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3"
|
||||
* },
|
||||
* "sequence":4294967295,
|
||||
* "address":"bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq",
|
||||
* "value":0.00001824
|
||||
* }
|
||||
* ],
|
||||
* "vout":[
|
||||
* {
|
||||
* "value":0,
|
||||
* "n":0,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_RETURN -385055325 46226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "hex":"6a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "type":"nulldata"
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* "value":0.0000155,
|
||||
* "n":1,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_DUP OP_HASH160 066ebee590278f32aedc8a4865700c49e717f1d7 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
* "hex":"76a914066ebee590278f32aedc8a4865700c49e717f1d788ac",
|
||||
* "reqSigs":1,
|
||||
* "type":"pubkeyhash",
|
||||
* "addresses":[
|
||||
* "bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* "hex":"0100000001e8958e53637c5eee257271e9e449e34ba8415a10c88f6ecd0637974bdef6ee4d010000008b483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3ffffffff020000000000000000276a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a0e060000000000001976a914066ebee590278f32aedc8a4865700c49e717f1d788ac00000000",
|
||||
* "blockhash":"0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536",
|
||||
* "confirmations":98000,
|
||||
* "time":1568338904,
|
||||
* "blocktime":1568338904,
|
||||
* "isValidSLPTx":false,
|
||||
* "status":200
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
*/
|
||||
async transaction (rpcData) {
|
||||
async txData (rpcData) {
|
||||
try {
|
||||
// console.log('transaction rpcData: ', rpcData)
|
||||
|
||||
const txid = rpcData.payload.params.txid
|
||||
|
||||
const data = await this.bchjs.Transaction.get(txid.toString())
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
const retObj = data
|
||||
retObj.status = 200
|
||||
|
||||
const retObj = await this.useCases.bch.getTxData(rpcData)
|
||||
return retObj
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
// throw err
|
||||
console.error('Error in JSON RPC BCH txData()): ', err)
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'transaction'
|
||||
endpoint: 'txData'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch PubKey
|
||||
* @apiPermission public
|
||||
* @apiName PubKey
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Get the public key from an address.
|
||||
* Given an address the endpoint will return an object with the
|
||||
* following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - publickey: - Address public key
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "pubkey", "address": "bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk"}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "pubkey",
|
||||
* "pubkey": {
|
||||
* "success": true,
|
||||
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
async pubKey (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const address = rpcData.payload.params.address
|
||||
|
||||
const data = await this.bchjs.encryption.getPubKey(address)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
const retObj = {
|
||||
success: true,
|
||||
status: 200,
|
||||
endpoint: 'pubkey',
|
||||
pubkey: data
|
||||
}
|
||||
// retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH pubKey()')
|
||||
|
||||
// Handle different error formats.
|
||||
let error = err
|
||||
if (err.error && typeof err.error === 'string') {
|
||||
error = new Error(err.error)
|
||||
}
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: error.message,
|
||||
endpoint: 'pubkey'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch UtxoIsValid
|
||||
* @apiPermission public
|
||||
* @apiName UtxoIsValid
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Verify if UTXO is valid
|
||||
* Given a UTXO object (txid and vout), a full node is queried to verify that
|
||||
* the UTXO still exists in the mempool (true), or if it has been spent (false).
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - isValid: - Boolean: true or false
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxoIsValid", "utxo": {"tx_hash": "17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a", "tx_pos": 0}}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "utxoIsValid",
|
||||
* "isValid": true
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
async utxoIsValid (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const utxo = rpcData.payload.params.utxo
|
||||
// console.log('utxo: ', utxo)
|
||||
|
||||
const isValid = await this.bchjs.Utxo.isValid(utxo)
|
||||
|
||||
const retObj = {
|
||||
success: true,
|
||||
status: 200,
|
||||
endpoint: 'utxoIsValid',
|
||||
isValid
|
||||
}
|
||||
// retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH utxoIsValid()')
|
||||
|
||||
// Handle different error formats.
|
||||
let error = err
|
||||
if (err.error && typeof err.error === 'string') {
|
||||
error = new Error(err.error)
|
||||
}
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: error.message,
|
||||
endpoint: 'utxoIsValid'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch getTokenData
|
||||
* @apiPermission public
|
||||
* @apiName getTokenData
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Get data associated with a token
|
||||
* Given a token ID, this endpoint will retrieve the IPFS CIDs associated with
|
||||
* the tokens mutable and immutable data. This is extension of the PS002
|
||||
* specification for mutable data for tokens:
|
||||
* https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps002-slp-mutable-data.md
|
||||
*
|
||||
* If optional parameter 'withTxHistory' is true, data will include the
|
||||
* transaction history of the token.
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - tokenData: - Address public key
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "getTokenData", "tokenId": "c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d", "withTxHistory": true }}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "getTokenData",
|
||||
* "tokenData": {
|
||||
* "genesisData": {
|
||||
* "type": 1,
|
||||
* "ticker": 'MT2',
|
||||
* "name": 'Mutable Token',
|
||||
* "tokenId": 'c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d',
|
||||
* "documentUri": 'ipfs://bafybeie7oxpsr7evcnlptecxfdhaqlot4732phukd2ekgvuzoir2frost4',
|
||||
* "documentHash": '56ed1a5768076a318d02b5db64e125544dca57ab6b2cc7ca61dfa4645d244463',
|
||||
* "decimals": 0,
|
||||
* "mintBatonIsActive": true,
|
||||
* "tokensInCirculationBN": '1000',
|
||||
* "tokensInCirculationStr": '1000',
|
||||
* "blockCreated": 739412,
|
||||
* "totalBurned": '0',
|
||||
* "totalMinted": '1000'
|
||||
* },
|
||||
* "immutableData": 'ipfs://bafybeie7oxpsr7evcnlptecxfdhaqlot4732phukd2ekgvuzoir2frost4',
|
||||
* "mutableData": 'ipfs://bafybeigotuony53ley3n63hqwyxiqruqn5uamskmci6f645putnc46jju4'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async getTokenData (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const tokenId = rpcData.payload.params.tokenId
|
||||
// console.log('tokenId: ', tokenId)
|
||||
|
||||
const withTxHistory = !!rpcData.payload.params.withTxHistory
|
||||
|
||||
const tokenData = await this.bchjs.PsfSlpIndexer.getTokenData(tokenId, withTxHistory)
|
||||
|
||||
const retObj = {
|
||||
success: true,
|
||||
status: 200,
|
||||
endpoint: 'getTokenData',
|
||||
tokenData
|
||||
}
|
||||
// retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH utxoIsValid()')
|
||||
|
||||
// Handle different error formats.
|
||||
let error = err
|
||||
if (err.error && typeof err.error === 'string') {
|
||||
error = new Error(err.error)
|
||||
}
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: error.message,
|
||||
endpoint: 'getTokenData'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch getTokenData2
|
||||
* @apiPermission public
|
||||
* @apiName getTokenData2
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Get token icon and other media
|
||||
*
|
||||
* Get the icon for a token, given it's token ID.
|
||||
* This function expects a string input of a token ID property.
|
||||
* This function returns an object with a tokenIcon property that contains
|
||||
* the URL to the icon.
|
||||
*
|
||||
* The output object always have these properties:
|
||||
* - tokenIcon: A url to the token icon, if it exists.
|
||||
* - tokenStats: Data about the token from psf-slp-indexer.
|
||||
* - optimizedTokenIcon: An alternative, potentially more optimal, url to the token icon, if it exists.
|
||||
* - iconRepoCompatible: true if the token icon is available via token.bch.sx
|
||||
* - ps002Compatible: true if the token icon is compatible with PS007 specification.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "getTokenData2", "tokenId": "c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d", "withTxHistory": true }}
|
||||
*
|
||||
*/
|
||||
async getTokenData2 (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const tokenId = rpcData.payload.params.tokenId
|
||||
// console.log('tokenId: ', tokenId)
|
||||
|
||||
const updateCache = rpcData.payload.params.updateCache
|
||||
|
||||
const tokenData = await this.bchjs.PsfSlpIndexer.getTokenData2(tokenId, updateCache)
|
||||
|
||||
const retObj = {
|
||||
success: true,
|
||||
status: 200,
|
||||
endpoint: 'getTokenData2',
|
||||
tokenData
|
||||
}
|
||||
// retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH utxoIsValid()')
|
||||
|
||||
// Handle different error formats.
|
||||
let error = err
|
||||
if (err.error && typeof err.error === 'string') {
|
||||
error = new Error(err.error)
|
||||
}
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: error.message,
|
||||
endpoint: 'getTokenData2'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BCHRPC
|
||||
// module.exports = BCHRPC
|
||||
export default BCHRPC
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local support libraries
|
||||
const { wlogger } = require('../../adapters/wlogger')
|
||||
const UserController = require('./users')
|
||||
const AuthController = require('./auth')
|
||||
const AboutController = require('./about')
|
||||
const BCHController = require('./bch')
|
||||
import wlogger from '../../adapters/wlogger.js'
|
||||
import UserController from './users/index.js'
|
||||
import AuthController from './auth/index.js'
|
||||
import AboutController from './about/index.js'
|
||||
import BCHController from './bch/index.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -38,6 +38,11 @@ class JSONRPC {
|
||||
this.aboutController = new AboutController()
|
||||
this.bchController = new BCHController(localConfig)
|
||||
|
||||
// Cache to store IDs of processed JSON RPC commands. Used to prevent
|
||||
// duplicate processing.
|
||||
this.msgCache = []
|
||||
this.MSG_CACHE_SIZE = 30
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -46,23 +51,54 @@ class JSONRPC {
|
||||
async router (str, from) {
|
||||
try {
|
||||
// console.log('router str: ', str)
|
||||
// console.log('router from: ', from)
|
||||
console.log('JSON RPC router recieved data from: ', from)
|
||||
|
||||
// Exit quietly if 'from' is not specified.
|
||||
if (!from || typeof from !== 'string') {
|
||||
// console.warn(
|
||||
// 'Warning: Can not send JSON RPC response. Can not determine which peer this message came from.'
|
||||
// )
|
||||
return
|
||||
wlogger.info(
|
||||
'Warning: Can not send JSON RPC response. Can not determine which peer this message came from.'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
// Attempt to parse the incoming data as a JSON RPC string.
|
||||
const parsedData = _this.jsonrpc.parse(str)
|
||||
// console.log('parsedData: ', parsedData)
|
||||
// wlogger.debug(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
|
||||
|
||||
// Exit quietly if the incoming string is an invalid JSON RPC string.
|
||||
if (parsedData.type === 'invalid') {
|
||||
return
|
||||
wlogger.info('Rejecting invalid JSON RPC command.')
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for duplicate entries with same 'id' value.
|
||||
const alreadyProcessed = _this._checkIfAlreadyProcessed(parsedData)
|
||||
if (alreadyProcessed) {
|
||||
return false
|
||||
} else {
|
||||
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
|
||||
|
||||
// This node will regularly ping known circuit relays with an /about
|
||||
// JSON RPC call. These will be handled by ipfs-coord, but will percolate
|
||||
// up to this library. Ignore these messages.
|
||||
if (
|
||||
parsedData.type.includes('success') &&
|
||||
parsedData.payload.method === undefined
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Log the incoming JSON RPC command.
|
||||
wlogger.info(
|
||||
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}, method: ${parsedData.payload.method}`
|
||||
)
|
||||
|
||||
// Additional debugging for BCH.
|
||||
if (parsedData.payload.method === 'bch') {
|
||||
wlogger.info(
|
||||
`rpcData.payload: ${JSON.stringify(parsedData.payload, null, 2)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Added the property "from" to the parsedData object;
|
||||
@@ -100,8 +136,19 @@ class JSONRPC {
|
||||
|
||||
// Encrypt and publish the response to the originators private OrbitDB,
|
||||
// if ipfs-coord has been initialized and the peers ID is registered.
|
||||
if (_this.ipfsCoord.ipfs) {
|
||||
await _this.ipfsCoord.ipfs.orbitdb.sendToDb(from, retStr)
|
||||
|
||||
// console.log('responding to JSON RPC command')
|
||||
const thisNode = _this.ipfsCoord.thisNode
|
||||
// console.log('thisNode: ', thisNode)
|
||||
|
||||
try {
|
||||
await _this.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
from,
|
||||
retStr,
|
||||
thisNode
|
||||
)
|
||||
} catch (err) {
|
||||
console.log('sendPrivateMessage() err: ', err)
|
||||
}
|
||||
|
||||
// Return the response and originator. Useful for testing.
|
||||
@@ -113,6 +160,36 @@ class JSONRPC {
|
||||
}
|
||||
}
|
||||
|
||||
// Checks the ID of the JSON RPC call, to see if the message has already been
|
||||
// processed. Returns true if the ID exists in the cache of processed messages.
|
||||
// If the ID is new, the function adds it to the cache and return false.
|
||||
_checkIfAlreadyProcessed (data) {
|
||||
try {
|
||||
// console.log('data: ', data)
|
||||
|
||||
const id = data.payload.id
|
||||
|
||||
// Check if the hash is in the array of already processed message.
|
||||
const alreadyProcessed = this.msgCache.includes(id)
|
||||
|
||||
// Update the msgCache if this is a new message.
|
||||
if (!alreadyProcessed) {
|
||||
// Add the hash to the array.
|
||||
this.msgCache.push(id)
|
||||
|
||||
// If the array is at its max size, then remove the oldest element.
|
||||
if (this.msgCache.length > this.MSG_CACHE_SIZE) {
|
||||
this.msgCache.shift()
|
||||
}
|
||||
}
|
||||
|
||||
return alreadyProcessed
|
||||
} catch (err) {
|
||||
console.error('Error in _checkIfAlreadyProcessed: ', err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// The default JSON RPC response if the incoming command could not be routed.
|
||||
defaultResponse () {
|
||||
const errorObj = {
|
||||
@@ -125,4 +202,4 @@ class JSONRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JSONRPC
|
||||
export default JSONRPC
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/* eslint no-useless-catch: 0 */
|
||||
|
||||
// Local libraries
|
||||
const RateLimitLib = require('koa2-ratelimit').RateLimit
|
||||
import { RateLimit as RateLimitLib } from 'koa2-ratelimit'
|
||||
|
||||
class RateLimit {
|
||||
constructor (options) {
|
||||
@@ -80,4 +80,4 @@ class RateLimit {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RateLimit
|
||||
export default RateLimit
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const Validators = require('../validators')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import Validators from '../validators.js'
|
||||
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class UserRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -533,4 +534,4 @@ class UserRPC {
|
||||
// TODO create deleteUser()
|
||||
}
|
||||
|
||||
module.exports = UserRPC
|
||||
export default UserRPC
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
/* eslint no-useless-catch: 0 */
|
||||
|
||||
// Public npm libraries
|
||||
const jwt = require('jsonwebtoken')
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// const UserModel = require('../../adapters/localdb/models/users')
|
||||
|
||||
class Validators {
|
||||
@@ -93,4 +94,4 @@ class Validators {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Validators
|
||||
export default Validators
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Passport = require('../../../adapters/passport')
|
||||
import Passport from '../../../adapters/passport.js'
|
||||
const passport = new Passport()
|
||||
|
||||
let _this
|
||||
@@ -96,4 +96,4 @@ class AuthRESTController {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRESTController
|
||||
export default AuthRESTController
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const AuthRESTController = require('./controller')
|
||||
import AuthRESTController from './controller.js'
|
||||
|
||||
class AuthRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -48,4 +48,4 @@ class AuthRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRouter
|
||||
export default AuthRouter
|
||||
|
||||
@@ -29,20 +29,42 @@ class BCHRESTController {
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @api {post} /bch/transactions Transactions
|
||||
* @apiName Transactions
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
|
||||
*
|
||||
* Given the 'addresses' property returns an array of objects
|
||||
* with the following properties
|
||||
*
|
||||
* - success : - Petition status
|
||||
* - transactions : [] - Transaction of the provided address
|
||||
* - transactions: [] - Transaction details
|
||||
* - height : - Reference to the blockchain size
|
||||
* - tx_hash: "" - Transaction hash
|
||||
* - address : "" - Address associated to the transactions
|
||||
*
|
||||
* Note: For a single address pass the 'addresses' of string type
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/transactions
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
*
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* "success":true,
|
||||
* "transactions":[
|
||||
* {
|
||||
* "transactions":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
|
||||
* }
|
||||
* ],
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
@@ -54,6 +76,7 @@ class BCHRESTController {
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
|
||||
async transactions (ctx) {
|
||||
try {
|
||||
const addrs = ctx.request.body.addresses
|
||||
@@ -71,7 +94,19 @@ class BCHRESTController {
|
||||
* @api {post} /bch/balance Balance
|
||||
* @apiName Balance
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns the balance in BCH for an address.
|
||||
* @apiDescription Returns the balance of an address or an array of addresses.
|
||||
*
|
||||
* Given the 'addresses' property returns an array of objects
|
||||
* with the following properties
|
||||
*
|
||||
* - success : - Petition status
|
||||
* - balances : [] - Balance of the provided addresses
|
||||
* - balance : {} - Object with the balance types of an address
|
||||
* - confirmed : - Confirmed balance
|
||||
* - unconfirmed : - Unconfirmed Balance
|
||||
* - address : "" - Address related to the balance
|
||||
*
|
||||
* Note: For a single address pass the 'addresses' of string type
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/balance
|
||||
@@ -79,8 +114,16 @@ class BCHRESTController {
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* "success":true,
|
||||
* "balances":[
|
||||
* {
|
||||
* "balance":{
|
||||
* "confirmed":1000,
|
||||
* "unconfirmed":0
|
||||
* },
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
@@ -110,17 +153,70 @@ class BCHRESTController {
|
||||
* @apiName UTXOs
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
* with token information.
|
||||
*
|
||||
* Given an address, this endpoint will return an object with thre following
|
||||
* properties:
|
||||
*
|
||||
* - address: "" - the address these UTXOs are associated with
|
||||
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
|
||||
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
|
||||
* not spent, to be safe.
|
||||
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
|
||||
* - type1: {}
|
||||
* - tokens: [] - SLP token Type 1 tokens.
|
||||
* - mintBatons: [] - SLP token Type 1 mint batons.
|
||||
* - nft: {}
|
||||
* - tokens: [] - NFT tokens
|
||||
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
|
||||
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* }
|
||||
* [
|
||||
* {
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj",
|
||||
* "bchUtxos":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "tx_pos":0,
|
||||
* "value":1000,
|
||||
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "vout":0,
|
||||
* "isValid":false
|
||||
* }
|
||||
* ],
|
||||
* "nullUtxos":[
|
||||
*
|
||||
* ],
|
||||
* "slpUtxos":{
|
||||
* "type1":{
|
||||
* "mintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* },
|
||||
* "nft":{
|
||||
* "groupMintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "groupTokens":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
@@ -151,15 +247,14 @@ class BCHRESTController {
|
||||
* @apiDescription Broadcast a transaction to the BCH network.
|
||||
* The transaction should be encoded as a hexidecimal string.
|
||||
*
|
||||
* This endpoint will return a transaction id
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000" }' localhost:5001/bch/broadcast
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* }
|
||||
* "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
@@ -175,7 +270,7 @@ class BCHRESTController {
|
||||
const hex = ctx.request.body.hex
|
||||
|
||||
const txid = await _this.bchjs.RawTransactions.sendRawTransaction(hex)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
||||
|
||||
ctx.body = txid
|
||||
} catch (err) {
|
||||
@@ -188,16 +283,78 @@ class BCHRESTController {
|
||||
* @apiName Transaction
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get data about a specific transaction.
|
||||
* Given a transaction id this endpoint will return an object
|
||||
* with the following properties
|
||||
*
|
||||
* - txid: "" - Transaction ID
|
||||
* - hash: "" - Transaction hash
|
||||
* - version: - Version number
|
||||
* - size: - Transaction size
|
||||
* - locktime: -
|
||||
* - vin: [] - Transaction inputs
|
||||
* - vout: [] - Transaction outputs
|
||||
* - hex: "" - hexadecimal script
|
||||
* - blockhash: "" - Reference to the block register
|
||||
* - confirmations : "" - Transaction confirmations
|
||||
* - time: - Execution time
|
||||
* - blocktime: - Execution time
|
||||
* - isValidSLPTx: - Determines if the transaction was under SLP
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/transaction
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* }
|
||||
* {
|
||||
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "version":1,
|
||||
* "size":272,
|
||||
* "locktime":0,
|
||||
* "vin":[
|
||||
* {
|
||||
* "txid":"4deef6de4b973706cd6e8fc8105a41a84be349e4e9717225ee5e7c63538e95e8",
|
||||
* "vout":1,
|
||||
* "scriptSig":{
|
||||
* "asm":"3045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f2496[ALL|FORKID] 0467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3",
|
||||
* "hex":"483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3"
|
||||
* },
|
||||
* "sequence":4294967295,
|
||||
* "address":"bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq",
|
||||
* "value":0.00001824
|
||||
* }
|
||||
* ],
|
||||
* "vout":[
|
||||
* {
|
||||
* "value":0,
|
||||
* "n":0,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_RETURN -385055325 46226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "hex":"6a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "type":"nulldata"
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* "value":0.0000155,
|
||||
* "n":1,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_DUP OP_HASH160 066ebee590278f32aedc8a4865700c49e717f1d7 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
* "hex":"76a914066ebee590278f32aedc8a4865700c49e717f1d788ac",
|
||||
* "reqSigs":1,
|
||||
* "type":"pubkeyhash",
|
||||
* "addresses":[
|
||||
* "bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* "hex":"0100000001e8958e53637c5eee257271e9e449e34ba8415a10c88f6ecd0637974bdef6ee4d010000008b483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3ffffffff020000000000000000276a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a0e060000000000001976a914066ebee590278f32aedc8a4865700c49e717f1d788ac00000000",
|
||||
* "blockhash":"0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536",
|
||||
* "confirmations":97988,
|
||||
* "time":1568338904,
|
||||
* "blocktime":1568338904,
|
||||
* "isValidSLPTx":false
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
@@ -221,6 +378,57 @@ class BCHRESTController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/pubkey PubKey
|
||||
* @apiName PubKey
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get the public key from an address
|
||||
*
|
||||
* Given the 'address' param returns an object
|
||||
* with the following properties
|
||||
*
|
||||
* - success : - Request status
|
||||
* - publicKey : '' - Public key of the provided address
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/bch/pubkey/bitcoincash:qpnty9t0w93fez04h7yzevujpv8pun204qv6yfuahk
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "success":true,
|
||||
* "publicKey": '033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee'
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async pubKey (ctx) {
|
||||
try {
|
||||
const address = ctx.params.address
|
||||
console.log(`pubKey address: ${address}`)
|
||||
|
||||
const pubkey = await _this.bchjs.encryption.getPubKey(address)
|
||||
// console.log(`pubkey: ${JSON.stringify(pubkey, null, 2)}`)
|
||||
|
||||
ctx.body = pubkey
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
let error = err
|
||||
if (err.error && typeof err.error === 'string') {
|
||||
error = new Error(err.error)
|
||||
}
|
||||
_this.handleError(ctx, error)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
@@ -236,4 +444,6 @@ class BCHRESTController {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = BCHRESTController
|
||||
|
||||
// module.exports = BCHRESTController
|
||||
export default BCHRESTController
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
// const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const BCHRESTController = require('./controller')
|
||||
// const BCHRESTController = require('./controller')
|
||||
import BCHRESTController from './controller.js'
|
||||
|
||||
class BCHRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -50,6 +52,7 @@ class BCHRouter {
|
||||
this.router.post('/utxos', this.bchRESTController.utxos)
|
||||
this.router.post('/broadcast', this.bchRESTController.broadcast)
|
||||
this.router.post('/transaction', this.bchRESTController.transaction)
|
||||
this.router.get('/pubkey/:address', this.bchRESTController.pubKey)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -57,4 +60,5 @@ class BCHRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BCHRouter
|
||||
// module.exports = BCHRouter
|
||||
export default BCHRouter
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
import ContactLib from '../../../adapters/contact.js'
|
||||
|
||||
const ContactLib = require('../../../adapters/contact')
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
const contactLib = new ContactLib()
|
||||
|
||||
let _this
|
||||
@@ -74,4 +74,4 @@ class ContactController {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ContactController
|
||||
export default ContactController
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const ContactRESTControllerLib = require('./controller')
|
||||
import ContactRESTControllerLib from './controller.js'
|
||||
|
||||
class ContactRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -53,4 +53,4 @@ class ContactRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContactRouter
|
||||
export default ContactRouter
|
||||
|
||||
@@ -6,12 +6,14 @@
|
||||
|
||||
// Public npm libraries.
|
||||
|
||||
// Load the REST API Controllers.
|
||||
const AuthRESTController = require('./auth')
|
||||
const UserRouter = require('./users')
|
||||
const ContactRESTController = require('./contact')
|
||||
const LogsRESTController = require('./logs')
|
||||
const BCHRESTController = require('./bch')
|
||||
// Local libraries
|
||||
import AuthRESTController from './auth/index.js'
|
||||
import UserRouter from './users/index.js'
|
||||
import ContactRESTController from './contact/index.js'
|
||||
import LogsRESTController from './logs/index.js'
|
||||
import IpfsRESTController from './ipfs/index.js'
|
||||
import BCHRESTController from './bch/index.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -29,7 +31,8 @@ class RESTControllers {
|
||||
)
|
||||
}
|
||||
|
||||
// console.log('Controllers localConfig: ', localConfig)
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
}
|
||||
|
||||
attachRESTControllers (app) {
|
||||
@@ -38,13 +41,15 @@ class RESTControllers {
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /auth route
|
||||
const authRESTController = new AuthRESTController(dependencies)
|
||||
authRESTController.attach(app)
|
||||
if (!this.config.noMongo) {
|
||||
// Attach the REST API Controllers associated with the /auth route
|
||||
const authRESTController = new AuthRESTController(dependencies)
|
||||
authRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /user route
|
||||
const userRouter = new UserRouter(dependencies)
|
||||
userRouter.attach(app)
|
||||
// Attach the REST API Controllers associated with the /user route
|
||||
const userRouter = new UserRouter(dependencies)
|
||||
userRouter.attach(app)
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /contact route
|
||||
const contactRESTController = new ContactRESTController(dependencies)
|
||||
@@ -54,10 +59,14 @@ class RESTControllers {
|
||||
const logsRESTController = new LogsRESTController(dependencies)
|
||||
logsRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /ipfs route
|
||||
const ipfsRESTController = new IpfsRESTController(dependencies)
|
||||
ipfsRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /fulcrum route
|
||||
const bchRESTController = new BCHRESTController(dependencies)
|
||||
bchRESTController.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RESTControllers
|
||||
export default RESTControllers
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
REST API Controller library for the /ipfs route
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
|
||||
// Local libraries
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class IpfsRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.getStatus = this.getStatus.bind(this)
|
||||
this.getPeers = this.getPeers.bind(this)
|
||||
this.getRelays = this.getRelays.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
this.connect = this.connect.bind(this)
|
||||
this.getThisNode = this.getThisNode.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs Get status on IPFS infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetIpfsStatus
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
|
||||
*
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await this.adapters.ipfs.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Return information on IPFS peers this node is connected to.
|
||||
async getPeers (ctx) {
|
||||
try {
|
||||
const showAll = ctx.request.body.showAll
|
||||
|
||||
const peers = await this.adapters.ipfs.getPeers(showAll)
|
||||
|
||||
ctx.body = { peers }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getPeers(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
async getRelays (ctx) {
|
||||
try {
|
||||
const relays = await this.adapters.ipfs.getRelays()
|
||||
|
||||
ctx.body = { relays }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getRelays(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
async connect (ctx) {
|
||||
try {
|
||||
const multiaddr = ctx.request.body.multiaddr
|
||||
const getDetails = ctx.request.body.getDetails
|
||||
|
||||
// console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs)
|
||||
const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails })
|
||||
// console.log('result: ', result)
|
||||
|
||||
ctx.body = result
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/connect():', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
|
||||
* @apiPermission public
|
||||
* @apiName GetThisNode
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
|
||||
*
|
||||
*/
|
||||
async getThisNode (ctx) {
|
||||
try {
|
||||
const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
|
||||
ctx.body = { thisNode }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getThisNode(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = IpfsRESTControllerLib
|
||||
export default IpfsRESTControllerLib
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
REST API library for the /ipfs route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import IPFSRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
// let _this
|
||||
|
||||
class IpfsRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.ipfsRESTController = new IPFSRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/ipfs'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/', this.ipfsRESTController.getStatus)
|
||||
this.router.post('/peers', this.ipfsRESTController.getPeers)
|
||||
this.router.post('/relays', this.ipfsRESTController.getRelays)
|
||||
this.router.post('/connect', this.ipfsRESTController.connect)
|
||||
this.router.get('/node', this.ipfsRESTController.getThisNode)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = BchRouter
|
||||
export default IpfsRouter
|
||||
@@ -1,4 +1,4 @@
|
||||
const LogsApiLib = require('../../../adapters/logapi')
|
||||
import LogsApiLib from '../../../adapters/logapi.js'
|
||||
const logsApiLib = new LogsApiLib()
|
||||
let _this
|
||||
|
||||
@@ -62,4 +62,4 @@ class LogsApi {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsApi
|
||||
export default LogsApi
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const LogsRESTControllerLib = require('./controller')
|
||||
import LogsRESTControllerLib from './controller.js'
|
||||
|
||||
class LogsRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -52,4 +52,4 @@ class LogsRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsRouter
|
||||
export default LogsRouter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function errorMiddleware () {
|
||||
export default function errorMiddleware () {
|
||||
return async (ctx, next) => {
|
||||
try {
|
||||
await next()
|
||||
@@ -8,4 +8,4 @@ module.exports = function errorMiddleware () {
|
||||
ctx.app.emit('error', err, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
/*
|
||||
REST API validator middleware.
|
||||
|
||||
These are a series of functions that ensure the user making a REST API
|
||||
matches a user in the database (or not). In can do fine-grain user control
|
||||
such as telling the difference between an admin, a normal user, and an
|
||||
anonymous user.
|
||||
|
||||
This middleware is used to gatekeep access to different REST API resources.
|
||||
|
||||
CT 9/17/22:
|
||||
This library was lightly refactored to make it work with the new unit tests.
|
||||
This not and the commented code below can be deleted once it is verified that
|
||||
this refactor did not result in any breaking changes.
|
||||
*/
|
||||
|
||||
const User = require('../../../adapters/localdb/models/users')
|
||||
const config = require('../../../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import User from '../../../adapters/localdb/models/users.js'
|
||||
|
||||
import config from '../../../../config/index.js'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -20,12 +33,10 @@ class Validators {
|
||||
|
||||
async ensureUser (ctx, next) {
|
||||
try {
|
||||
// console.log(`getToken: ${typeof (getToken)}`)
|
||||
const token = _this.getToken(ctx)
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
@@ -34,20 +45,23 @@ class Validators {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// console.log('Err: Could not find user.')
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
ctx.throw(401)
|
||||
// console.log('Ensure user error: ', error)
|
||||
// console.log('ctx: ', ctx)
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +74,8 @@ class Validators {
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
@@ -70,22 +85,26 @@ class Validators {
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
|
||||
if (ctx.state.user.type !== 'admin') {
|
||||
ctx.throw(401, 'not admin')
|
||||
// ctx.throw(401, 'not admin')
|
||||
throw new Error('User is not an admin')
|
||||
}
|
||||
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
@@ -101,7 +120,8 @@ class Validators {
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
// The user ID targeted in this API call.
|
||||
@@ -114,14 +134,16 @@ class Validators {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
console.log(`Err: Token could not be decoded: ${err}`)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
// console.log('ctx.state.user: ', ctx.state.user)
|
||||
|
||||
@@ -135,7 +157,8 @@ class Validators {
|
||||
|
||||
// If they don't match, then the calling user better be an admin.
|
||||
if (ctx.state.user.type !== 'admin') {
|
||||
ctx.throw(401, 'not admin')
|
||||
// ctx.throw(401, 'not admin')
|
||||
throw new Error('User is not an admin')
|
||||
} else {
|
||||
wlogger.verbose("It's ok. The user is an admin.")
|
||||
}
|
||||
@@ -144,6 +167,8 @@ class Validators {
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
// console.log('Error in ensureTargetUserOrAdmin(): ', error)
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
@@ -166,4 +191,4 @@ class Validators {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Validators
|
||||
export default Validators
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
REST API Controller library for the /user route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -36,7 +36,7 @@ class UserRESTControllerLib {
|
||||
* @apiGroup REST Users
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5001/users
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5010/users
|
||||
*
|
||||
* @apiParam {Object} user User object (required)
|
||||
* @apiParam {String} user.email Email
|
||||
@@ -124,7 +124,7 @@ class UserRESTControllerLib {
|
||||
|
||||
ctx.body = { users }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in users/controller.js/getUsers(): '.err)
|
||||
wlogger.error('Error in users/controller.js/getUsers(): ', err)
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class UserRESTControllerLib {
|
||||
* @apiGroup REST Users
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5010/users/56bd1da600a526986cf65c80
|
||||
*
|
||||
* @apiSuccess {Object} users User object
|
||||
* @apiSuccess {ObjectId} users._id User id
|
||||
@@ -281,4 +281,4 @@ class UserRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRESTControllerLib
|
||||
export default UserRESTControllerLib
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const UserRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
import UserRESTControllerLib from './controller.js'
|
||||
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -85,4 +86,4 @@ class UserRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRouter
|
||||
export default UserRouter
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
This Controller library is concerned with timer-based functions that are
|
||||
kicked off periodicially.
|
||||
*/
|
||||
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class TimerControllers {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Timer Controller libraries.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Timer Controller libraries.'
|
||||
)
|
||||
}
|
||||
|
||||
this.debugLevel = localConfig.debugLevel
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.exampleTimerFunc = this.exampleTimerFunc.bind(this)
|
||||
|
||||
// this.startTimers()
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
startTimers () {
|
||||
// Any new timer control functions can be added here. They will be started
|
||||
// when the server starts.
|
||||
this.optimizeWalletHandle = setInterval(this.exampleTimerFunc, 60000 * 10)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
stopTimers () {
|
||||
clearInterval(this.optimizeWalletHandle)
|
||||
}
|
||||
|
||||
// Replace this example function with your own timer handler.
|
||||
exampleTimerFunc (negativeTest) {
|
||||
try {
|
||||
console.log('Example timer controller executed.')
|
||||
|
||||
if (negativeTest) throw new Error('test error')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in exampleTimerFunc(): ', err)
|
||||
|
||||
// Note: Do not throw an error. This is a top-level function.
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TimerControllers
|
||||
@@ -21,4 +21,4 @@ class User {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User
|
||||
export default User
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
Use Cases for interacting with the BCH blockchain.
|
||||
*/
|
||||
|
||||
class BCHUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating BCH Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.bchjs = this.adapters.bchjs
|
||||
// console.log('use-cases/bch/index.js restURL: ', this.bchjs.restURL)
|
||||
}
|
||||
|
||||
// Get transaction history for an address, sorted by block height.
|
||||
async getTransactions (rpcData) {
|
||||
try {
|
||||
// console.log(
|
||||
// `getTransactions rpcData: ${JSON.stringify(rpcData, null, 2)}`
|
||||
// )
|
||||
|
||||
// Get the list of addresses.
|
||||
const addr = rpcData.payload.params.address
|
||||
|
||||
// Default to descending sorting.
|
||||
let sortOrder = rpcData.payload.params.sortOrder
|
||||
if (!sortOrder) sortOrder = 'DESCENDING'
|
||||
// console.log('sortOrder: ', sortOrder)
|
||||
|
||||
// Default to page 1
|
||||
let page = rpcData.payload.params.page
|
||||
if (!page) page = 0
|
||||
// console.log('page: ', page)
|
||||
|
||||
// Get the transaction history for the list of addresses.
|
||||
const data = await this.bchjs.Electrumx.transactions([addr])
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
if (!data.success) {
|
||||
throw new Error('Could not query Fulcrum indexer.')
|
||||
}
|
||||
// console.log(`transactions: ${JSON.stringify(transactions, null, 2)}`);
|
||||
|
||||
// Sort the transactions.
|
||||
const txsArr = await this.bchjs.Electrumx.sortAllTxs(
|
||||
data.transactions[0].transactions,
|
||||
sortOrder
|
||||
)
|
||||
// console.log(`txsArr: ${JSON.stringify(txsArr, null, 2)}`)
|
||||
|
||||
// Paginate the results
|
||||
const pagedResults = this.bchjs.Util.chunk100(txsArr)
|
||||
// console.log(
|
||||
// `pagedResults[page]: ${JSON.stringify(pagedResults[page], null, 2)}`
|
||||
// )
|
||||
|
||||
const retObj = {
|
||||
address: addr,
|
||||
txs: pagedResults[page],
|
||||
status: 200,
|
||||
success: true
|
||||
}
|
||||
// console.log(`retObj: ${JSON.stringify(retObj, null, 2)}`)
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH transactions(): ', err)
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'txHistory'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get transaction data for an array of TXIDs.
|
||||
async getTxData (rpcData) {
|
||||
try {
|
||||
// console.log('transaction rpcData: ', rpcData)
|
||||
|
||||
const txids = rpcData.payload.params.txids
|
||||
// console.log(`txids: ${JSON.stringify(txids, null, 2)}`)
|
||||
|
||||
if (!Array.isArray(txids)) {
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: 'Input txids must be an array of transaction IDs.',
|
||||
endpoint: 'txData'
|
||||
}
|
||||
}
|
||||
|
||||
if (txids.length > 20) {
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: 'Array input must be 20 elements or less.',
|
||||
endpoint: 'txData'
|
||||
}
|
||||
}
|
||||
|
||||
const txData = []
|
||||
for (let i = 0; i < txids.length; i++) {
|
||||
const thisTxid = txids[i]
|
||||
|
||||
// console.log(`this.bchjs.restURL: ${this.bchjs.restURL}`)
|
||||
const data = await this.bchjs.Transaction.get(thisTxid.toString())
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
txData.push(data.txData)
|
||||
}
|
||||
|
||||
const retObj = {
|
||||
status: 200,
|
||||
txData
|
||||
}
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.error('Error in JSON RPC BCH getTxData(): ', err)
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'txData'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = BCHUseCases
|
||||
export default BCHUseCases
|
||||
+12
-2
@@ -4,7 +4,9 @@
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
const UserUseCases = require('./user')
|
||||
// Local libraries
|
||||
import UserUseCases from './user.js'
|
||||
import BCHUseCases from './bch/index.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -17,7 +19,15 @@ class UseCases {
|
||||
|
||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||
this.user = new UserUseCases(localConfig)
|
||||
this.bch = new BCHUseCases(localConfig)
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
async start () {
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UseCases
|
||||
export default UseCases
|
||||
|
||||
+7
-13
@@ -3,8 +3,9 @@
|
||||
functions are called by the /user REST API endpoints.
|
||||
*/
|
||||
|
||||
// const UserModel = require('../adapters/localdb/models/users')
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
import UserEntity from '../entities/user.js'
|
||||
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
class UserLib {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -17,6 +18,7 @@ class UserLib {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.UserEntity = new UserEntity()
|
||||
this.UserModel = this.adapters.localdb.Users
|
||||
}
|
||||
|
||||
@@ -24,17 +26,9 @@ class UserLib {
|
||||
async createUser (userObj) {
|
||||
try {
|
||||
// Input Validation
|
||||
if (!userObj.email || typeof userObj.email !== 'string') {
|
||||
throw new Error("Property 'email' must be a string!")
|
||||
}
|
||||
if (!userObj.password || typeof userObj.password !== 'string') {
|
||||
throw new Error("Property 'password' must be a string!")
|
||||
}
|
||||
if (!userObj.name || typeof userObj.name !== 'string') {
|
||||
throw new Error("Property 'name' must be a string!")
|
||||
}
|
||||
|
||||
const user = new this.UserModel(userObj)
|
||||
const userEntity = this.UserEntity.validate(userObj)
|
||||
const user = new this.UserModel(userEntity)
|
||||
|
||||
// Enforce default value of 'user'
|
||||
user.type = 'user'
|
||||
@@ -188,4 +182,4 @@ class UserLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserLib
|
||||
export default UserLib
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/key/swarm/psk/1.0.0/
|
||||
/base16/
|
||||
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
|
||||
@@ -5,14 +5,17 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const axios = require('axios').default
|
||||
import { assert } from 'chai'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
// const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const config = require('../../../config')
|
||||
const app = require('../../../bin/server')
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
const AdminLib = require('../../../src/adapters/admin')
|
||||
import config from '../../../config/index.js'
|
||||
import Server from '../../../bin/server.js'
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import AdminLib from '../../../src/adapters/admin.js'
|
||||
const adminLib = new AdminLib()
|
||||
|
||||
// const request = supertest.agent(app.listen())
|
||||
@@ -20,14 +23,17 @@ const context = {}
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
try {
|
||||
process.env.E2ETEST = 'true'
|
||||
if (!config.noMongo) {
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
const app = new Server()
|
||||
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
|
||||
@@ -44,82 +50,80 @@ describe('Auth', () => {
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
} catch (err) {
|
||||
console.log('err in auth beforeAll: ', err)
|
||||
}
|
||||
})
|
||||
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw 401 if email is wrong format', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'wrongEmail',
|
||||
password: 'wrongpassword'
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
await axios(options)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
it('should throw 401 if email is wrong format', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'wrongEmail',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'test@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
await axios(options)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'test@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
const config = require('../../../config')
|
||||
const axios = require('axios').default
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import config from '../../../config/index.js'
|
||||
import axios from 'axios'
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
|
||||
import ContactController from '../../../src/controllers/rest-api/contact/controller.js'
|
||||
|
||||
// Mock data
|
||||
// const mockData = require('./mocks/contact-mocks')
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
const ContactController = require('../../../src/controllers/rest-api/contact/controller')
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
const config = require('../../../config')
|
||||
const assert = require('chai').assert
|
||||
import config from '../../../config/index.js'
|
||||
import { assert } from 'chai'
|
||||
import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const axios = require('axios').default
|
||||
const sinon = require('sinon')
|
||||
|
||||
const util = require('util')
|
||||
import LogsController from '../../../src/controllers/rest-api/logs/controller.js'
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const LogsController = require('../../../src/controllers/rest-api/logs/controller')
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
describe('LogsApi', () => {
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
const assert = require('chai').assert
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
|
||||
const Validators = require('../../../src/controllers/rest-api/middleware/validators')
|
||||
|
||||
const sinon = require('sinon')
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
|
||||
const util = require('util')
|
||||
/*
|
||||
import { assert } from 'chai';
|
||||
import testUtils from '../../utils/test-utils.js';
|
||||
import Validators from '../../../src/controllers/rest-api/middleware/validators.js';
|
||||
import sinon from 'sinon';
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js';
|
||||
import util from 'util';
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const context = {}
|
||||
@@ -303,3 +301,4 @@ describe('Validators', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
const assert = require('chai').assert
|
||||
import { assert } from 'chai'
|
||||
import Admin from '../../../src/adapters/admin.js'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
const Admin = require('../../../src/adapters/admin')
|
||||
|
||||
const sinon = require('sinon')
|
||||
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
let sandbox
|
||||
@@ -18,101 +17,103 @@ describe('Admin', () => {
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('loginAdmin()', () => {
|
||||
it('should logind admin', async () => {
|
||||
try {
|
||||
const error = new Error('test error')
|
||||
error.response = {
|
||||
status: 422
|
||||
if (!config.noMongo) {
|
||||
describe('loginAdmin()', () => {
|
||||
it('should logind admin', async () => {
|
||||
try {
|
||||
const error = new Error('test error')
|
||||
error.response = {
|
||||
status: 422
|
||||
}
|
||||
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
|
||||
|
||||
const result = await uut.loginAdmin()
|
||||
const user = result.data.user
|
||||
|
||||
assert.property(user, '_id')
|
||||
assert.property(user, 'email')
|
||||
assert.property(user, 'type')
|
||||
|
||||
assert.isString(user._id)
|
||||
assert.isString(user.email)
|
||||
assert.isString(user.type)
|
||||
|
||||
assert.equal(user.type, 'admin')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
// sandbox.stub(uut.axios, 'request').onFirstCall().throws(error)
|
||||
})
|
||||
|
||||
const result = await uut.loginAdmin()
|
||||
const user = result.data.user
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Returns an erroneous password to force
|
||||
// an auth error
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
|
||||
|
||||
assert.property(user, '_id')
|
||||
assert.property(user, 'email')
|
||||
assert.property(user, 'type')
|
||||
|
||||
assert.isString(user._id)
|
||||
assert.isString(user.email)
|
||||
assert.isString(user.type)
|
||||
|
||||
assert.equal(user.type, 'admin')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
// Returns an erroneous password to force
|
||||
// an auth error
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves({ password: 'wrong' })
|
||||
|
||||
await uut.loginAdmin()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
assert.include(err.response.data, 'Unauthorized')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('createSystemUser()', () => {
|
||||
it('should create admin', async () => {
|
||||
try {
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
assert.property(result, 'password')
|
||||
assert.property(result, 'id')
|
||||
assert.property(result, 'token')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
await uut.loginAdmin()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 401)
|
||||
assert.include(err.response.data, 'Unauthorized')
|
||||
}
|
||||
const error2 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 500
|
||||
}
|
||||
// The loginAdmin() function in some use cases is recursive
|
||||
// after handling the 422 error, it gets called again
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.onFirstCall()
|
||||
.throws(error1)
|
||||
.onSecondCall()
|
||||
.throws(error2)
|
||||
|
||||
await uut.createSystemUser()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle errors when remove user', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
sandbox.stub(uut.axios, 'request').throws(error1)
|
||||
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
|
||||
describe('createSystemUser()', () => {
|
||||
it('should create admin', async () => {
|
||||
try {
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
await uut.createSystemUser()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
assert.property(result, 'email')
|
||||
assert.property(result, 'password')
|
||||
assert.property(result, 'id')
|
||||
assert.property(result, 'token')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
const error2 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 500
|
||||
}
|
||||
// The loginAdmin() function in some use cases is recursive
|
||||
// after handling the 422 error, it gets called again
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.onFirstCall()
|
||||
.throws(error1)
|
||||
.onSecondCall()
|
||||
.throws(error2)
|
||||
|
||||
await uut.createSystemUser()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle errors when remove user', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
sandbox.stub(uut.axios, 'request').throws(error1)
|
||||
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
|
||||
|
||||
await uut.createSystemUser()
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Consumer Simulation E2E Test
|
||||
|
||||
The files in this directory simulates a consumer of web services, using JSON RPC over IPFS. It starts a new IPFS node that connects to the ipfs-bch-wallet-service and exercises each JSON RPC endpoint.
|
||||
|
||||
## Instructions
|
||||
|
||||
Execute these steps in order to run the test:
|
||||
|
||||
1. Run your local copy of ipfs-bch-wallet-service service provider.
|
||||
2. Update the [test-data.json](./test-data.json) file with the IPFS ID of the ipfs-bch-wallet-service.
|
||||
3. In another terminal, run the client simulation with `node client.e2e.js`
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
An end-to-end (e2e) test simulating a client connecting over IPFS and
|
||||
that exercises each JSON RPC endpoint.
|
||||
*/
|
||||
|
||||
const TestUtils = require('./lib/test-utils')
|
||||
const testUtils = new TestUtils()
|
||||
|
||||
const testData = require('./test-data.json')
|
||||
|
||||
async function startTest () {
|
||||
try {
|
||||
console.log('E2E TEST: Starting consumer E2E tests...')
|
||||
|
||||
await testUtils.startIpfs()
|
||||
|
||||
await testUtils.connectToUut(testData.uutAddr, testData.uutId)
|
||||
|
||||
await testUtils.testBalance(testData.uutId)
|
||||
|
||||
await testUtils.testTransaction(testData.uutId)
|
||||
|
||||
await testUtils.testTransactions(testData.uutId)
|
||||
|
||||
await testUtils.testUtxos(testData.uutId)
|
||||
|
||||
console.log('E2E TEST: Tests completed successfully!')
|
||||
process.exit()
|
||||
} catch (err) {
|
||||
console.error('Error in startTest(): ', err)
|
||||
}
|
||||
}
|
||||
startTest()
|
||||
@@ -0,0 +1,303 @@
|
||||
/*
|
||||
A class library of test utility functions.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const IpfsCoord = require('ipfs-coord')
|
||||
const IPFS = require('ipfs-http-client')
|
||||
const EventEmitter = require('events')
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
const http = require('http')
|
||||
|
||||
let _this
|
||||
|
||||
class TestUtils {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies.
|
||||
this.bchjs = new BCHJS()
|
||||
this.eventEmitter = new EventEmitter()
|
||||
this.uid = uid
|
||||
this.jsonrpc = jsonrpc
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
// This handler function recieves data from other ipfs-coord peers, like
|
||||
// the ipfs-bch-wallet-service that we're testing.
|
||||
// It emits the data as an 'rpcData' event. The tests will listen for this
|
||||
// event.
|
||||
rpcHandler (inData) {
|
||||
try {
|
||||
// console.log('Data recieved by rpcHandler: ', inData)
|
||||
|
||||
const jsonData = JSON.parse(inData)
|
||||
|
||||
_this.eventEmitter.emit('rpcData', jsonData)
|
||||
} catch (err) {
|
||||
console.error('Error in rpcHandler: ', err)
|
||||
// Do not throw an error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
// Send the RPC command to the service, wait a period of time for a response.
|
||||
// Timeout if a response is not recieved.
|
||||
async sendRPC (ipfsId, cmdStr) {
|
||||
try {
|
||||
// Send the RPC command to the server/service.
|
||||
// await this.ipfsCoord.ipfs.orbitdb.sendToDb(ipfsId, cmdStr)
|
||||
await this.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
ipfsId,
|
||||
cmdStr,
|
||||
this.ipfsCoord.thisNode
|
||||
)
|
||||
|
||||
let retData
|
||||
|
||||
// This event is triggered when the response comes back.
|
||||
this.eventEmitter.on('rpcData', (inData) => {
|
||||
retData = inData
|
||||
})
|
||||
|
||||
// Used for calculating the timeout.
|
||||
const start = new Date()
|
||||
let now = start
|
||||
let timeDiff = 0
|
||||
|
||||
// Wait for the response from the server. Exit once the response is
|
||||
// recieved, or a timeout occurs.
|
||||
do {
|
||||
await this.bchjs.Util.sleep(1000)
|
||||
|
||||
now = new Date()
|
||||
|
||||
timeDiff = now.getTime() - start.getTime()
|
||||
// console.log('timeDiff: ', timeDiff)
|
||||
} while (
|
||||
// Exit once the RPC data comes back, or if a period of time passes.
|
||||
!retData || // eslint-disable-line no-unmodified-loop-condition
|
||||
timeDiff > 10000
|
||||
)
|
||||
|
||||
return retData
|
||||
} catch (err) {
|
||||
console.error('Error in sendRPC')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async startIpfs () {
|
||||
try {
|
||||
const ipfsOptionsExternal = {
|
||||
host: 'localhost',
|
||||
port: 5001,
|
||||
agent: http.Agent({ keepAlive: true, maxSockets: 2000 })
|
||||
}
|
||||
|
||||
// Start the IPFS node.
|
||||
this.ipfs = await IPFS.create(ipfsOptionsExternal)
|
||||
await this.ipfs.config.profiles.apply('server')
|
||||
|
||||
// Start ipfs-coord.
|
||||
this.ipfsCoord = new IpfsCoord({
|
||||
ipfs: this.ipfs,
|
||||
type: 'node.js',
|
||||
// type: 'browser',
|
||||
bchjs: this.bchjs,
|
||||
privateLog: this.rpcHandler, // Default to console.log
|
||||
isCircuitRelay: false,
|
||||
apiInfo: 'none',
|
||||
announceJsonLd,
|
||||
nodeType: 'external'
|
||||
})
|
||||
|
||||
await this.ipfsCoord.start()
|
||||
|
||||
// Wait to let ipfs-coord connect to subnet peers.
|
||||
await this.bchjs.Util.sleep(30000)
|
||||
} catch (err) {
|
||||
console.error('Error in startIpfs().')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to the unit under test (uut)
|
||||
async connectToUut (addr, ipfsId) {
|
||||
try {
|
||||
try {
|
||||
await this.ipfs.swarm.connect(addr)
|
||||
console.log(`E2E TEST: Connected to IPFS node ${addr}`)
|
||||
} catch (err) {
|
||||
throw new Error('Could not connect to UUT IPFS node.')
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// 'ipfs-coord peer info: ',
|
||||
// this.ipfsCoord.ipfs.peers.state.peers[ipfsId]
|
||||
// )
|
||||
|
||||
const peers = this.ipfsCoord.thisNode.peerList
|
||||
// console.log('peers: ', peers)
|
||||
// console.log('Target peer in peerList? ', peers.includes(ipfsId))
|
||||
|
||||
// if (!this.ipfsCoord.ipfs.peers.state.peers[ipfsId]) {
|
||||
if (!peers.includes(ipfsId)) {
|
||||
throw new Error('Could not find UUT in ipfs-coord list of peers.')
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
console.error('Error in connectToUut()')
|
||||
console.log(
|
||||
'Is the service running? Did you update the test-data.json file?'
|
||||
)
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
|
||||
// Get the balance for a BCH address over JSON RPC.
|
||||
async testBalance (ipfsId) {
|
||||
try {
|
||||
// Generate the JSON RPC command
|
||||
const id = uid()
|
||||
const cmd = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'balance',
|
||||
addresses: ['bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj']
|
||||
})
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
|
||||
// console.log(`Publishing message to ${ipfsId}`)
|
||||
|
||||
console.log('E2E TEST: Sending Balance command...')
|
||||
const result = await this.sendRPC(ipfsId, cmdStr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
if (result.result.value.success && result.result.value.balances) {
|
||||
console.log('E2E TEST: balance test passed.')
|
||||
return true
|
||||
} else {
|
||||
console.log('E2E TEST: balance test failed.')
|
||||
this.failTest()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in testBalance()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the info a on transaction over JSON RPC.
|
||||
async testTransaction (ipfsId) {
|
||||
try {
|
||||
// Generate the JSON RPC command
|
||||
const id = uid()
|
||||
const cmd = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'transaction',
|
||||
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
})
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
|
||||
// console.log(`Publishing message to ${ipfsId}`)
|
||||
|
||||
console.log('E2E TEST: Sending Transaction command...')
|
||||
const result = await this.sendRPC(ipfsId, cmdStr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
if (result.result.value.status && result.result.value.confirmations) {
|
||||
console.log('E2E TEST: transaction test passed.')
|
||||
return true
|
||||
} else {
|
||||
console.log('E2E TEST: transaction test failed.')
|
||||
this.failTest()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in testTransaction()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the info a on transaction over JSON RPC.
|
||||
async testTransactions (ipfsId) {
|
||||
try {
|
||||
// Generate the JSON RPC command
|
||||
const id = uid()
|
||||
const cmd = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'transactions',
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
})
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
|
||||
// console.log(`Publishing message to ${ipfsId}`)
|
||||
|
||||
console.log('E2E TEST: Sending Transactions command...')
|
||||
const result = await this.sendRPC(ipfsId, cmdStr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
if (
|
||||
result.result.value.status &&
|
||||
Array.isArray(result.result.value.transactions)
|
||||
) {
|
||||
console.log('E2E TEST: transactions test passed.')
|
||||
return true
|
||||
} else {
|
||||
console.log('E2E TEST: transactions test failed.')
|
||||
this.failTest()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in testTransactions()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the utxos for a BCH address over JSON RPC.
|
||||
async testUtxos (ipfsId) {
|
||||
try {
|
||||
// Generate the JSON RPC command
|
||||
const id = uid()
|
||||
const cmd = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'utxos',
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
})
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
|
||||
// console.log(`Publishing message to ${ipfsId}`)
|
||||
|
||||
console.log('E2E TEST: Sending Utxos command...')
|
||||
const result = await this.sendRPC(ipfsId, cmdStr)
|
||||
// console.log('result', result.result.value)
|
||||
|
||||
const bchUtxos = result.result.value[0].bchUtxos
|
||||
const slpUtxos = result.result.value[0].slpUtxos
|
||||
|
||||
if (result.result.value && bchUtxos && slpUtxos) {
|
||||
console.log('E2E TEST: utxos test passed.')
|
||||
return true
|
||||
} else {
|
||||
console.log('E2E TEST: utxos test failed.')
|
||||
this.failTest()
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in testUtxos()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
failTest () {
|
||||
console.log('Exiting test program.')
|
||||
process.exit()
|
||||
}
|
||||
}
|
||||
|
||||
const announceJsonLd = {
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'e2e-test-client',
|
||||
description: 'A test client runing e2e tests on ipfs-bch-wallet-service.',
|
||||
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TestUtils
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"uutAddr": "/ip4/88.99.188.196/tcp/4001/p2p/12D3KooWT2tDg2pHwKz84Htzh2KsfSUZbZXmoAUFnaSwUVrDsag3/p2p-circuit/ipfs/12D3KooWNuZPDb15ENgk2CYF2dByq1oK6CQ5tUpRW9CpwwNynuJY",
|
||||
"uutId": "12D3KooWNuZPDb15ENgk2CYF2dByq1oK6CQ5tUpRW9CpwwNynuJY"
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
Integration tests for the endpoints in controllers/json-rpc/bch
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const assert = require('chai').assert
|
||||
|
||||
// Local libraries
|
||||
const BCHController = require('../../../../src/controllers/json-rpc/bch')
|
||||
const Adapters = require('../../../../src/adapters')
|
||||
const UseCases = require('../../../../src/use-cases')
|
||||
|
||||
describe('#BCH', () => {
|
||||
let uut
|
||||
|
||||
before(async () => {
|
||||
const adapters = new Adapters()
|
||||
const useCases = new UseCases({ adapters })
|
||||
// await adapters.start()
|
||||
|
||||
uut = new BCHController({ adapters, useCases })
|
||||
})
|
||||
|
||||
describe('#utxoIsValid', () => {
|
||||
it('should return true for valid UTXO with fullnode properties', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
utxo: {
|
||||
txid: 'a2059b1321e96a90a386894a68fa5829756118895b3cdb9a0393d94fd2ceed93',
|
||||
vout: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.utxoIsValid(rpcData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.isValid, true)
|
||||
})
|
||||
|
||||
it('should return true for valid UTXO with fulcrum properties', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
utxo: {
|
||||
tx_hash: 'a2059b1321e96a90a386894a68fa5829756118895b3cdb9a0393d94fd2ceed93',
|
||||
tx_pos: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.utxoIsValid(rpcData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.isValid, true)
|
||||
})
|
||||
|
||||
it('should return false for valid UTXO with fullnode properties', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
utxo: {
|
||||
txid: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
|
||||
vout: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.utxoIsValid(rpcData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.isValid, false)
|
||||
})
|
||||
|
||||
it('should return false for valid UTXO with fulcrum properties', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
utxo: {
|
||||
tx_hash: '17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a',
|
||||
tx_pos: 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.utxoIsValid(rpcData)
|
||||
// console.log('result: ', result
|
||||
|
||||
assert.equal(result.isValid, false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should return data for a token', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
tokenId: '5f31905f335fa932879c5aabfd1c14ac748f6696148bd300f845ea5016ad573e'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTokenData(rpcData)
|
||||
console.log('result: ', result)
|
||||
|
||||
// assert.equal(result.isValid, true)
|
||||
assert.property(result.tokenData, 'immutableData')
|
||||
assert.property(result.tokenData, 'mutableData')
|
||||
assert.equal(result.success, true)
|
||||
})
|
||||
|
||||
it('should return data for a token with tx history', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
tokenId: '43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d',
|
||||
withTxHistory: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTokenData(rpcData)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isArray(result.tokenData.genesisData.txs)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
Integration tests for the BCH Use Cases library
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const assert = require('chai').assert
|
||||
|
||||
// Local libraries
|
||||
const BCHUseCases = require('../../../../src/use-cases/bch')
|
||||
const Adapters = require('../../../../src/adapters')
|
||||
|
||||
describe('#BCH', () => {
|
||||
let uut
|
||||
|
||||
before(async () => {
|
||||
const adapters = new Adapters()
|
||||
// await adapters.start()
|
||||
|
||||
uut = new BCHUseCases({ adapters })
|
||||
})
|
||||
|
||||
describe('#getTransactions', () => {
|
||||
it('should get transactions for an address, with default descending sorting', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTransactions(rpcData)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.success, true)
|
||||
assert.equal(result.status, 200)
|
||||
assert.isArray(result.txs)
|
||||
|
||||
// Assert descending sort order.
|
||||
assert.isAbove(result.txs[0].height, result.txs[1].height)
|
||||
})
|
||||
|
||||
it('should get transactions for an address, with explicit descending sorting', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v',
|
||||
sortOrder: 'DESCENDING'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTransactions(rpcData)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.success, true)
|
||||
assert.equal(result.status, 200)
|
||||
assert.isArray(result.txs)
|
||||
|
||||
// Assert descending sort order.
|
||||
assert.isAbove(result.txs[0].height, result.txs[1].height)
|
||||
})
|
||||
|
||||
it('should sort in ascending order', async () => {
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
address: 'bitcoincash:qpdh9s677ya8tnx7zdhfrn8qfyvy22wj4qa7nwqa5v',
|
||||
sortOrder: 'ASCENDING'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTransactions(rpcData)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.success, true)
|
||||
assert.equal(result.status, 200)
|
||||
assert.isArray(result.txs)
|
||||
|
||||
// Assert descending sort order.
|
||||
assert.isAbove(result.txs[1].height, result.txs[0].height)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTxData', () => {
|
||||
it('should get data for a single non-SLP tx', async () => {
|
||||
const txids = [
|
||||
'11384d7e5a8af93806591debe5bbe2d7826aeea987b874dfbe372dfdcc0ee54f'
|
||||
]
|
||||
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
txids
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTxData(rpcData)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.status, 200)
|
||||
assert.isArray(result.txData)
|
||||
assert.equal(result.txData.length, 1)
|
||||
})
|
||||
|
||||
it('should get data on multiple non-SLP txs', async () => {
|
||||
const txids = [
|
||||
'11384d7e5a8af93806591debe5bbe2d7826aeea987b874dfbe372dfdcc0ee54f',
|
||||
'f9b54fd8d27b0237923437ed4df8d45557f52b3ad4d8d03d4104e925c84ab4ca'
|
||||
]
|
||||
|
||||
const rpcData = {
|
||||
payload: {
|
||||
params: {
|
||||
txids
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getTxData(rpcData)
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert.equal(result.status, 200)
|
||||
assert.isArray(result.txData)
|
||||
assert.equal(result.txData.length, 2)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Unit tests for the adapters index.js library
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
|
||||
import sinon from 'sinon'
|
||||
|
||||
// Local libraries
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
|
||||
describe('#adapters', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new Adapters()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should start the async adapters', async () => {
|
||||
// Mock dependencies
|
||||
uut.config.getJwtAtStartup = true
|
||||
uut.config.useIpfs = true
|
||||
uut.config.env = 'not-a-test'
|
||||
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
|
||||
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
|
||||
sandbox.stub(uut.ipfs, 'start').resolves()
|
||||
|
||||
const result = await uut.start()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.config.getJwtAtStartup = false
|
||||
uut.config.env = 'dev'
|
||||
sandbox.stub(uut.ipfs, 'start').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const ContactLib = require('../../../src/adapters/contact')
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import ContactLib from '../../../src/adapters/contact.js'
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
const FullStackJWT = require('../../../src/adapters/fullstack-jwt')
|
||||
import sinon from 'sinon'
|
||||
import FullStackJWT from '../../../src/adapters/fullstack-jwt.js'
|
||||
|
||||
describe('#FullStackJWT', () => {
|
||||
let sandbox
|
||||
@@ -18,8 +18,8 @@ describe('#FullStackJWT', () => {
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver',
|
||||
login: 'somelogin',
|
||||
password: 'somepassword'
|
||||
fullstackLogin: 'somelogin',
|
||||
fullstackPassword: 'somepassword'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
})
|
||||
@@ -81,7 +81,7 @@ describe('#FullStackJWT', () => {
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver',
|
||||
login: 'somelogin'
|
||||
fullstackLogin: 'somelogin'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
Unit tests for the IPFS Adapter.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
import sinon from 'sinon'
|
||||
import IPFSCoordAdapter from '../../../src/adapters/ipfs/ipfs-coord.js'
|
||||
import create from '../mocks/ipfs-mock.js'
|
||||
import IPFSCoordMock from '../mocks/ipfs-coord-mock.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
describe('#IPFS', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = IPFSMock.create()
|
||||
const ipfs = create()
|
||||
const bchjs = {}
|
||||
uut = new IPFSCoordAdapter({ ipfs, bchjs })
|
||||
|
||||
@@ -39,7 +40,7 @@ describe('#IPFS', () => {
|
||||
|
||||
it('should throw an error if bchjs instance is not included', () => {
|
||||
try {
|
||||
const ipfs = IPFSMock.create()
|
||||
const ipfs = create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
@@ -62,10 +63,49 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should get the public IP address if this node is a Circuit Relay', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
sandbox.stub(uut, 'publicIp').resolves('123')
|
||||
|
||||
// Force Circuit Relay
|
||||
uut.config.isCircuitRelay = true
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should exit quietly if this node is a Circuit Relay and there is an issue getting the IP address', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
sandbox.stub(uut, 'publicIp').rejects(new Error('test error'))
|
||||
|
||||
// Force Circuit Relay
|
||||
uut.config.isCircuitRelay = true
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
|
||||
uut.config.isProduction = true
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
assert.equal(result, true)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attachRPCRouter', () => {
|
||||
it('should attached a router output', async () => {
|
||||
it('should attach a router output', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
privateLog: {},
|
||||
@@ -73,6 +113,12 @@ describe('#IPFS', () => {
|
||||
orbitdb: {
|
||||
privateLog: {}
|
||||
}
|
||||
},
|
||||
adapters: {
|
||||
pubsub: {
|
||||
privateLog: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,16 +127,35 @@ describe('#IPFS', () => {
|
||||
uut.attachRPCRouter(router)
|
||||
})
|
||||
|
||||
it('should throw an error if ipfs-coord has not been instantiated', () => {
|
||||
it('should catch and throw an error', () => {
|
||||
try {
|
||||
// Force an error
|
||||
delete uut.ipfsCoord.adapters
|
||||
|
||||
const router = console.log
|
||||
|
||||
uut.attachRPCRouter(router)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#subscribeToChat', () => {
|
||||
it('should subscribe to the chat channel', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
adapters: {
|
||||
pubsub: {
|
||||
subscribeToPubsubChannel: async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.subscribeToChat()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,19 +2,22 @@
|
||||
Unit tests for the index.js file for the IPFS and ipfs-coord libraries.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
import sinon from 'sinon'
|
||||
import IPFSLib from '../../../src/adapters/ipfs/index.js'
|
||||
// import create from '../mocks/ipfs-mock.js'
|
||||
import IPFSCoordMock from '../mocks/ipfs-coord-mock.js'
|
||||
|
||||
describe('#IPFS-adapter-index', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let bchjs
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new IPFSLib()
|
||||
bchjs = new BCHJS()
|
||||
uut = new IPFSLib({ bchjs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
@@ -38,7 +41,9 @@ describe('#IPFS-adapter-index', () => {
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.ipfsAdapter = new IPFSMock()
|
||||
uut.ipfsAdapter = {
|
||||
start: async () => {}
|
||||
}
|
||||
uut.IpfsCoordAdapter = IPFSCoordMock
|
||||
|
||||
const result = await uut.start({ bchjs: {} })
|
||||
@@ -59,5 +64,174 @@ describe('#IPFS-adapter-index', () => {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle lock-file errors', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.ipfsAdapter, 'start')
|
||||
.rejects(new Error('Lock already being held'))
|
||||
|
||||
// Prevent process from exiting
|
||||
sandbox.stub(uut.process, 'exit').returns()
|
||||
|
||||
await uut.start({ bchjs })
|
||||
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Lock already being held')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should return an object with node metrics', () => {
|
||||
// Force uut to have the needed properties
|
||||
uut.ipfsCoordAdapter = {
|
||||
ipfsCoord: {
|
||||
thisNode: {
|
||||
ipfsId: 'fake-id',
|
||||
ipfsMultiaddrs: [],
|
||||
bchAddr: 'fake-bch-addr',
|
||||
slpAddr: 'fake-slp-addr',
|
||||
pubKey: 'fake-pubkey',
|
||||
peerList: [],
|
||||
relayData: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = uut.getStatus()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'ipfsId')
|
||||
assert.property(result, 'multiAddrs')
|
||||
assert.property(result, 'bchAddr')
|
||||
assert.property(result, 'slpAddr')
|
||||
assert.property(result, 'pubKey')
|
||||
assert.property(result, 'peers')
|
||||
assert.property(result, 'relays')
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', () => {
|
||||
try {
|
||||
uut.getStatus()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'Cannot read properties')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#_removeDuplicatePeers', () => {
|
||||
it('should return the same array when there are no duplicates', () => {
|
||||
const inArray = [
|
||||
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
|
||||
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
|
||||
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
|
||||
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
|
||||
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu'
|
||||
]
|
||||
|
||||
const result = uut._removeDuplicatePeers(inArray)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.length, inArray.length)
|
||||
})
|
||||
|
||||
it('should remove duplicates from an array', () => {
|
||||
const inArray = [
|
||||
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
|
||||
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA',
|
||||
'12D3KooWGsgHWyDLKuV4ZSfRJfsxQJj77rxx3i8Px3qXKHsLN7a2',
|
||||
'12D3KooWJyc54njjeZGbLew4D8u1ghrmZTTPyh3QpBF7dxtd3zGY',
|
||||
'12D3KooWDtj9cfj1SKuLbDNKvKRKSsGN8qivq9M8CYpLPDpcD5pu',
|
||||
'12D3KooWPpBXhhAeoCZCGuQ3KR4xwHzzvtP57f6zLmo8P7ZFBJFE',
|
||||
'12D3KooWRBhwfeP2Y9CDkFRBAZ1pmxUadH36TKuk3KtKm5XXP8mA'
|
||||
]
|
||||
|
||||
const result = uut._removeDuplicatePeers(inArray)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.length, inArray.length - 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPeers', () => {
|
||||
it('should return an array of current JSON data about each peer when showAll is true', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoordAdapter.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', protocol: 'test', version: '1' } } }, { from: 'b' }]
|
||||
},
|
||||
adapters: {
|
||||
ipfs: {
|
||||
getPeers: async () => ['a', 'b']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.getPeers(true)
|
||||
|
||||
assert.isArray(result)
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.ipfsCoordAdapter.ipfsCoord = {
|
||||
thisNode: {},
|
||||
adapters: {
|
||||
ipfs: {
|
||||
getPeers: async () => { throw new Error('test error') }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.getPeers()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRelays', () => {
|
||||
it('should return known relays', () => {
|
||||
uut.ipfsCoordAdapter.ipfsCoord = {
|
||||
thisNode: {
|
||||
peerData: [{ from: 'a', data: { jsonLd: { name: 'a', description: 'test' } } }, { from: 'b' }],
|
||||
relayData: [{ ipfsId: 'c' }, { ipfsId: 'a' }]
|
||||
}
|
||||
}
|
||||
|
||||
const result = uut.getRelays()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isArray(result.v2Relays)
|
||||
assert.equal(result.v2Relays.length, 2)
|
||||
assert.property(result.v2Relays[0], 'name')
|
||||
assert.property(result.v2Relays[0], 'description')
|
||||
assert.property(result.v2Relays[1], 'name')
|
||||
assert.property(result.v2Relays[1], 'description')
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.ipfsCoordAdapter.ipfsCoord = { thisNode: {} }
|
||||
|
||||
uut.getRelays()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,44 +2,79 @@
|
||||
Unit tests for the IPFS Adapter.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import cloneDeep from 'lodash.clonedeep'
|
||||
import { peerIdFromString } from '@libp2p/peer-id'
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
// Local libraries
|
||||
import IPFSLib from '../../../src/adapters/ipfs/ipfs.js'
|
||||
// import create from '../mocks/ipfs-mock.js'
|
||||
import config from '../../../config/index.js'
|
||||
import createHeliaLib from '../mocks/helia-mock.js'
|
||||
|
||||
// config.isProduction = true;
|
||||
describe('#IPFS-adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let ipfs
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new IPFSLib()
|
||||
|
||||
ipfs = cloneDeep(createHeliaLib)
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should instantiate IPFS Lib in dev mode.', async () => {
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
})
|
||||
|
||||
it('should instantiate dev IPFS Lib in production mode.', async () => {
|
||||
config.isProduction = true
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IPFS = IPFSMock
|
||||
sandbox.stub(uut, 'createNode').resolves(ipfs)
|
||||
sandbox.stub(uut, 'publicIp').resolves('192.168.2.4')
|
||||
sandbox.stub(uut, 'multiaddr').returns('/ip4/fake-multiaddr')
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
// Assert properties of the instance are set.
|
||||
assert.equal(uut.isReady, true)
|
||||
assert.property(uut, 'multiaddrs')
|
||||
assert.property(uut, 'id')
|
||||
|
||||
assert.property(result, 'config')
|
||||
// Output should be an instance of IPFS
|
||||
assert.property(result, 'libp2p')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.IPFS, 'create').rejects(new Error('test error'))
|
||||
sandbox.stub(uut, 'createNode').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
@@ -47,4 +82,161 @@ describe('#IPFS-adapter', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#stop', () => {
|
||||
it('should stop the IPFS node', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs = {
|
||||
stop: () => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.stop()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#ensureBlocksDir', () => {
|
||||
it('should create directory if it does not exist', () => {
|
||||
// Force desired code path
|
||||
sandbox.stub(uut.fs, 'existsSync').returns(false)
|
||||
sandbox.stub(uut.fs, 'mkdirSync').returns(true)
|
||||
|
||||
const result = uut.ensureBlocksDir()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should report and throw errors', () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.fs, 'existsSync').throws(new Error('test error'))
|
||||
|
||||
try {
|
||||
uut.ensureBlocksDir()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createNode', () => {
|
||||
it('should report and throw errors', async () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut, 'createLibp2p').rejects(new Error('test error'))
|
||||
|
||||
try {
|
||||
await uut.createNode()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create an IPFS node from Helia', async () => {
|
||||
uut.config.isCircuitRelay = false
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
|
||||
it('should create a Circuit Relay if configured', async () => {
|
||||
uut.config.isCircuitRelay = true
|
||||
const result = await uut.createNode()
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
|
||||
it('should create a new private key on first run', async () => {
|
||||
uut.config.isCircuitRelay = false
|
||||
|
||||
// Mock dependencies and force desired code path.
|
||||
let beenCalled = false
|
||||
sandbox.stub(uut, 'getKeychain').resolves({
|
||||
exportPeerId: async () => {
|
||||
if (!beenCalled) {
|
||||
beenCalled = true
|
||||
throw new Error('test error')
|
||||
}
|
||||
return peerIdFromString('12D3KooWSXF1PnEfiA8bCG8SJduCvzdwHtvhVPK4WC6zzDoto2XP')
|
||||
},
|
||||
createKey: async () => {}
|
||||
})
|
||||
sandbox.stub(uut, 'createLibp2p').resolves()
|
||||
sandbox.stub(uut, 'createHelia').resolves({})
|
||||
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'fs')
|
||||
})
|
||||
|
||||
it('should not use circuit relay when CONNECT_PREF is set to direct', async () => {
|
||||
process.env.CONNECT_PREF = 'direct'
|
||||
|
||||
uut.config.isCircuitRelay = false
|
||||
const result = await uut.createNode()
|
||||
// console.log('result: ', result)
|
||||
|
||||
delete process.env.CONNECT_PREF
|
||||
|
||||
// Assert the returned IPFS node has expected properties
|
||||
assert.property(result, 'libp2p')
|
||||
assert.property(result, 'blockstore')
|
||||
|
||||
// Stop the IPFS node
|
||||
await result.stop()
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getSeed', () => {
|
||||
it('should read the seed from the JSON file', async () => {
|
||||
// Mock dependencies and force desired code path
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').resolves('12345678')
|
||||
|
||||
const result = await uut.getSeed()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should generate a new seed if the JSON file is not found', async () => {
|
||||
// Mock dependencies and force desired code path
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').resolves()
|
||||
|
||||
const result = await uut.getSeed()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch, report, and throw errors', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.jsonFiles, 'readJSON').rejects(new Error('test error'))
|
||||
sandbox.stub(uut.jsonFiles, 'writeJSON').rejects(new Error('test error'))
|
||||
|
||||
await uut.getSeed()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
const assert = require('chai').assert
|
||||
const fs = require('fs')
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
import fs from 'fs'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const util = require('util')
|
||||
import JsonFiles from '../../../src/adapters/json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const JsonFiles = require('../../../src/adapters/json-files')
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const JSON_FILE = 'test-json-file.json'
|
||||
const JSON_PATH = `${__dirname.toString()}/${JSON_FILE}`
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
const assert = require('chai').assert
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const sinon = require('sinon')
|
||||
import LogsApiLib from '../../../src/adapters/logapi.js'
|
||||
import mockData from '../mocks/log-api-mock.js'
|
||||
|
||||
const util = require('util')
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LogsApiLib = require('../../../src/adapters/logapi')
|
||||
const mockData = require('../mocks/log-api-mock')
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const context = {}
|
||||
let sandbox
|
||||
@@ -32,19 +35,18 @@ describe('#LogsApiLib', () => {
|
||||
})
|
||||
|
||||
it('should return log', async () => {
|
||||
try {
|
||||
const pass = 'test'
|
||||
const result = await uut.getLogs(pass)
|
||||
// console.log('result', result)
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'generateFileName').returns(`${__dirname.toString()}/../mocks/adapters/fake-log`)
|
||||
|
||||
assert.isTrue(result.success)
|
||||
assert.isArray(result.data)
|
||||
assert.property(result.data[0], 'message')
|
||||
assert.property(result.data[0], 'level')
|
||||
assert.property(result.data[0], 'timestamp')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
const pass = 'test'
|
||||
const result = await uut.getLogs(pass)
|
||||
// console.log('result', result)
|
||||
|
||||
assert.isTrue(result.success)
|
||||
assert.isArray(result.data)
|
||||
assert.property(result.data[0], 'message')
|
||||
assert.property(result.data[0], 'level')
|
||||
assert.property(result.data[0], 'timestamp')
|
||||
})
|
||||
|
||||
it('should return false if files are not found!', async () => {
|
||||
@@ -201,31 +203,25 @@ describe('#LogsApiLib', () => {
|
||||
})
|
||||
|
||||
it('should ignore fileReader callback errors', async () => {
|
||||
try {
|
||||
// https://sinonjs.org/releases/latest/stubs/
|
||||
// About yields
|
||||
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
|
||||
// https://sinonjs.org/releases/latest/stubs/
|
||||
// About yields
|
||||
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
|
||||
|
||||
const fileName = context.fileName
|
||||
const result = await uut.readLines(fileName)
|
||||
assert.isArray(result)
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
|
||||
|
||||
const result = await uut.readLines(fileName)
|
||||
assert.isArray(result)
|
||||
})
|
||||
|
||||
it('should return data', async () => {
|
||||
try {
|
||||
const fileName = context.fileName
|
||||
const result = await uut.readLines(fileName)
|
||||
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[1], 'message')
|
||||
assert.property(result[1], 'level')
|
||||
assert.property(result[1], 'timestamp')
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
const result = await uut.readLines(fileName)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[1], 'message')
|
||||
assert.property(result[1], 'level')
|
||||
assert.property(result[1], 'timestamp')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
|
||||
const NodeMailer = require('../../../src/adapters/nodemailer')
|
||||
import sinon from 'sinon'
|
||||
import NodeMailer from '../../../src/adapters/nodemailer.js'
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const assert = require('chai').assert
|
||||
const PassportLib = require('../../../src/adapters/passport')
|
||||
|
||||
const sinon = require('sinon')
|
||||
import { assert } from 'chai'
|
||||
import PassportLib from '../../../src/adapters/passport.js'
|
||||
import sinon from 'sinon'
|
||||
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user