Compare commits
456 Commits
Author | SHA1 | Date |
---|---|---|
|
d875d928d7 | 1 day ago |
|
3f2b3786d7 | 1 day ago |
|
f92e1473ca | 2 days ago |
|
125a3b61ae | 4 days ago |
|
5a6b5f2cae | 6 days ago |
|
b6dbd23ae7 | 2 weeks ago |
|
f01815bd04 | 2 weeks ago |
|
22ab6d1154 | 2 weeks ago |
|
ea7f300360 | 7 months ago |
|
43a46b65e1 | 7 months ago |
|
475b47b7ea | 7 months ago |
|
d083be1196 | 9 months ago |
|
ec58a24ed4 | 9 months ago |
|
82d87b32c1 | 9 months ago |
|
13514c7a62 | 9 months ago |
|
e4a2ef9a21 | 9 months ago |
|
36c5895d4f | 9 months ago |
|
14fd866d1a | 9 months ago |
|
ed5a493654 | 9 months ago |
|
74af120c8f | 9 months ago |
|
d478356076 | 9 months ago |
|
53eb5f086b | 9 months ago |
|
b603045a26 | 9 months ago |
|
66bc43543d | 9 months ago |
|
5ed34f0aa5 | 9 months ago |
|
95c0b7feeb | 9 months ago |
|
1acbfab1de | 9 months ago |
|
0040d38b50 | 9 months ago |
|
52ad99941e | 9 months ago |
|
89d8a44164 | 9 months ago |
|
3b2f17873a | 9 months ago |
|
f8393d570e | 9 months ago |
|
6079faacce | 9 months ago |
|
aa9bf3549c | 9 months ago |
|
5523794f47 | 9 months ago |
|
656ab99f88 | 9 months ago |
|
15080d6749 | 9 months ago |
|
36e1d39170 | 9 months ago |
|
168aa3bc90 | 9 months ago |
|
281e51eb7a | 9 months ago |
|
9768963862 | 11 months ago |
|
d875779ba6 | 1 year ago |
|
9f0d2bfadf | 1 year ago |
|
895b1de27d | 1 year ago |
|
fb891b9390 | 1 year ago |
|
12f828e5c2 | 1 year ago |
|
5cb6e10bd2 | 1 year ago |
|
23ff7ac1ec | 1 year ago |
|
cea4c9dec4 | 1 year ago |
|
6a72950fbe | 1 year ago |
|
37606b5889 | 1 year ago |
|
1fcad6306f | 1 year ago |
|
d262c2d88f | 1 year ago |
|
e650f45b9f | 1 year ago |
|
e08f2ebc28 | 1 year ago |
|
89c2291cbd | 1 year ago |
|
beb6ac3b46 | 1 year ago |
|
0aa4ee74e6 | 1 year ago |
|
ed9634ef25 | 1 year ago |
|
40173091e9 | 1 year ago |
|
acd9325451 | 1 year ago |
|
bd86fe79bb | 1 year ago |
|
8bce41276b | 1 year ago |
|
7a18d934f2 | 1 year ago |
|
98cf3ae648 | 1 year ago |
|
7f1fac9a91 | 1 year ago |
|
2e4ed0c963 | 1 year ago |
|
500437ff4c | 1 year ago |
|
804ba91d7b | 1 year ago |
|
f45bbc4ad2 | 1 year ago |
|
e14e5e56c9 | 1 year ago |
|
0cb1e575fc | 1 year ago |
|
e46a5623ba | 1 year ago |
|
3863653a5e | 1 year ago |
|
575e0581f8 | 1 year ago |
|
efdaa13244 | 1 year ago |
|
a2d1833a36 | 1 year ago |
|
6c943e1e93 | 1 year ago |
|
859d882018 | 1 year ago |
|
2d3aa63ce6 | 1 year ago |
|
57a931c161 | 1 year ago |
|
ee9ec62253 | 1 year ago |
|
61d563e952 | 1 year ago |
|
fed5cd9d38 | 1 year ago |
|
17137a1a55 | 1 year ago |
|
1bd901feab | 1 year ago |
|
d847830836 | 1 year ago |
|
bd72be79bb | 1 year ago |
|
ea75a70734 | 1 year ago |
|
813eb218e3 | 1 year ago |
|
d79fb62b70 | 1 year ago |
|
56e5558bf7 | 1 year ago |
|
cc42ce627c | 1 year ago |
|
ffdcd2c78b | 1 year ago |
|
eb7216d068 | 1 year ago |
|
a90e35e631 | 1 year ago |
|
6bcffb6bdc | 1 year ago |
|
9663a0b75b | 1 year ago |
|
8e65dd2c9e | 1 year ago |
|
ceceab04af | 1 year ago |
|
c991464229 | 1 year ago |
|
01ee444941 | 1 year ago |
|
03dca70aa1 | 1 year ago |
|
d1de24310d | 1 year ago |
|
9a6281f234 | 1 year ago |
|
b603181ba2 | 1 year ago |
|
c21d1f5b59 | 2 years ago |
|
ada3c6caf3 | 2 years ago |
|
26ebb316be | 2 years ago |
|
0efaf136ff | 2 years ago |
|
d29a1fbdb8 | 2 years ago |
|
9e91f06530 | 2 years ago |
|
541b1cfa3c | 2 years ago |
|
5107c300eb | 2 years ago |
|
1e52024fab | 2 years ago |
|
2b32f96be3 | 2 years ago |
|
27d210c295 | 2 years ago |
|
ef502511af | 2 years ago |
|
54b1d5b8b9 | 2 years ago |
|
79f3344f7e | 2 years ago |
|
aad8547a8a | 2 years ago |
|
2887a5a80f | 2 years ago |
|
997e41a0c8 | 2 years ago |
|
a099af5bc4 | 2 years ago |
|
9db65d9786 | 2 years ago |
|
3eb784c479 | 2 years ago |
|
ff50809df8 | 2 years ago |
|
4b29474f3e | 2 years ago |
|
296d7f95e0 | 2 years ago |
|
1cdacb9535 | 2 years ago |
|
b1e3af15dd | 2 years ago |
|
a5079bcd61 | 2 years ago |
|
44bb84d6f8 | 2 years ago |
|
0e48676c5c | 2 years ago |
|
6804fc762d | 2 years ago |
|
670141a097 | 2 years ago |
|
d7e15cbfaf | 2 years ago |
|
8339de2a50 | 2 years ago |
|
7a5e78b8cd | 2 years ago |
|
251c450431 | 2 years ago |
|
90dd0ac1c7 | 2 years ago |
|
47f8d3d972 | 2 years ago |
|
006775a0ce | 2 years ago |
|
795a643f39 | 2 years ago |
|
a7dc8c541a | 2 years ago |
|
439fd68077 | 2 years ago |
|
72140a6d1e | 2 years ago |
|
9141658976 | 2 years ago |
|
00a231372a | 2 years ago |
|
8501afe9b5 | 2 years ago |
|
893fcd964b | 2 years ago |
|
ca28744c33 | 2 years ago |
|
6b61e0852f | 2 years ago |
|
7e40c4ea30 | 2 years ago |
|
be34d70670 | 2 years ago |
|
be0d1775d0 | 2 years ago |
|
86d4d15552 | 2 years ago |
|
b4e65e2098 | 2 years ago |
|
baa284b0b9 | 2 years ago |
|
9cc7dcd412 | 2 years ago |
|
a90d4c48b3 | 2 years ago |
|
411a602727 | 2 years ago |
|
abcf1de46a | 2 years ago |
|
04f909329e | 2 years ago |
|
57208d1029 | 2 years ago |
|
9e2c9e05c5 | 2 years ago |
|
921fb6850e | 2 years ago |
|
94a060d489 | 2 years ago |
|
dee4f75d3d | 2 years ago |
|
7c45b044d7 | 2 years ago |
|
7331ba1e50 | 2 years ago |
|
2c113030b4 | 2 years ago |
|
6ce2ae82c8 | 2 years ago |
|
b505931c82 | 2 years ago |
|
a0f037df4a | 2 years ago |
|
65f73057ca | 2 years ago |
|
fc459774ef | 2 years ago |
|
38a02839d6 | 2 years ago |
|
ac6b9d8002 | 2 years ago |
|
5fd902cab8 | 2 years ago |
|
c70911cdd3 | 2 years ago |
|
7f275be31e | 2 years ago |
|
c4c98fdf13 | 2 years ago |
|
0dc879038a | 2 years ago |
|
c0a1472eef | 2 years ago |
|
2807a408a8 | 2 years ago |
|
7fea447975 | 2 years ago |
|
38e2c84957 | 2 years ago |
|
0a22082987 | 2 years ago |
|
3b8ca9177a | 2 years ago |
|
211b6fdca4 | 2 years ago |
|
fa7a4a4c7b | 2 years ago |
|
b13e18a843 | 2 years ago |
|
44321d62e5 | 2 years ago |
|
40b1b80503 | 2 years ago |
|
7277bcb187 | 2 years ago |
|
96b4810020 | 2 years ago |
|
a7c65268a5 | 2 years ago |
|
f8166e67e8 | 2 years ago |
|
2b368c4e39 | 2 years ago |
|
4d163a8216 | 2 years ago |
|
2e54ba92ac | 2 years ago |
|
0ce5107e86 | 2 years ago |
|
dcf29c5e96 | 2 years ago |
|
7363309801 | 2 years ago |
|
44dbc2691d | 2 years ago |
|
6163f24268 | 2 years ago |
|
8b2aec049a | 2 years ago |
|
c2755acb33 | 2 years ago |
|
c4b2e21139 | 2 years ago |
|
76afb82dae | 2 years ago |
|
381be53e40 | 2 years ago |
|
198866791e | 2 years ago |
|
4a9689feca | 2 years ago |
|
20632b6ac3 | 2 years ago |
|
b1b1de5add | 2 years ago |
|
65e4c35226 | 2 years ago |
|
5c79a078fa | 2 years ago |
|
cc1cda3e56 | 2 years ago |
|
246b6ee5d8 | 2 years ago |
|
243b5fd293 | 2 years ago |
|
e62e7473f1 | 2 years ago |
|
72beafa26c | 2 years ago |
|
815446587e | 2 years ago |
|
64034aa017 | 2 years ago |
|
6a19acb351 | 2 years ago |
|
5abe432a2d | 2 years ago |
|
c930b8e121 | 2 years ago |
|
8986fb6771 | 2 years ago |
|
f5fe776cd0 | 2 years ago |
|
744775a197 | 2 years ago |
|
4f6cebc98d | 2 years ago |
|
c0c9d798a1 | 2 years ago |
|
eafc7835f8 | 2 years ago |
|
c487379898 | 2 years ago |
|
d98064ca2c | 2 years ago |
|
e7b03f4b2f | 2 years ago |
|
149561dedb | 2 years ago |
|
d350f1c0f9 | 2 years ago |
|
e74c5bc635 | 2 years ago |
|
772633b0c1 | 2 years ago |
|
24013f6f89 | 2 years ago |
|
7a0bd37d91 | 2 years ago |
|
cfd8f095a3 | 2 years ago |
|
c27ee4ed6a | 2 years ago |
|
d6185dd409 | 2 years ago |
|
a72f00a97b | 2 years ago |
|
8c4443a873 | 2 years ago |
|
6945d084b4 | 2 years ago |
|
dd180cba25 | 2 years ago |
|
daa4c735fb | 2 years ago |
|
f7ed36c5ac | 2 years ago |
|
02390fef1c | 2 years ago |
|
5853ffdfd1 | 2 years ago |
|
556af1e65d | 2 years ago |
|
1307fbc919 | 2 years ago |
|
a0c5da1d5b | 2 years ago |
|
1c365479ad | 2 years ago |
|
c3e217f708 | 2 years ago |
|
487165c197 | 2 years ago |
|
9b519e0267 | 2 years ago |
|
4d5bb8e3dc | 2 years ago |
|
65ee024e8d | 2 years ago |
|
444c657158 | 2 years ago |
|
2e7c9456ea | 2 years ago |
|
1211294ddb | 2 years ago |
|
c9d389cbd7 | 2 years ago |
|
43d5b52d96 | 2 years ago |
|
72fecba40a | 2 years ago |
|
2565d11f19 | 2 years ago |
|
2fe9c9f129 | 2 years ago |
|
88f481d120 | 2 years ago |
|
69a7e3010b | 2 years ago |
|
f63e21b9d9 | 2 years ago |
|
9f5ee4eb5b | 2 years ago |
|
4a470feae9 | 2 years ago |
|
9cff990441 | 2 years ago |
|
59bae5d33a | 2 years ago |
|
cf0cb040d7 | 2 years ago |
|
b4f1b47cf1 | 2 years ago |
|
96d1ebaa5a | 2 years ago |
|
25ba51c467 | 2 years ago |
|
20bb789c47 | 2 years ago |
|
86fd2a7b29 | 2 years ago |
|
6f13a03ec8 | 2 years ago |
|
966791fd80 | 2 years ago |
|
7f00e3de9c | 2 years ago |
|
9bfa04a712 | 2 years ago |
|
c5de2592e6 | 2 years ago |
|
f775f2814f | 2 years ago |
|
32adf76a5a | 2 years ago |
|
c16913402b | 2 years ago |
|
e2eeb8b919 | 2 years ago |
|
ba2a326b6c | 2 years ago |
|
1ec90e9949 | 2 years ago |
|
66a5dd7c24 | 2 years ago |
|
cd2324944c | 2 years ago |
|
89e7ebadaa | 2 years ago |
|
07f8d326d8 | 2 years ago |
|
75b49ef122 | 2 years ago |
|
76cd5f3359 | 2 years ago |
|
05aa2d883f | 2 years ago |
|
d0b651f8db | 2 years ago |
|
a1a507a44d | 2 years ago |
|
9165ae6a58 | 2 years ago |
|
da405a866b | 2 years ago |
|
ce3abf2fa7 | 2 years ago |
|
da96875acb | 2 years ago |
|
dd328cad1c | 2 years ago |
|
a4c29ac35b | 2 years ago |
|
c1f7622d45 | 2 years ago |
|
07f8b53546 | 2 years ago |
|
098788b01e | 2 years ago |
|
428e9a5fd9 | 2 years ago |
|
70abf7af25 | 2 years ago |
|
5b39581b82 | 2 years ago |
|
64173a8b2d | 2 years ago |
|
5f873ca50f | 2 years ago |
|
6728077914 | 2 years ago |
|
592074679b | 2 years ago |
|
b8477e3613 | 2 years ago |
|
7d8a8a15d6 | 2 years ago |
|
976b6f0e27 | 2 years ago |
|
3a9c436c32 | 2 years ago |
|
1580c296cd | 2 years ago |
|
c7fd9fad08 | 2 years ago |
|
9da3eed1b6 | 2 years ago |
|
3795851acf | 2 years ago |
|
346e414181 | 2 years ago |
|
47011011d9 | 2 years ago |
|
df5f5768c1 | 2 years ago |
|
2fa8760422 | 2 years ago |
|
1e6786a850 | 2 years ago |
|
81dfa053dc | 2 years ago |
|
519c18bafc | 2 years ago |
|
ee096d7e1b | 2 years ago |
|
9bb484d3fe | 2 years ago |
|
af547f922e | 2 years ago |
|
f3375191fc | 2 years ago |
|
987d5d833f | 2 years ago |
|
7059df2a5c | 2 years ago |
|
fa3e2f77d1 | 2 years ago |
|
f84719e2c3 | 2 years ago |
|
5d945a234c | 2 years ago |
|
44412d357e | 2 years ago |
|
ddb63c1bae | 2 years ago |
|
34eabeb622 | 2 years ago |
|
386ff5afa6 | 2 years ago |
|
87128c3a94 | 2 years ago |
|
3910284d58 | 2 years ago |
|
9c59954205 | 2 years ago |
|
89fcb11c39 | 3 years ago |
|
f5c1c03b22 | 3 years ago |
|
bea5980c6f | 3 years ago |
|
cf66708df9 | 3 years ago |
|
87f65803e0 | 3 years ago |
|
4ef50a1f85 | 3 years ago |
|
c1d7e22642 | 3 years ago |
|
c6e435a151 | 3 years ago |
|
e45d6d598a | 3 years ago |
|
e4d6256803 | 3 years ago |
|
a9074f2efc | 3 years ago |
|
4001c83d46 | 3 years ago |
|
492c0f45c9 | 3 years ago |
|
de0e8294b1 | 3 years ago |
|
f817ae39bc | 3 years ago |
|
d040ab891c | 3 years ago |
|
5a38585b71 | 4 years ago |
|
6e036d8382 | 4 years ago |
|
a1b177b11d | 4 years ago |
|
33bde83c19 | 4 years ago |
|
e017125a83 | 4 years ago |
|
98c5b8d250 | 4 years ago |
|
58dd1904ec | 4 years ago |
|
3c4ba33baf | 4 years ago |
|
cdf7b0f0a5 | 4 years ago |
|
e3e37303f5 | 4 years ago |
|
80516e1ffe | 4 years ago |
|
58c46ebf71 | 4 years ago |
|
a390dbda3f | 4 years ago |
|
0ec41f2de1 | 4 years ago |
|
bf8c687d6d | 4 years ago |
|
6be6378c65 | 4 years ago |
|
12fd25ef98 | 4 years ago |
|
d8d9ca501c | 4 years ago |
|
4166f4f06c | 4 years ago |
|
5aac044a13 | 4 years ago |
|
25bdec8a70 | 4 years ago |
|
4da3b66bc7 | 4 years ago |
|
18ec1b70fe | 4 years ago |
|
6c64b076d1 | 4 years ago |
|
03ca93bb0c | 4 years ago |
|
c72e2ed2c0 | 4 years ago |
|
b50c58a72e | 4 years ago |
|
536f26389a | 4 years ago |
|
8586b18a00 | 4 years ago |
|
65882678ca | 4 years ago |
|
e8424cc035 | 4 years ago |
|
c30d0942ed | 4 years ago |
|
23f207cca1 | 4 years ago |
|
da553bc055 | 4 years ago |
|
1409042275 | 4 years ago |
|
da4d163d35 | 4 years ago |
|
86cc31f4b9 | 4 years ago |
|
d33bff42f9 | 4 years ago |
|
6a03e1d133 | 4 years ago |
|
541e47b6e5 | 4 years ago |
|
d953277d83 | 4 years ago |
|
8080b1bca8 | 4 years ago |
|
e16fd44b36 | 4 years ago |
|
378d86db0d | 4 years ago |
|
b7fdaaac21 | 4 years ago |
|
ed9ce410a7 | 4 years ago |
|
be627fa38d | 4 years ago |
|
da24d3fcc7 | 4 years ago |
|
2f5dbd9ae2 | 4 years ago |
|
1cd61c0e22 | 4 years ago |
|
9e3e6d4a5d | 4 years ago |
|
3364e44dd5 | 4 years ago |
|
1847e04a43 | 4 years ago |
|
fb269e3cfe | 4 years ago |
|
b302b774fd | 4 years ago |
|
7066cd9dda | 4 years ago |
|
1443e795ea | 4 years ago |
|
3ac835cabd | 4 years ago |
|
fe1ee623a9 | 4 years ago |
|
2588ecaf1c | 4 years ago |
|
b307c8f5f0 | 4 years ago |
|
ceb336c9a8 | 4 years ago |
|
06bf136568 | 4 years ago |
|
173d8d8d3a | 4 years ago |
|
848d450732 | 4 years ago |
|
1b86a8bc84 | 4 years ago |
|
f83fa33a34 | 4 years ago |
|
d116db5fc6 | 4 years ago |
|
c07796e360 | 4 years ago |
|
b6f1e06757 | 4 years ago |
|
c139dce06c | 4 years ago |
|
6b13d67151 | 4 years ago |
|
ea2e4769f3 | 4 years ago |
|
a33b79a359 | 4 years ago |
|
5c796c104e | 4 years ago |
|
1de6015247 | 4 years ago |
|
407774834a | 4 years ago |
|
9eed0d94de | 4 years ago |
|
932d26b9bd | 4 years ago |
|
88c14bb0bc | 4 years ago |
|
b8fbcc8f0c | 4 years ago |
|
d2958650e6 | 4 years ago |
|
a42e76edce | 4 years ago |
|
4c94197bc2 | 4 years ago |
|
99b3dbb798 | 4 years ago |
|
a980d9c3fa | 4 years ago |
|
685372fc5b | 4 years ago |
|
deb6ef307a | 4 years ago |
|
af2dc43444 | 4 years ago |
@ -1,100 +0,0 @@
|
||||
version: 2
|
||||
jobs:
|
||||
# Define in CircleCi Project Variables: $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS
|
||||
# Publish jobs require those variables
|
||||
publish_docker_linuxamd64:
|
||||
machine:
|
||||
docker_layer_caching: false
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
LATEST_TAG="${CIRCLE_TAG:1}"
|
||||
DOCKERHUB_DESTINATION="$DOCKERHUB_REPO:$LATEST_TAG-amd64"
|
||||
DOCKERHUB_DOCKERFILE="Dockerfile"
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker build --pull -t "$DOCKERHUB_DESTINATION" -f "$DOCKERHUB_DOCKERFILE" .
|
||||
sudo docker push "$DOCKERHUB_DESTINATION"
|
||||
|
||||
publish_docker_linuxarm32v7:
|
||||
machine:
|
||||
docker_layer_caching: false
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
LATEST_TAG="${CIRCLE_TAG:1}"
|
||||
DOCKERHUB_DESTINATION="$DOCKERHUB_REPO:$LATEST_TAG-arm32v7"
|
||||
DOCKERHUB_DOCKERFILE="Dockerfile.arm32v7"
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker build --pull -t "$DOCKERHUB_DESTINATION" -f "$DOCKERHUB_DOCKERFILE" .
|
||||
sudo docker push "$DOCKERHUB_DESTINATION"
|
||||
|
||||
publish_docker_linuxarm64v8:
|
||||
machine:
|
||||
docker_layer_caching: false
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
command: |
|
||||
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
LATEST_TAG="${CIRCLE_TAG:1}"
|
||||
DOCKERHUB_DESTINATION="$DOCKERHUB_REPO:$LATEST_TAG-arm64v8"
|
||||
DOCKERHUB_DOCKERFILE="Dockerfile.arm64v8"
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
sudo docker build --pull -t "$DOCKERHUB_DESTINATION" -f "$DOCKERHUB_DOCKERFILE" .
|
||||
sudo docker push "$DOCKERHUB_DESTINATION"
|
||||
|
||||
publish_docker_multiarch:
|
||||
machine:
|
||||
enabled: true
|
||||
image: circleci/classic:201808-01
|
||||
steps:
|
||||
- run:
|
||||
command: |
|
||||
# Turn on Experimental features
|
||||
LATEST_TAG="${CIRCLE_TAG:1}"
|
||||
sudo mkdir $HOME/.docker
|
||||
sudo sh -c 'echo "{ \"experimental\": \"enabled\" }" >> $HOME/.docker/config.json'
|
||||
#
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
#
|
||||
sudo docker manifest create --amend "$DOCKERHUB_REPO:$LATEST_TAG" "$DOCKERHUB_REPO:$LATEST_TAG-amd64" "$DOCKERHUB_REPO:$LATEST_TAG-arm32v7" "$DOCKERHUB_REPO:$LATEST_TAG-arm64v8"
|
||||
sudo docker manifest annotate "$DOCKERHUB_REPO:$LATEST_TAG" "$DOCKERHUB_REPO:$LATEST_TAG-amd64" --os linux --arch amd64
|
||||
sudo docker manifest annotate "$DOCKERHUB_REPO:$LATEST_TAG" "$DOCKERHUB_REPO:$LATEST_TAG-arm32v7" --os linux --arch arm --variant v7
|
||||
sudo docker manifest annotate "$DOCKERHUB_REPO:$LATEST_TAG" "$DOCKERHUB_REPO:$LATEST_TAG-arm64v8" --os linux --arch arm64 --variant v8
|
||||
sudo docker manifest push "$DOCKERHUB_REPO:$LATEST_TAG" -p
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
publish:
|
||||
jobs:
|
||||
- publish_docker_linuxamd64:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
|
||||
- publish_docker_linuxarm32v7:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
|
||||
- publish_docker_linuxarm64v8:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
|
||||
- publish_docker_multiarch:
|
||||
requires:
|
||||
- publish_docker_linuxamd64
|
||||
- publish_docker_linuxarm32v7
|
||||
- publish_docker_linuxarm64v8
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /v(?:(?<major>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<minor>(?:0|[1-9](?:(?:0|[1-9])+)*))[.](?<patch>(?:0|[1-9](?:(?:0|[1-9])+)*))(?:-(?:([A-Za-z1-9])*))?)$/
|
@ -0,0 +1,49 @@
|
||||
.angular/
|
||||
.circleci/
|
||||
.git/
|
||||
.github/
|
||||
.settings/
|
||||
.vscode/
|
||||
frontend/
|
||||
backend/
|
||||
backup/
|
||||
cookies/
|
||||
coverage/
|
||||
dist/
|
||||
docker/
|
||||
dockerfiles/
|
||||
logs/
|
||||
node_modules/
|
||||
node_modules_old/
|
||||
node_modules_prod/
|
||||
node_modules_dev/
|
||||
out-tsc/
|
||||
tmp/
|
||||
typings/
|
||||
.browserlistrc
|
||||
_config.yml
|
||||
.classpath
|
||||
.DS_Store
|
||||
.gitattributes
|
||||
.gitignore
|
||||
.idea
|
||||
*.launch
|
||||
.project
|
||||
.sass-cache
|
||||
*.sublime-workspace
|
||||
.vscode/*
|
||||
connect.lock
|
||||
ECLDummyData.log
|
||||
libpeerconnection.log
|
||||
npm-debug.log
|
||||
RTL-Config.json
|
||||
RTL-Config-Old.json
|
||||
RTL-Config-1.json
|
||||
RTL-Multi-Node-Conf.json
|
||||
RTL.conf
|
||||
RTL-1.conf
|
||||
RTL-Multi-Node-Conf-1.json
|
||||
RTL-Config-for-BTC-Testing.json
|
||||
testem.log
|
||||
Thumbs.db
|
||||
yarn-error.log
|
@ -0,0 +1,215 @@
|
||||
{
|
||||
"root": true,
|
||||
"ignorePatterns": [
|
||||
"backend/**/*.js"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": [
|
||||
"*.ts"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2022,
|
||||
"sourceType": "module",
|
||||
"project": "./tsconfig.json",
|
||||
"createDefaultProgram": true
|
||||
},
|
||||
"env": { "es2022": true },
|
||||
"plugins": ["deprecation"],
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/all",
|
||||
"plugin:@angular-eslint/recommended",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
],
|
||||
"rules": {
|
||||
"@angular-eslint/component-selector": ["error", { "prefix": "rtl", "style": "kebab-case", "type": "element" }],
|
||||
"@angular-eslint/directive-selector": ["error", { "style": "camelCase", "type": "attribute" }],
|
||||
"@angular-eslint/consistent-component-styles": "off",
|
||||
"@angular-eslint/prefer-on-push-component-change-detection": "off",
|
||||
"@angular-eslint/prefer-standalone": "off",
|
||||
"@angular-eslint/prefer-standalone-component": "off",
|
||||
"@angular-eslint/sort-ngmodule-metadata-arrays": "off",
|
||||
"@angular-eslint/use-component-view-encapsulation": "off",
|
||||
"@angular-eslint/use-injectable-provided-in": "off",
|
||||
"@typescript-eslint/member-delimiter-style": "off",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/type-annotation-spacing": 0,
|
||||
"quotes": ["error", "single"],
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": ["error", { "before": false, "after": true }],
|
||||
"computed-property-spacing": ["error", "never", { "enforceForClassMembers": true }],
|
||||
"array-bracket-spacing": ["error", "never", { "objectsInArrays": false }],
|
||||
"space-in-parens": ["error", "never"],
|
||||
"eol-last": ["error", "always"],
|
||||
"array-bracket-newline": ["error", "consistent"],
|
||||
"curly": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"strict": "error",
|
||||
"max-len": ["error", { "code": 320 }],
|
||||
"no-multiple-empty-lines": "error",
|
||||
"no-trailing-spaces": "error",
|
||||
"quote-props": ["error", "as-needed"],
|
||||
"space-before-function-paren": ["error", { "anonymous": "never", "asyncArrow": "always", "named": "never" }],
|
||||
"semi": ["error", "always"],
|
||||
"dot-location": "error",
|
||||
"no-await-in-loop": "error",
|
||||
"no-console": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-promise-executor-return": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-unreachable-loop": "error",
|
||||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"require-atomic-updates": "error",
|
||||
"array-callback-return": "error",
|
||||
"block-scoped-var": "error",
|
||||
"default-case": "error",
|
||||
"default-case-last": "error",
|
||||
"eqeqeq": "error",
|
||||
"grouped-accessor-pairs": "error",
|
||||
"guard-for-in": "error",
|
||||
"no-alert": "error",
|
||||
"no-caller": "error",
|
||||
"no-constructor-return": "error",
|
||||
"no-div-regex": "error",
|
||||
"no-eq-null": "error",
|
||||
"no-eval": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-label": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-implicit-globals": "error",
|
||||
"no-implied-eval": "error",
|
||||
"no-iterator": "error",
|
||||
"no-labels": "error",
|
||||
"no-lone-blocks": "error",
|
||||
"no-loop-func": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-nonoctal-decimal-escape": "error",
|
||||
"no-octal-escape": "error",
|
||||
"no-proto": "error",
|
||||
"no-restricted-properties": "error",
|
||||
"no-return-assign": "error",
|
||||
"no-return-await": "error",
|
||||
"no-script-url": "error",
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-useless-call": "error",
|
||||
"no-useless-concat": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-void": "error",
|
||||
"no-warning-comments": "error",
|
||||
"prefer-named-capture-group": "error",
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"prefer-regex-literals": "error",
|
||||
"vars-on-top": "error",
|
||||
"wrap-iife": "error",
|
||||
"yoda": "error",
|
||||
"no-label-var": "error",
|
||||
"no-restricted-globals": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"comma-style": "error",
|
||||
"func-call-spacing": "error",
|
||||
"func-name-matching": "error",
|
||||
"func-names": "error",
|
||||
"id-match": "error",
|
||||
"implicit-arrow-linebreak": "error",
|
||||
"indent": ["error", 2, { "SwitchCase": 1, "MemberExpression": 1, "ArrayExpression": "off" }],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true, "overrides": { "this": { "before": false }}}],
|
||||
"lines-around-comment": "error",
|
||||
"max-depth": ["error", { "max": 7 }],
|
||||
"max-nested-callbacks": "error",
|
||||
"max-statements-per-line": ["error", { "max": 3 }],
|
||||
"no-array-constructor": "error",
|
||||
"no-continue": "error",
|
||||
"no-mixed-operators": "error",
|
||||
"no-multi-assign": "error",
|
||||
"no-new-object": "error",
|
||||
"no-restricted-syntax": "error",
|
||||
"no-tabs": "error",
|
||||
"no-unneeded-ternary": "error",
|
||||
"no-whitespace-before-property": "error",
|
||||
"nonblock-statement-body-position": "error",
|
||||
"object-curly-newline": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"one-var-declaration-per-line": "error",
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"padded-blocks": ["error", { "classes": "always", "blocks": "never", "switches": "never" }],
|
||||
"padding-line-between-statements": "error",
|
||||
"prefer-exponentiation-operator": "error",
|
||||
"semi-spacing": "error",
|
||||
"semi-style": "error",
|
||||
"space-before-blocks": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"switch-colon-spacing": "error",
|
||||
"template-tag-spacing": "error",
|
||||
"unicode-bom": "error",
|
||||
"wrap-regex": "error",
|
||||
"arrow-body-style": "error",
|
||||
"arrow-parens": "error",
|
||||
"arrow-spacing": "error",
|
||||
"generator-star-spacing": "error",
|
||||
"no-confusing-arrow": "error",
|
||||
"no-duplicate-imports": "error",
|
||||
"no-restricted-exports": "error",
|
||||
"no-restricted-imports": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-var": "error",
|
||||
"prefer-arrow-callback": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-numeric-literals": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error",
|
||||
"rest-spread-spacing": "error",
|
||||
"symbol-description": "error",
|
||||
"template-curly-spacing": "error",
|
||||
"yield-star-spacing": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"*.html"
|
||||
],
|
||||
"parser": "@angular-eslint/template-parser",
|
||||
"extends": [
|
||||
"plugin:@angular-eslint/template/all",
|
||||
"plugin:@angular-eslint/template/recommended",
|
||||
"plugin:@angular-eslint/template/process-inline-templates"
|
||||
],
|
||||
"rules": {
|
||||
"@angular-eslint/arrow-body-style": "off",
|
||||
"@angular-eslint/template/accessibility-elements-content": "off",
|
||||
"@angular-eslint/template/accessibility-interactive-supports-focus": "off",
|
||||
"@angular-eslint/template/button-has-type": "off",
|
||||
"@angular-eslint/template/click-events-have-key-events": "off",
|
||||
"@angular-eslint/template/conditional-complexity": "off",
|
||||
"@angular-eslint/template/cyclomatic-complexity": "off",
|
||||
"@angular-eslint/template/elements-content": "off",
|
||||
"@angular-eslint/template/i18n": "off",
|
||||
"@angular-eslint/template/no-autofocus": "off",
|
||||
"@angular-eslint/template/no-call-expression": "off",
|
||||
"@angular-eslint/template/no-inline-styles": "off",
|
||||
"@angular-eslint/template/no-interpolation-in-attributes": "off",
|
||||
"@angular-eslint/template/no-positive-tabindex": "off",
|
||||
"@angular-eslint/template/prefer-ngsrc": "off",
|
||||
"@angular-eslint/template/prefer-control-flow": "off",
|
||||
"@angular-eslint/template/prefer-self-closing-tags": "off",
|
||||
"@angular-eslint/template/use-track-by-function": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
RTL allows the user to configure and control specific application parameters for app customization and integration.<br />
|
||||
The parameters can be configured via RTL-Config.json file or through environment variables defined at the OS level. Required <br />
|
||||
parameters have `default` values for initial setup and can be updated after RTL server initial start.<br />
|
||||
<br />
|
||||
### RTL-Config.json<br />
|
||||
```
|
||||
{
|
||||
"multiPass": "<The password in plain text, default 'password', Required>",
|
||||
"port": "<port number for the rtl node server, default '3000', Required>",
|
||||
"host": "<host for the rtl node server, default 'all IPs', Optional>",
|
||||
"defaultNodeIndex": <Default index to load when rtl server starts, default 1, Optional>,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl database file should be saved, defults to RTL root, Optional>",
|
||||
"SSO": {
|
||||
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), Required>,
|
||||
"rtlCookiePath": "<Full path of the cookie file including the file name. The application url needs to pass the value from this cookie file as query param 'access-key' for the SSO authentication to work, Required if SSO=1 else empty (Optional)>",
|
||||
"logoutRedirectLink": "<URL to re-direct to after logout/timeout from RTL, Required if SSO=1 else empty (Optional)>"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"index": <Incremental node indices starting from 1, Required>,
|
||||
"lnNode": "<Node name to uniquely identify the node in the UI, Required>",
|
||||
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL, Required>",
|
||||
"authentication": {
|
||||
"macaroonPath": "<Path for the folder containing 'admin.macaroon' for LND node, Required for LND>",
|
||||
"runePath": "<Complete path including filename for CLN rune for the node, Required for CLN>",
|
||||
"lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>"
|
||||
"swapMacaroonPath": "<Path for the folder containing 'loop.macaroon' (LND), Required for LND Loop>",
|
||||
"boltzMacaroonPath": "<Path for the folder containing 'admin.macaroon' (Boltz), Required for Boltz Swaps>",
|
||||
"configPath": "<Full path of the lnd.conf/core lightning config/eclair.conf file including the file name, if present locally, Optional, only mandatory for ECL if the lnApiPassword is missing>",
|
||||
},
|
||||
"settings": {
|
||||
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT/OPERATOR. Default MERCHANT, Optional>",
|
||||
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Optional>",
|
||||
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK, YELLOW. Default PURPLE, Optional>",
|
||||
"channelBackupPath": "<Path to save channel backup file. Only for LND implementation, Default <RTL root>\backup\node-1, Optional>",
|
||||
"bitcoindConfigPath": "<Path of bitcoind.conf path if available locally>",
|
||||
"logLevel": <logging levels, will log in accordance with the logLevel value provided, Allowed values ERROR, WARN, INFO, DEBUG>,
|
||||
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Optional>,
|
||||
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD', Optional>",
|
||||
"unannouncedChannels": <parameter to turn off/on setting for opening announced Channels, default false, Optional>
|
||||
"lnServerUrl": "<Service url for LND/Core Lightning REST APIs for the node, e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001 OR http://192.168.0.1:8080. Default 'https://127.0.0.1:8080', Optional>
|
||||
"swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. https://127.0.0.1:8081, Optional>",
|
||||
"boltzServerUrl": "<Service url for boltz server REST APIs for the node, e.g. https://127.0.0.1:9003, Optional>",
|
||||
"blockExplorerUrl": "<url for local or centralized block explorer. e.g. https://mempool.space>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
<br />
|
||||
### Environment variables<br />
|
||||
The environment variable can also be used for all of the above configurations except the UI settings.<br />
|
||||
If the environment variables are set, it will take precedence over the parameters in the RTL-Config.json file.<br />
|
||||
<br />
|
||||
PORT (port number for the rtl node server, default 3000, Optional)<br />
|
||||
HOST (host for the rtl node server, default localhost, Optional)<br />
|
||||
DB_DIRECTORY_PATH (Path for the folder where rtl database file should be saved, default RTL root directory, Optional)
|
||||
APP_PASSWORD (Plaintext password to be provided by the parent container, NOT suggested for standalone RTL applications, to be used by Umbrel) (Optional)<br />
|
||||
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Optional)<br />
|
||||
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080) (Optional)<br />
|
||||
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://127.0.0.1:8081) (Optional)<br />
|
||||
BOLTZ_SERVER_URL (Boltz server URL for REST APIs, default http://127.0.0.1:9003) (Optional)<br />
|
||||
CONFIG_PATH (Full path of the LNP .conf file including the file name) (Optional for LND & CLN, Mandatory for ECL if LN_API_PASSWORD is undefined)<br />
|
||||
MACAROON_PATH (Path for the folder containing 'admin.macaroon' for LND, Required for LND)<br />
|
||||
RUNE_PATH (Complete path for the file containing 'rune' for CLN where the file should define the rune in 'LIGHTNING_RUNE="your-rune"' format, Required for CLN)<br />
|
||||
SWAP_MACAROON_PATH (Path for the folder containing Loop's 'loop.macaroon', optional)<br />
|
||||
BOLTZ_MACAROON_PATH (Path for the folder containing Boltz's 'admin.macaroon', optional)<br />
|
||||
RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication, Required)<br />
|
||||
RTL_COOKIE_PATH (Full path of the cookie file including the file name, Required if RTL_SSO=1 else Optional)<br />
|
||||
LOGOUT_REDIRECT_LINK (URL to re-direct to after logout/timeout from RTL, Required if RTL_SSO=1 else Optional)<br />
|
||||
RTL_CONFIG_PATH (Path for the folder containing 'RTL-Config.json' file, Required)<br />
|
||||
BITCOIND_CONFIG_PATH (Full path of the bitcoind.conf file including the file name, Optional)<br />
|
||||
CHANNEL_BACKUP_PATH (Folder location for saving the channel backup files, valid for LND implementation only, Required if ln implementation=LND else Optional)<br />
|
||||
ENABLE_OFFERS (Boolean flag to enable the offers feature on core lighning, default false, optional)<br />
|
||||
ENABLE_PEERSWAP (Boolean flag to enable the peerswap feature on core lighning, default false, optional)<br />
|
||||
LN_API_PASSWORD (Password for Eclair implementation if the eclair.conf path is not available, Required if ln implementation=ECL && config path is undefined)<br />
|
@ -0,0 +1,78 @@
|
||||
[Intro](../README.md) -- **Application Features** -- [Road Map](Roadmap.md) -- [Application Configurations](Application_configurations.md)
|
||||
|
||||
## RTL - Feature List
|
||||
|
||||
### Home Page - Operator
|
||||
- General node information
|
||||
- On-Chain and Lightning balances
|
||||
- Channel capacity with Local-Remote balances and Balance score
|
||||
- Routing fee earned and transactions routed
|
||||
- Channel status for Active, Pending, Inactive and Closing channels
|
||||
|
||||
### Home Page - Merchant
|
||||
- On-Chain and Lightning balances
|
||||
- Inbound Channel capacity
|
||||
- Outbound Channel capacity
|
||||
- Receive and Pay Lightning transactions
|
||||
|
||||
### On-Chain
|
||||
- Total, Confirmed and Unconfirmed balances in Sats, BTC and Fiat
|
||||
- Receive Funds, Generate Address (with QR Code)
|
||||
- Send funds
|
||||
- Sweep all funds {LND only}
|
||||
- List of on-chain transactions {LND only}
|
||||
- Export transaction list to csv
|
||||
|
||||
### Lightning
|
||||
#### Peers/Channels
|
||||
- Peer management (Connect, disconnect with network peers)
|
||||
- Open channel
|
||||
- Close channel
|
||||
- Update channel fee policy
|
||||
- Circular Rebalance {LND only}
|
||||
- Open, Pending and Closed channel list
|
||||
- Export all lists to csv
|
||||
|
||||
#### Transactions
|
||||
- Make payments
|
||||
- Invoice generation
|
||||
- Query routes
|
||||
|
||||
#### Routing
|
||||
- Forwarding history
|
||||
- Routing peers summary {LND only}
|
||||
|
||||
#### Graph Lookup
|
||||
- Lookup a node details with pubkey
|
||||
- Lookup a channel details with Channel ID
|
||||
|
||||
#### Sign/Verify
|
||||
- Sign a message with node's private key and generate a signature
|
||||
- Verify the message with a signature to determine the pubkey of the node used to sign
|
||||
|
||||
#### Loop - Optional Feature {LND only}
|
||||
- Loop Out for gaining Inbound channel capacity
|
||||
- Loop In for replenishing Outbound capacity
|
||||
- Loop Out and In transactions list
|
||||
|
||||
#### Backup {LND only}
|
||||
- All channel backup and verify
|
||||
- Individual channel backup and verify
|
||||
- Folder location of the backup files
|
||||
|
||||
### Network {LND only}
|
||||
- Network information from the graph
|
||||
|
||||
### Settings
|
||||
- Fiat conversion toggle
|
||||
- Default node setting for multiple nodes
|
||||
- Toggle for Persona switch to change the dashboard layout
|
||||
- Day-Night mode toggle
|
||||
- Themes for color customizations
|
||||
|
||||
### Help
|
||||
- Basic In-product documentation
|
||||
|
||||
### Public Key
|
||||
- Display the node pubkey along with a QR code
|
||||
- Display the node URI along with a QR code
|
@ -0,0 +1,79 @@
|
||||
## Contributing to RTL
|
||||
Thanks for your interest in contributing to the development of RTL. RTL is a community project and aspires to remain free and open source for the benefit of the community. With that objective in mind, this document provides contribution guidelines which the community can utilize to contribute towards the development and maintenance of this software.
|
||||
|
||||
### <a name="how"></a>How Can I Contribute
|
||||
There are multiple ways you can contribute towards the development and not all of those methods involve coding. Below are a few examples on how meaningful contributions can be made.
|
||||
* [Bug Report](#bug) - While using RTL, if you notice something is not working correctly create a bug report, by creating an issue.
|
||||
* [Feature Request](#feature) - While using RTL, if you feel that the software should be changed in certain way to make it for usable and helpful, create a feature request.
|
||||
* [Testing](#testing) - Testing is one the easiest and most sought after method of contribution. Testing can be done on release branches, so that releases are relatively bug free.
|
||||
* [Design](#design) - Design inputs can be made based on user enhancement suggestions or novel ideas which you get while using RTL.
|
||||
* [Code](#code) - Development contributions are made via making coding changes to the software and getting it tested, reviewed and merged.
|
||||
* [Code Review](#codereview) - Code review contributions are made by reviewing the code changes submitted via PRs to address bugs or feature requests
|
||||
|
||||
#### <a name="bug"></a>Bug Report
|
||||
Bug reports are reports of technical or functional issues with the software. Bug reports help with the removal of defects from the software and improve its quality. Guidelines for submitting a bug report:
|
||||
* Label the bug with the correct Lightning implementation (LND/Core Lightning/Eclair).
|
||||
* Add the `Bug` label to the issue
|
||||
* Provide details of your configuration like Device, Operating system, Bitcoin version, Lightning implementation version, RTL version etc.
|
||||
* Attempt to explain the scenario in detail, so that the developer can try to replicate the issue at their end.
|
||||
* If the bug is with the UI, screenshots help. Try to highlight the problem areas by circling with red outline.
|
||||
* Take care to redact sensitive info from the screenshots like Pubkey or channel IDs etc.
|
||||
* Be responsive to the developers requesting details on the issues.
|
||||
|
||||
#### <a name="feature"></a>Feature Request
|
||||
Feature Requests are requests raised to add new features to the application. The features requests can range from technical to functional, making the application better for everyone. Guidelines to follow for create a feature request:
|
||||
* Label the feature request with the correct Lightning implementation (LND/Core Lightning/Eclair).
|
||||
* Add the `Enhancement Request` label to the issue
|
||||
* If the feature relates to an existing aspect of the application, indicate clearly which part of the application the feature request relates to. E.g. Transactions page under Lightning menu.
|
||||
* Provide the justification for the feature request. E.g. Privacy/Security/Usability benefit.
|
||||
* If the feature request is technical in nature, try to provide the platform detail like OS, Lightning Implementation version etc.
|
||||
* For new UI features mockups are helpful for the developers.
|
||||
* Be responsive on the feature requests when developers request details or clarification and also help with the testing of the features requested.
|
||||
|
||||
#### <a name="testing"></a>Testing
|
||||
Testing is the easiest and most effective method to contribute. It helps uncover bugs and improve the quality of software. Best time to test would be pre-release, when the changes are being made to the software for the next release. RTL maintains a release branch for the next planned release and changes are merge to the release branch on a regular basis. The testers can contribute by pulling from the release branch and testing the software. If issues are found during testing, follow the steps described above to raise bug reports to help address the issues.
|
||||
|
||||
#### <a name="design"></a>Design
|
||||
Design suggestions are always welcome and helpful. Design suggestion can range from improving both the aesthetics as well as the UX of the application. We believe improving design and UX of the application is an ongoing journey. User feedback and bugs raised also provide insights into how both can be improved. if you would like to provide design related suggestions or contribute with design inputs, raise issues on the [Design repo of RTL](https://github.com/Ride-The-Lightning/RTL-Design) and follow the guidance provided there.
|
||||
|
||||
#### <a name="code"></a>Code
|
||||
Contributions via code is the most sought after contribution and something we enthusiastically encourage. Follow the below guideline to be able to contribute code to RTL.
|
||||
|
||||
##### Pull Code
|
||||
* Pull the code from the release (current eg Release-0.12.2) branch into your local workspace via github commandline/GUI.
|
||||
|
||||
##### Install Dependencies
|
||||
* Assuming that nodejs (v14 & above) and npm are already installed on your local machine. Go into your RTL root folder and run `npm install`.
|
||||
* Use `npm install --legacy-peer-deps` if there is any dependency conflict.
|
||||
* Sometimes after installation, user receives a message from npm to fix dependency vulnerability by running `npm audit fix`. Please do not follow this step as it can break some of the working RTL code on your machine. We audit and fix these vulnerabilities as soon as possible at our end.
|
||||
|
||||
##### Node Backend Server for Development
|
||||
* The RTL server code has been written in typescript and `npm run watchbackend` script can be used to compile and generate their javascript equivalents. Keep the script running to watch for realtime changes and compilation. `watchbackend` and `buildbackend` scripts get the configuration options from tsconfig, read .ts files from the `./server` folder and save the compiled .js and .map files in `./backend` folder.
|
||||
* To run RTL node server in development mode, open another command window, go to workspace/RTL and excute `npm run server`. This will run the script named `server` defined in package.json. This script sets the node environment as development and starts the server from rtl.js. Nodemon restarts the node application when file changes in the directory are detected.
|
||||
* This `server` script has been written for windows machine. Please update the script to set the `NODE_ENV=development` according to your machine's OS.
|
||||
* To check all available scripts for the project, explore the `scripts` section of package.json.
|
||||
![](../screenshots/node-server-dev.jpg)
|
||||
|
||||
##### Angular Frontend Server for Development
|
||||
* The last step starts the node server but it cannot detect and update the code written in Angular. We run the angular development server separately while working on the frontend of the project and package the final build once the development is finished.
|
||||
* To run the angular development server, go to workspace/RTL and run `npm run start`. It will start the angular server at default '4200' port and serve the application on localhost:4200.
|
||||
![](../screenshots/angular-server-dev.jpg)
|
||||
![](../screenshots/localhost-ui-dev.jpg)
|
||||
|
||||
##### Package Angular Build
|
||||
* Run `npm run test` script to verify and fix, if needed, automated test cases.
|
||||
* Execute `npm run lint` to lint the code before final compilation.
|
||||
* To compile the backend code, `npm run buildbackend` script should be used. It will compile the code written in typescript in `server` folder and create a folder named `backend` with final compiled javascript code.
|
||||
* The Angular application code needs to be compiled into the output directory named `frontend` at workspace/RTL. It can be done by running `npm run buildfrontend` command in the RTL root.
|
||||
* Please make sure to remove all linting and other errors thrown by the build command before moving to the next step.
|
||||
![](../screenshots/angular-build.jpg)
|
||||
|
||||
##### Create a Pull Request
|
||||
* Create a new branch on the github to push your updated code.
|
||||
* Commit your updates into the newly created branch.
|
||||
* Create a new pull request once you are satisfied with your updates to be merged into the latest `release` branch with details of your updates and submit it for the review.
|
||||
|
||||
##### Caution about adding new libraries
|
||||
* We are conservative in adding new dependencies to the repository. Do your best to not add any new libraries on RTL. We believe this is the best strategy to keep the software safe from vulnerabilites.
|
||||
* Confirm before starting by creating an issue about adding the library
|
||||
* The library should be popular, well maintained and pre-existing vulnerability free.
|
@ -0,0 +1,111 @@
|
||||
![](../screenshots/RTL-CLN-Arch-3.png)
|
||||
|
||||
## RTL Core lightning setup
|
||||
|
||||
* [Introduction](#intro)
|
||||
* [Pre-requisite](#prereq)
|
||||
* [Architecture](#arch)
|
||||
* [Installation](#install)
|
||||
* [Prep for execution](#prep)
|
||||
* [Start the server and access the app](#start)
|
||||
|
||||
### <a name="intro"></a>Introduction
|
||||
RTL is now enabled to manage lightning nodes running Core Lightning
|
||||
|
||||
Follow the below steps to install and setup RTL to run on Core Lightning
|
||||
|
||||
### <a name="prereq"></a>Pre-requisites:
|
||||
1. Functioning Core Lightning node. Follow install instructions on their [github](https://github.com/ElementsProject/lightning)
|
||||
2. NodeJS - Can be downloaded [here](https://nodejs.org/en/download)
|
||||
3. CLNRest - Ensure that core lightning's `CLNRest` API server is configured. Configuration instructions [here](https://docs.corelightning.org/docs/rest#configuration)
|
||||
4. Create/reuse core-lightning's rune. Check [`createrune`](https://docs.corelightning.org/reference/lightning-createrune) and [`showrunes`](https://docs.corelightning.org/reference/lightning-showrunes) documentation for more details on how to create runes
|
||||
4. Copy the `rune` and save it in a file which must be accessible to RTL. The content of the file must be `LIGHTNING_RUNE="<your-rune>"`
|
||||
|
||||
### <a name="arch"></a>Architecture
|
||||
![](../screenshots/RTL-CLN-Arch-2.png)
|
||||
|
||||
### <a name="install"></a>Installation:
|
||||
To download a specific RTL version follow the instructions on the [release page](https://github.com/Ride-The-Lightning/RTL/releases)
|
||||
|
||||
To download from master (*not recommended*):
|
||||
|
||||
#### First time setup
|
||||
```
|
||||
$ git clone https://github.com/Ride-The-Lightning/RTL.git
|
||||
$ cd RTL
|
||||
$ npm install --omit=dev --legacy-peer-deps
|
||||
```
|
||||
|
||||
#### Or: Update existing build
|
||||
```
|
||||
$ cd RTL
|
||||
$ git reset --hard HEAD
|
||||
$ git clean -f -d
|
||||
$ git pull
|
||||
$ npm install --omit=dev --legacy-peer-deps
|
||||
```
|
||||
|
||||
#### Error on npm install
|
||||
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
|
||||
|
||||
### <a name="prep"></a>Prep for Execution
|
||||
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app
|
||||
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`
|
||||
* Locate the complete path of the readable `.commando` file on your node
|
||||
* Modify the RTL conf file per the example file below
|
||||
|
||||
Ensure that the follow values are correct per your config:
|
||||
* `lnImplementation` - This should be `CLN`, indicating that RTL is connecting to a core lightning node
|
||||
* `runePath` - Path of the folder including **filename** which contains the `rune` for the node. The content of the file must be `LIGHTNING_RUNE="<your-rune>"`
|
||||
* `lnServerUrl` - complete url with ip address and port of the CLNRest server
|
||||
* `multiPass` - Specify the password (in plain text) to access RTL. This password will be hashed and not stored as plain text
|
||||
* `configPath` (optional) - File path of the core lightning config file, if RTL server is local to the core lightning server
|
||||
|
||||
```
|
||||
{
|
||||
"multiPass": <password required for accessing RTL>,
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
"logoutRedirectLink": ""
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"index": 1,
|
||||
"lnNode": "Core Lightning Testnet # 1",
|
||||
"lnImplementation": "CLN",
|
||||
"authentication": {
|
||||
"runePath": "<Modify to include the path of the folder including filename which contains `rune`>",
|
||||
"configPath": "<Optional - Config file path for core lightning>"
|
||||
},
|
||||
"settings": {
|
||||
"userPersona": "OPERATOR",
|
||||
"themeMode": "DAY",
|
||||
"themeColor": "PURPLE",
|
||||
"bitcoindConfigPath": "",
|
||||
"logLevel": "INFO",
|
||||
"fiatConversion": false,
|
||||
"unannouncedChannels": false,
|
||||
"lnServerUrl": "https://<CLNRest api server ip address>:3001",
|
||||
"blockExplorerUrl": "<Default: https://mempool.space>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
### <a name="start"></a>Start the server and access the app
|
||||
Run the following command:
|
||||
|
||||
`$ node rtl`
|
||||
|
||||
If the server started successfully, you should get the below output on the console:
|
||||
|
||||
`$ Server is up and running, please open the UI at http://localhost:3000 or your proxy configured url`
|
||||
|
||||
Open your browser at the following address: http://localhost:3000 to access the RTL app
|
||||
|
||||
### Detailed config and instructions
|
||||
For detailed config and access options and other information, view the main readme page
|
@ -0,0 +1,105 @@
|
||||
![](../screenshots/RTL-ECL-Dashboard.png)
|
||||
|
||||
## RTL Eclair setup
|
||||
|
||||
* [Introduction](#intro)
|
||||
* [Pre-requisite](#prereq)
|
||||
* [Architecture](#arch)
|
||||
* [Installation](#install)
|
||||
* [Prep for execution](#prep)
|
||||
* [Start the server and access the app](#start)
|
||||
|
||||
### <a name="intro"></a>Introduction
|
||||
RTL is now enabled to manage an Eclair node.
|
||||
|
||||
Follow the below steps to install and setup RTL to run on Eclair.
|
||||
|
||||
### <a name="prereq"></a>Pre-requisites:
|
||||
1. Functioning Eclair node v0.4.1 or above. Follow install instructions on their [github](https://github.com/ACINQ/eclair) page.
|
||||
2. Bitcoin core v0.19.1 or above (this is an Eclair dependency).
|
||||
3. NodeJS - Can be downloaded [here](https://nodejs.org/en/download)
|
||||
|
||||
### <a name="install"></a>Installation:
|
||||
Eclair is integrated with RTL v0.8.0 and above.
|
||||
To download a specific RTL version follow the instructions on the [release page](https://github.com/Ride-The-Lightning/RTL/releases)
|
||||
|
||||
To download from master (*not recommended*) follow the below instructions:
|
||||
#### First time setup
|
||||
```
|
||||
$ git clone https://github.com/Ride-The-Lightning/RTL.git
|
||||
$ cd RTL
|
||||
$ npm install --omit=dev --legacy-peer-deps
|
||||
```
|
||||
#### Or: Update existing build
|
||||
```
|
||||
$ cd RTL
|
||||
$ git reset --hard HEAD
|
||||
$ git clean -f -d
|
||||
$ git pull
|
||||
$ npm install --omit=dev --legacy-peer-deps
|
||||
```
|
||||
|
||||
#### Error on npm install
|
||||
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
|
||||
|
||||
### <a name="prep"></a>Prep for Execution
|
||||
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
|
||||
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`..
|
||||
* Locate the complete path of the readable `eclair.conf` for your node.
|
||||
* Modify the RTL conf file per the example file below
|
||||
|
||||
Ensure that the follow values are correct per your config:
|
||||
* `lnImplementation` - This should be `ECL`, indicating that RTL is connecting to an Eclair node.
|
||||
* `lnServerUrl` - complete url with ip address and port of the eclair server.
|
||||
* `multiPass` - Specify the password (in plain text) to access RTL. This password will be hashed and not stored as plain text.
|
||||
* `configPath` (Optinal) - Full path of the folder containing `eclair.conf` including the file name. Can be used for the basic password authentication through `eclair.api.password`.
|
||||
* `lnApiPassword` (Mandatory if configPath is missing) - The same value from eclair.conf's eclair.api.password should be provided directly here. It will be used for Eclair API authentication.
|
||||
|
||||
```
|
||||
{
|
||||
"multiPass": <password required for accessing RTL>,
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
"logoutRedirectLink": ""
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"index": 1,
|
||||
"lnNode": "Eclair Testnet # 1",
|
||||
"lnImplementation": "ECL",
|
||||
"authentication": {
|
||||
"configPath": "<Optional - Config file path, including .conf file>",
|
||||
"lnApiPassword": "<Mandatory if the configPath is missing - Password used for API authentication>",
|
||||
},
|
||||
"settings": {
|
||||
"userPersona": "OPERATOR",
|
||||
"themeMode": "DAY",
|
||||
"themeColor": "PURPLE",
|
||||
"bitcoindConfigPath": "",
|
||||
"logLevel": "INFO",
|
||||
"fiatConversion": false,
|
||||
"unannouncedChannels": false,
|
||||
"lnServerUrl": "http://<eclair api server ip address>:port",
|
||||
"blockExplorerUrl": "<Default: https://mempool.space>"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
### <a name="start"></a>Start the server and access the app
|
||||
Run the following command:
|
||||
|
||||
`$ node rtl`
|
||||
|
||||
If the server started successfully, you should get the below output on the console:
|
||||
|
||||
`$ Server is up and running, please open the UI at http://localhost:3000 or your proxy configured url.`
|
||||
|
||||
Open your browser at the following address: http://localhost:3000 to access the RTL app.
|
||||
|
||||
### Detailed config and instructions
|
||||
For detailed config and access options and other information, view the main readme page.
|
@ -0,0 +1,82 @@
|
||||
### Setup https access for RTL
|
||||
|
||||
Forward the ports 80 and 3002 on the router to the device running RTL.
|
||||
Allow the ports through the firewall of the device.
|
||||
|
||||
Install, if needed, openssl
|
||||
On Debian based distros:
|
||||
$> sudo apt install openssl
|
||||
|
||||
Create a self certificate with openssl
|
||||
$> openssl req -newkey rsa:4096 -x509 -sha512 -days 365 -nodes -out /path/to/some/folder/rtl-cert.crt -keyout /path/to/some/folder/rtl-cert.key
|
||||
|
||||
#### Nginx
|
||||
|
||||
Install Nginx:
|
||||
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/
|
||||
On Debian based distros:
|
||||
$> sudo apt install nginx
|
||||
|
||||
nginx default config file is at /etc/nginx/nginx.conf. You will need it.
|
||||
|
||||
Sample configuration to be inserted in the nginx.conf (adjust the path and filename of your certificate and key):
|
||||
|
||||
stream {
|
||||
upstream RTL {
|
||||
server 127.0.0.1:3000;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 3002 ssl;
|
||||
proxy_pass RTL;
|
||||
|
||||
ssl_certificate /path/to/some/folder/rtl-cert.crt;
|
||||
ssl_certificate_key /path/to/some/folder/rtl-cert.key;
|
||||
ssl_session_cache shared:SSL:1m;
|
||||
ssl_session_timeout 4h;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # this line works for me with only TLSv1.2
|
||||
ssl_prefer_server_ciphers on;
|
||||
}
|
||||
}
|
||||
|
||||
Restart Nginx with the new configuration and connect to RTL over https on the port 3002.
|
||||
On Debian based distros:
|
||||
$> sudo systemctl restart nginx
|
||||
|
||||
#### Apache2
|
||||
|
||||
Skip to step 5 if you already have Apache2 set up with HTTPS configured.
|
||||
|
||||
1. Install [Apache2](https://httpd.apache.org/download.cgi)
|
||||
- On Debian-based distros: `sudo apt install apache2`
|
||||
- On Fedora-based distros: `sudo yum install httpd`
|
||||
2. Install [Let's Encrypt](https://letsencrypt.org) to get a free TLS certificate. The easiest way to install is to use Snap: `sudo snap install certbot`.
|
||||
3. Run `certbot` to get a Let's Encrypt certificate: `sudo certbot`. Follow the instructions given to validate your domain name and install the certificate only. You may choose to redirect HTTP traffic to HTTPS instead (which attempts to secure every connection even when the client device does not request it). Let's Encrypt does not issue certificates for IP addresses. if you don't have a domain name, but you can use a service like NoIP.
|
||||
4. Locate the Let's Encrypt Apache2 configuration file. It's usually in `/etc/apache2/sites-enabled` and called "000-default-le-ssl.conf" or similar.
|
||||
5. Add the following lines to the Apache2 configuration file between the VIrtualHost 443 tags. This will redirect Apache's document root on your webserver to instead point to RTL. Change "/" to something like "/rtl" if you would instead like to redirect "/rtl" to RTL and do something else at the document root. Change "3002" to whatever port number you are using if it is not 3002.
|
||||
|
||||
ProxyPass "/" "http://127.0.0.1:3002/rtl"
|
||||
ProxyPassReverse "/" "http://127.0.0.1:3002/rtl"
|
||||
|
||||
6. Restart Apache2.
|
||||
- Debian-based distros: `sudo systemctl restart apache2`
|
||||
- Fedora-based distros: `sudo systemctl restart httpd`
|
||||
7. (Option) Edit ~/.rtl/rtl.js to disable insercure HTTP access to RTL entirely. Find these lines:
|
||||
|
||||
if (common.host) {
|
||||
server.listen(common.port, common.host);
|
||||
} else {
|
||||
server.listen(common.port);
|
||||
}
|
||||
|
||||
...and change them to:
|
||||
|
||||
if (common.host) {
|
||||
server.listen(common.port, "127.0.0.1");
|
||||
} else {
|
||||
server.listen(common.port, "127.0.0.1");
|
||||
}
|
||||
|
||||
This disables normal HTTP access to your server except if the client is on the same machine as the server. This will allow you to access http://localhost:3002 (or whatever port number you are using) on a browser that is on the same machine as RTL, but otherwise, you will have to access RTL through the Apache2 reverse proxy at https://yourdomain.tld/rtl, which will secure the connection with HTTPS.
|
||||
|
||||
Note: Occasionally you will receive "Invalid CSRF token, form tempered" when attempting to log in to RTL. If that happens, refresh the page and try again.
|
@ -0,0 +1,47 @@
|
||||
### Connect to RTL remotely over Tor
|
||||
|
||||
This guide will allow you to remotely connect to RTL over Tor. This can work on any platform, the below example is for serving an android and windows client.
|
||||
|
||||
#### Server Setup
|
||||
Install Tor on the same local machine as RTL. see the tor project wiki [here](https://trac.torproject.org/projects/tor/wiki)
|
||||
On Debian based distros:
|
||||
$> sudo apt install tor
|
||||
|
||||
Edit `/etc/tor/torrc` (Debian based distro) configuration file, and add the following lines:
|
||||
```
|
||||
HiddenServiceDir /var/lib/tor/rtl-service-v3/
|
||||
HiddenServiceVersion 3
|
||||
HiddenServicePort 3000 127.0.0.1:3000
|
||||
```
|
||||
Change `/var/lib/tor/rtl-service-v3/` to any directory you want to store the hidden service credentials.
|
||||
|
||||
Save the changes to the `torrc` file and restart tor.
|
||||
$> sudo systemctl restart tor
|
||||
or sometimes:
|
||||
$> sudo systemctl daemon-reload
|
||||
|
||||
View the contents of the file `/var/lib/tor/rtl-service-v3/hostname`. You need to be root. It will show an onion address. This is your address.
|
||||
On Debian based distro:
|
||||
$> su -c "cat /var/lib/tor/rtl-service-v3/hostname"
|
||||
|
||||
#### Client setup: Android
|
||||
|
||||
Install Tor browser (or any other compatible browser) for Android from the app store
|
||||
|
||||
Open the tor enabled browser and type in the onion address (example `z1234567890abc.onion:3000`)
|
||||
Only you have access to this website! All traffic in the tor enabled browser will go over Tor (which is slower than clearnet).
|
||||
|
||||
#### Client setup: Windows Tor Browser (not updated)
|
||||
|
||||
Download and install Tor Browser for windows: https://www.torproject.org/download/
|
||||
|
||||
In Windows, edit `"%HOMEDRIVE%%HOMEPATH%"\Desktop\Tor Browser\Browser\TorBrowser\Data\Tor\torrc`
|
||||
|
||||
Add the following line. Replace the onion address, password(cookie), and mydevice with your credentials:
|
||||
```
|
||||
HidServAuth 1234567890abcdefg.onion abcdef01234567890+/K mydevice
|
||||
```
|
||||
|
||||
Save and exit.
|
||||
|
||||
Now open Tor Browser, type in the `1234567890abcdefg.onion:3000` address!
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 297 KiB |
After Width: | Height: | Size: 338 KiB |
After Width: | Height: | Size: 324 KiB |
After Width: | Height: | Size: 280 KiB |
After Width: | Height: | Size: 312 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 88 KiB |
After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 181 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 142 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 92 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 222 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 146 KiB |
After Width: | Height: | Size: 77 KiB |
After Width: | Height: | Size: 48 KiB |
@ -0,0 +1,92 @@
|
||||
name: Lint & Test
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ 'v*' ]
|
||||
release:
|
||||
types: [released]
|
||||
# Triggers the workflow only when merging pull request to the branches.
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [ master, 'Release-*' ]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v2
|
||||
id: cache-npm-packages
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Install NPM dependencies
|
||||
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v2
|
||||
id: cache-npm-packages
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Install NPM dependencies
|
||||
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Lint Src and Server
|
||||
run: npm run lint
|
||||
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
needs: prepare
|
||||
env:
|
||||
CI: true
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v2
|
||||
id: cache-npm-packages
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Install NPM dependencies
|
||||
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Run tests
|
||||
run: npm run test
|
@ -0,0 +1,86 @@
|
||||
name: Artifact
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, 'Release-*' ]
|
||||
tags: [ 'v*' ]
|
||||
release:
|
||||
types: [released]
|
||||
# Triggers the workflow only when merging pull request to the branches.
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [ master, 'Release-*' ]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v2
|
||||
id: cache-npm-packages
|
||||
with:
|
||||
path: node_modules
|
||||
key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }}
|
||||
|
||||
- name: Install NPM dependencies
|
||||
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Cache build frontend
|
||||
uses: actions/cache@v2
|
||||
id: cache-build-frontend
|
||||
with:
|
||||
path: frontend
|
||||
key: ${{ runner.os }}-frontend-${{ github.sha }}
|
||||
|
||||
- name: Run build production application
|
||||
run: npm run buildfrontend
|
||||
|
||||
- name: Cache build backend
|
||||
uses: actions/cache@v2
|
||||
id: cache-build-backend
|
||||
with:
|
||||
path: backend
|
||||
key: ${{ runner.os }}-backend-${{ github.sha }}
|
||||
|
||||
- name: Run build backend server
|
||||
run: npm run buildbackend
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Cache build frontend
|
||||
uses: actions/cache@v2
|
||||
id: cache-build-frontend
|
||||
with:
|
||||
path: frontend
|
||||
key: ${{ runner.os }}-frontend-${{ github.sha }}
|
||||
|
||||
- name: Cache build backend
|
||||
uses: actions/cache@v2
|
||||
id: cache-build-backend
|
||||
with:
|
||||
path: backend
|
||||
key: ${{ runner.os }}-backend-${{ github.sha }}
|
||||
|
||||
- name: Compress files
|
||||
run: tar -czf /tmp/rtlbuild.tar.gz frontend backend rtl.js package.json package-lock.json
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: rtl-build-${{ github.event.release.tag_name }}
|
||||
path: /tmp/rtlbuild.tar.gz
|
@ -0,0 +1,52 @@
|
||||
name: Build docker images
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: [ 'v*' ]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Setup Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Set up version
|
||||
id: set-version
|
||||
run: |
|
||||
if [ "${{ github.event.inputs.version }}" != "" ]; then
|
||||
VERSION=${{ github.event.inputs.version }}
|
||||
elif [ "${{ github.ref_type }}" == "tag" ]; then
|
||||
VERSION=${{ github.ref_name }}
|
||||
else
|
||||
echo "No version provided and no tag found."
|
||||
exit 1
|
||||
fi
|
||||
echo "VERSION=$VERSION" >> $GITHUB_ENV
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
tags: |
|
||||
shahanafarooqui/rtl:${{ env.VERSION }}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "pwa-node",
|
||||
"request": "launch",
|
||||
"name": "Launch Program",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"program": "${workspaceFolder}/rtl.js"
|
||||
}
|
||||
]
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
{
|
||||
"eslint.enable": true,
|
||||
"eslint.validate": [
|
||||
"typescript",
|
||||
"HTML"
|
||||
],
|
||||
"eslint.options": {
|
||||
"configFile": ".eslintrc.json"
|
||||
},
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit"
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
FROM node:10-jessie-slim AS builder
|
||||
|
||||
ADD https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-armel /tini
|
||||
ADD https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-armel.asc /tini.asc
|
||||
RUN apt-get install gnupg
|
||||
RUN gpg --batch --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7 \
|
||||
&& gpg --batch --verify /tini.asc /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /RTL
|
||||
|
||||
COPY . /RTL
|
||||
|
||||
COPY package.json /RTL/package.json
|
||||
COPY package-lock.json /RTL/package-lock.json
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --only=prod
|
||||
|
||||
COPY . /RTL
|
||||
|
||||
FROM arm32v7/node:10-jessie-slim
|
||||
|
||||
WORKDIR /RTL
|
||||
|
||||
COPY --from=builder "/RTL" .
|
||||
COPY --from=builder "/tini" /sbin/tini
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "-g", "--"]
|
||||
|
||||
CMD ["node", "rtl"]
|
@ -1,29 +0,0 @@
|
||||
FROM node:10-stretch-slim AS builder
|
||||
|
||||
ADD https://github.com/krallin/tini/releases/download/v0.18.0/tini-static-arm64 /tini
|
||||
RUN chmod +x /tini
|
||||
|
||||
WORKDIR /RTL
|
||||
|
||||
COPY . /RTL
|
||||
|
||||
COPY package.json /RTL/package.json
|
||||
COPY package-lock.json /RTL/package-lock.json
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --only=prod
|
||||
|
||||
COPY . /RTL
|
||||
|
||||
FROM arm64v8/node:10-stretch-slim
|
||||
|
||||
WORKDIR /RTL
|
||||
|
||||
COPY --from=builder "/RTL" .
|
||||
COPY --from=builder "/tini" /sbin/tini
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENTRYPOINT ["/sbin/tini", "-g", "--"]
|
||||
|
||||
CMD ["node", "rtl"]
|
@ -0,0 +1,37 @@
|
||||
{
|
||||
"multiPass": "password",
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "C:\\Users\\xyz\\RTL",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
"logoutRedirectLink": ""
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"index": 1,
|
||||
"lnNode": "Node 1",
|
||||
"lnImplementation": "LND",
|
||||
"authentication": {
|
||||
"macaroonPath": "C:\\Users\\xyz\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet",
|
||||
"configPath": "C:\\Users\\xyz\\AppData\\Local\\Lnd\\lnd.conf",
|
||||
"swapMacaroonPath": "C:\\Users\\xyz\\AppData\\Local\\Loop\\mainnet",
|
||||
"boltzMacaroonPath": "C:\\Users\\xyz\\AppData\\Boltz\\mainnet"
|
||||
},
|
||||
"settings": {
|
||||
"userPersona": "MERCHANT",
|
||||
"themeMode": "DAY",
|
||||
"themeColor": "PURPLE",
|
||||
"channelBackupPath": "C:\\Users\\xyz\\backup\\node-1",
|
||||
"logLevel": "ERROR",
|
||||
"lnServerUrl": "https://127.0.0.1:8080",
|
||||
"swapServerUrl": "https://127.0.0.1:8081",
|
||||
"boltzServerUrl": "https://127.0.0.1:9003",
|
||||
"fiatConversion": false,
|
||||
"unannouncedChannels": false,
|
||||
"blockExplorerUrl": "https://mempool.space"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-hacker
|
@ -1,830 +0,0 @@
|
||||
@angular-devkit/build-angular
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Google, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
@angular/animations
|
||||
MIT
|
||||
|
||||
@angular/cdk
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@angular/common
|
||||
MIT
|
||||
|
||||
@angular/core
|
||||
MIT
|
||||
|
||||
@angular/flex-layout
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@angular/forms
|
||||
MIT
|
||||
|
||||
@angular/material
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@angular/material/button
|
||||
|
||||
@angular/material/card
|
||||
|
||||
@angular/material/checkbox
|
||||
|
||||
@angular/material/core
|
||||
|
||||
@angular/material/datepicker
|
||||
|
||||
@angular/material/dialog
|
||||
|
||||
@angular/material/divider
|
||||
|
||||
@angular/material/expansion
|
||||
|
||||
@angular/material/form-field
|
||||
|
||||
@angular/material/grid-list
|
||||
|
||||
@angular/material/icon
|
||||
|
||||
@angular/material/list
|
||||
|
||||
@angular/material/menu
|
||||
|
||||
@angular/material/paginator
|
||||
|
||||
@angular/material/progress-bar
|
||||
|
||||
@angular/material/progress-spinner
|
||||
|
||||
@angular/material/radio
|
||||
|
||||
@angular/material/select
|
||||
|
||||
@angular/material/sidenav
|
||||
|
||||
@angular/material/slide-toggle
|
||||
|
||||
@angular/material/snack-bar
|
||||
|
||||
@angular/material/sort
|
||||
|
||||
@angular/material/stepper
|
||||
|
||||
@angular/material/table
|
||||
|
||||
@angular/material/tabs
|
||||
|
||||
@angular/material/toolbar
|
||||
|
||||
@angular/material/tooltip
|
||||
|
||||
@angular/material/tree
|
||||
|
||||
@angular/platform-browser
|
||||
MIT
|
||||
|
||||
@angular/router
|
||||
MIT
|
||||
|
||||
@fortawesome/angular-fontawesome
|
||||
MIT
|
||||
|
||||
@fortawesome/fontawesome-svg-core
|
||||
MIT
|
||||
Font Awesome Free License
|
||||
-------------------------
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
|
||||
packaged as SVG and JS file types.
|
||||
|
||||
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
# Attribution
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
# Brand Icons
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
|
||||
|
||||
@fortawesome/free-regular-svg-icons
|
||||
(CC-BY-4.0 AND MIT)
|
||||
Font Awesome Free License
|
||||
-------------------------
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
|
||||
packaged as SVG and JS file types.
|
||||
|
||||
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
# Attribution
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
# Brand Icons
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
|
||||
|
||||
@fortawesome/free-solid-svg-icons
|
||||
(CC-BY-4.0 AND MIT)
|
||||
Font Awesome Free License
|
||||
-------------------------
|
||||
|
||||
Font Awesome Free is free, open source, and GPL friendly. You can use it for
|
||||
commercial projects, open source projects, or really almost whatever you want.
|
||||
Full Font Awesome Free license: https://fontawesome.com/license/free.
|
||||
|
||||
# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
|
||||
In the Font Awesome Free download, the CC BY 4.0 license applies to all icons
|
||||
packaged as SVG and JS file types.
|
||||
|
||||
# Fonts: SIL OFL 1.1 License (https://scripts.sil.org/OFL)
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
# Code: MIT License (https://opensource.org/licenses/MIT)
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
# Attribution
|
||||
Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
|
||||
Awesome Free files already contain embedded comments with sufficient
|
||||
attribution, so you shouldn't need to do anything additional when using these
|
||||
files normally.
|
||||
|
||||
We've kept attribution comments terse, so we ask that you do not actively work
|
||||
to remove them from files, especially code. They're a great way for folks to
|
||||
learn about Font Awesome.
|
||||
|
||||
# Brand Icons
|
||||
All brand icons are trademarks of their respective owners. The use of these
|
||||
trademarks does not indicate endorsement of the trademark holder by Font
|
||||
Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
|
||||
|
||||
@ngrx/effects
|
||||
MIT
|
||||
|
||||
@ngrx/store
|
||||
MIT
|
||||
|
||||
angular-user-idle
|
||||
MIT
|
||||
|
||||
angularx-qrcode
|
||||
MIT
|
||||
|
||||
convert-hex
|
||||
|
||||
convert-string
|
||||
|
||||
core-js
|
||||
MIT
|
||||
Copyright (c) 2014-2020 Denis Pushkarev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
dijkstrajs
|
||||
MIT
|
||||
```
|
||||
Dijkstra path-finding functions. Adapted from the Dijkstar Python project.
|
||||
|
||||
Copyright (C) 2008
|
||||
Wyatt Baldwin <self@wyattbaldwin.com>
|
||||
All rights reserved
|
||||
|
||||
Licensed under the MIT license.
|
||||
|
||||
http://www.opensource.org/licenses/mit-license.php
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
```
|
||||
|
||||
|
||||
hammerjs
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (C) 2011-2014 by Jorik Tangelder (Eight Media)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
isarray
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2013 Julian Gruber <julian@juliangruber.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
ngx-perfect-scrollbar
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016 Zef Oy
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
otplib
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Gerald Yeo <contact@fusedthought.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
perfect-scrollbar
|
||||
MIT
|
||||
The MIT License (MIT) Copyright (c) 2012-2017 Hyunje Jun and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
qrcode
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2012 Ryan Day
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
regenerator-runtime
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
resize-observer-polyfill
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Denis Rul
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
rxjs
|
||||
Apache-2.0
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright (c) 2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|
||||
|
||||
sha256
|
||||
|
||||
tslib
|
||||
Apache-2.0
|
||||
Apache License
|
||||
|
||||
Version 2.0, January 2004
|
||||
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
|
||||
zone.js
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2016-2018 Google, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -1,19 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RTL</title>
|
||||
<base href="/rtl/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/images/favicon-light/apple-touch-icon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="assets/images/favicon-light/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="assets/images/favicon-light/favicon-16x16.png">
|
||||
<link rel="manifest" href="assets/images/favicon-light/site.webmanifest">
|
||||
<link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="stylesheet" href="styles.807ae95794a05be97fba.css"></head>
|
||||
<body>
|
||||
<rtl-app></rtl-app>
|
||||
<script src="runtime.2cfa778f51a601dbdb7e.js" defer></script><script src="polyfills-es5.2ae7ace69949ec0a3f00.js" nomodule defer></script><script src="polyfills.3302e98effc5e50a54c2.js" defer></script><script src="main.4b92e283188428242458.js" defer></script></body>
|
||||
</html>
|
@ -1 +0,0 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"55621ac84bdf6c786e25",6:"3228fcaf0890e1d596e3",7:"6fecb498f36cf3efcace"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);
|
@ -1,97 +0,0 @@
|
||||
const path = require("path");
|
||||
const express = require("express");
|
||||
const bodyParser = require("body-parser");
|
||||
const cookieParser = require("cookie-parser");
|
||||
const common = require("./common");
|
||||
const app = express();
|
||||
|
||||
const baseHref = "/rtl/";
|
||||
const apiRoot = baseHref + "api/";
|
||||
const apiLNDRoot = baseHref + "api/lnd/";
|
||||
const apiCLRoot = baseHref + "api/cl/";
|
||||
|
||||
const authenticateRoutes = require("./routes/authenticate");
|
||||
const RTLConfRoutes = require("./routes/RTLConf");
|
||||
const infoRoutes = require("./routes/lnd/getInfo");
|
||||
const channelsRoutes = require("./routes/lnd/channels");
|
||||
const channelsBackupRoutes = require("./routes/lnd/channelsBackup");
|
||||
const peersRoutes = require("./routes/lnd/peers");
|
||||
const feesRoutes = require("./routes/lnd/fees");
|
||||
const balanceRoutes = require("./routes/lnd/balance");
|
||||
const walletRoutes = require("./routes/lnd/wallet");
|
||||
const graphRoutes = require("./routes/lnd/graph");
|
||||
const newAddressRoutes = require("./routes/lnd/newAddress");
|
||||
const transactionsRoutes = require("./routes/lnd/transactions");
|
||||
const payReqRoutes = require("./routes/lnd/payReq");
|
||||
const paymentsRoutes = require("./routes/lnd/payments");
|
||||
const invoiceRoutes = require("./routes/lnd/invoices");
|
||||
const switchRoutes = require("./routes/lnd/switch");
|
||||
const loopRoutes = require('./routes/lnd/loop');
|
||||
const messageRoutes = require("./routes/lnd/message");
|
||||
|
||||
const infoCLRoutes = require("./routes/c-lightning/getInfo");
|
||||
const feesCLRoutes = require("./routes/c-lightning/fees");
|
||||
const balanceCLRoutes = require("./routes/c-lightning/balance");
|
||||
const channelsCLRoutes = require("./routes/c-lightning/channels");
|
||||
const invoicesCLRoutes = require("./routes/c-lightning/invoices");
|
||||
const onChainCLRoutes = require("./routes/c-lightning/onchain");
|
||||
const paymentsCLRoutes = require("./routes/c-lightning/payments");
|
||||
const peersCLRoutes = require("./routes/c-lightning/peers");
|
||||
const networkCLRoutes = require("./routes/c-lightning/network");
|
||||
const messageCLRoutes = require("./routes/c-lightning/message");
|
||||
|
||||
app.use(cookieParser(common.secret_key));
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
app.use(baseHref, express.static(path.join(__dirname, "angular")));
|
||||
|
||||
// CORS fix, Only required for developement due to separate backend and frontend servers
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept, Authorization, filePath"
|
||||
);
|
||||
res.setHeader(
|
||||
"Access-Control-Allow-Methods",
|
||||
"GET, POST, PATCH, PUT, DELETE, OPTIONS"
|
||||
);
|
||||
next();
|
||||
});
|
||||
// CORS fix, Only required for developement due to separate backend and frontend servers
|
||||
|
||||
app.use(apiRoot + "authenticate", authenticateRoutes);
|
||||
app.use(apiRoot + "conf", RTLConfRoutes);
|
||||
app.use(apiLNDRoot + "getinfo", infoRoutes);
|
||||
app.use(apiLNDRoot + "channels", channelsRoutes);
|
||||
app.use(apiLNDRoot + "channels/backup", channelsBackupRoutes);
|
||||
app.use(apiLNDRoot + "peers", peersRoutes);
|
||||
app.use(apiLNDRoot + "fees", feesRoutes);
|
||||
app.use(apiLNDRoot + "balance", balanceRoutes);
|
||||
app.use(apiLNDRoot + "wallet", walletRoutes);
|
||||
app.use(apiLNDRoot + "network", graphRoutes);
|
||||
app.use(apiLNDRoot + "newaddress", newAddressRoutes);
|
||||
app.use(apiLNDRoot + "transactions", transactionsRoutes);
|
||||
app.use(apiLNDRoot + "payreq", payReqRoutes);
|
||||
app.use(apiLNDRoot + "payments", paymentsRoutes);
|
||||
app.use(apiLNDRoot + "invoices", invoiceRoutes);
|
||||
app.use(apiLNDRoot + "switch", switchRoutes);
|
||||
app.use(apiLNDRoot + "loop", loopRoutes);
|
||||
app.use(apiLNDRoot + "message", messageRoutes);
|
||||
|
||||
app.use(apiCLRoot + "getinfo", infoCLRoutes);
|
||||
app.use(apiCLRoot + "fees", feesCLRoutes);
|
||||
app.use(apiCLRoot + "balance", balanceCLRoutes);
|
||||
app.use(apiCLRoot + "channels", channelsCLRoutes);
|
||||
app.use(apiCLRoot + "invoices", invoicesCLRoutes);
|
||||
app.use(apiCLRoot + "onchain", onChainCLRoutes);
|
||||
app.use(apiCLRoot + "payments", paymentsCLRoutes);
|
||||
app.use(apiCLRoot + "peers", peersCLRoutes);
|
||||
app.use(apiCLRoot + "network", networkCLRoutes);
|
||||
app.use(apiCLRoot + "message", messageCLRoutes);
|
||||
|
||||
app.use((req, res, next) => {
|
||||
res.sendFile(path.join(__dirname, "angular", "index.html"));
|
||||
});
|
||||
|
||||
module.exports = app;
|
@ -0,0 +1,116 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { getAlias } from './network.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const listPeerChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeerchannels';
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels });
|
||||
return Promise.all(body.channels?.map((channel) => {
|
||||
channel.to_them_msat = channel.total_msat - channel.to_us_msat;
|
||||
channel.balancedness = (channel.total_msat === 0) ? 1 : (1 - Math.abs((channel.to_us_msat - (channel.total_msat - channel.to_us_msat)) / channel.total_msat)).toFixed(3);
|
||||
return getAlias(req.session.selectedNode, channel, 'peer_id');
|
||||
})).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels });
|
||||
return res.status(200).json(body.channels || []);
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Peer Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const openChannel = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/fundchannel';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Opened', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Open Channel Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const setChannelFee = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Setting Channel Fee..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/setchannel';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Updated Channel Policy', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Update Channel Policy Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const closeChannel = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
||||
req.setTimeout(60000 * 10); // timeout 10 mins
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/close';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel', data: options.url });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body });
|
||||
res.status(204).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Close Channel Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listForwards = (req, res, next) => {
|
||||
const { status } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listforwards';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + status, data: body });
|
||||
body.forwards = !body.forwards ? [] : (status === 'failed' || status === 'local_failed') ? body.forwards.slice(Math.max(0, body.forwards.length - 1000), Math.max(1000, body.forwards.length)).reverse() : body.forwards.reverse();
|
||||
res.status(200).json(body.forwards);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const funderUpdatePolicy = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting or Updating Funder Policy..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/funderupdate';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,60 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { CLWSClient } from './webSocketClient.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
const clWsClient = CLWSClient;
|
||||
export const getInfo = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Core Lightning Node Information..' });
|
||||
common.logEnvVariables(req);
|
||||
common.setOptions(req);
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/getinfo';
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.lnNode });
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Calling Info from Core Lightning server url ' + options.url });
|
||||
if (!options.headers || !options.headers.rune) {
|
||||
const errMsg = 'Core lightning get info failed due to missing rune!';
|
||||
const err = common.handleError({ statusCode: 502, message: 'Bad rune', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
else {
|
||||
return request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'GetInfo', msg: 'Node Information Before Update', data: body });
|
||||
body.lnImplementation = 'Core Lightning';
|
||||
const chainObj = { chain: '', network: '' };
|
||||
if (body.network.includes('litecoin') || body.network.includes('feathercoin')) {
|
||||
chainObj.chain = '';
|
||||
chainObj.network = '';
|
||||
}
|
||||
else if (body.network.includes('liquid')) {
|
||||
chainObj.chain = 'Liquid';
|
||||
chainObj.network = common.titleCase(body.network);
|
||||
}
|
||||
else {
|
||||
chainObj.chain = 'Bitcoin';
|
||||
chainObj.network = common.titleCase(body.network);
|
||||
}
|
||||
body.chains = [chainObj];
|
||||
body.uris = [];
|
||||
if (body.address && body.address.length > 0) {
|
||||
body.address.forEach((addr) => {
|
||||
body.uris.push(body.id + '@' + addr.address + ':' + addr.port);
|
||||
});
|
||||
}
|
||||
req.session.selectedNode.lnVersion = body.version || '';
|
||||
req.session.selectedNode.api_version = body.api_version || '';
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Core Lightning\'s Websocket Server.' });
|
||||
clWsClient.updateSelectedNode(req.session.selectedNode);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
|
||||
return res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,55 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const deleteExpiredInvoice = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Deleting Expired Invoices..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/delexpiredinvoice';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body });
|
||||
res.status(204).json({ status: 'Invoice Deleted Successfully' });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listInvoices = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting Invoices..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listinvoices';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List URL', data: options.url });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoice', 'List Invoices Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const addInvoice = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/invoice';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoice', 'Add Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,99 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getRoute = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/getroute';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Routes Received', data: body });
|
||||
return Promise.all(body.route?.map((rt) => getAlias(req.session.selectedNode, rt, 'id'))).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Network Routes with Alias Received', data: body });
|
||||
res.status(200).json(body || []);
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Network', 'Query Routes Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listchannels';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup Finished', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Network', 'Channel Lookup Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const feeRates = (req, res, next) => {
|
||||
const { style } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Fee Rates..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/feerates';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Fee Rates Received for ' + style, data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Network', 'Fee Rates Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listNodes = (req, res, next) => {
|
||||
const filter_liquidity_ads = !!req.body.liquidity_ads;
|
||||
delete req.body.liquidity_ads;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listnodes';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Network', msg: 'List Nodes URL' + options.url });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
|
||||
let response = body.nodes;
|
||||
if (filter_liquidity_ads) {
|
||||
response = body.nodes.filter((node) => ((node.option_will_fund) ? node : null));
|
||||
}
|
||||
res.status(200).json(response);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Network', 'Node Lookup Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const getAlias = (selNode, peer, id) => {
|
||||
options.url = selNode.settings.lnServerUrl + '/v1/listnodes';
|
||||
if (!peer[id]) {
|
||||
logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Network', msg: 'Empty Peer ID' });
|
||||
peer.alias = '';
|
||||
return peer;
|
||||
}
|
||||
options.body = { id: peer[id] };
|
||||
return request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Network', msg: 'Peer Alias Finished', data: body });
|
||||
peer.alias = body.nodes[0] && body.nodes[0].alias ? body.nodes[0].alias : peer[id].substring(0, 20);
|
||||
return peer;
|
||||
}).catch((errRes) => {
|
||||
common.handleError(errRes, 'Network', 'Peer Alias Error', selNode);
|
||||
peer.alias = peer[id].substring(0, 20);
|
||||
return peer;
|
||||
});
|
||||
};
|
@ -0,0 +1,95 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { Database } from '../../utils/database.js';
|
||||
import { CollectionFieldsEnum, CollectionsEnum } from '../../models/database.model.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
const databaseService = Database;
|
||||
export const listOfferBookmarks = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Bookmarks..' });
|
||||
databaseService.find(req.session.selectedNode, CollectionsEnum.OFFERS).then((offers) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmarks Received', data: offers });
|
||||
res.status(200).json(offers);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Offers', 'Offer Bookmarks Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const deleteOfferBookmark = (req, res, next) => {
|
||||
const { offer_str } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' });
|
||||
databaseService.remove(req.session.selectedNode, CollectionsEnum.OFFERS, CollectionFieldsEnum.BOLT12, offer_str).then((deleteRes) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: deleteRes });
|
||||
res.status(204).json(offer_str);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Offers', 'Offer Bookmark Delete Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listOffers = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offers..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listoffers';
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offers List URL', data: options.url });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offers List Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Offers', 'List Offers Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const createOffer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Creating Offer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/offer';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Created', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Offer', 'Create Offer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const fetchOfferInvoice = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/fetchinvoice';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Invoice Body', data: options.body });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Invoice Received', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Offers', 'Get Offer Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const disableOffer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Disabling Offer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableOffer';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
|
||||
res.status(202).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Offers', 'Disable Offer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,91 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getNewAddress = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Generating New Address..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/newaddr';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'New Address Generated', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'New Address Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const onChainWithdraw = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Withdrawing from On Chain..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/withdraw';
|
||||
options.body = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'OnChain', msg: 'OnChain Withdraw Options', data: options.body });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Withdraw Finished', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'Withdraw Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const getUTXOs = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Listing Funds..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listfunds';
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Funds List Received', data: body });
|
||||
// Local Remote Balance Calculation
|
||||
let lrBalance = { localBalance: 0, remoteBalance: 0, inactiveBalance: 0, pendingBalance: 0 };
|
||||
body.channels.forEach((channel) => {
|
||||
if ((channel.state === 'CHANNELD_NORMAL') && channel.connected === true) {
|
||||
lrBalance.localBalance = lrBalance.localBalance + channel.our_amount_msat;
|
||||
lrBalance.remoteBalance = lrBalance.remoteBalance + (channel.amount_msat - channel.our_amount_msat);
|
||||
}
|
||||
else if ((channel.state === 'CHANNELD_NORMAL') && channel.connected === false) {
|
||||
lrBalance.inactiveBalance = lrBalance.inactiveBalance + channel.our_amount_msat;
|
||||
}
|
||||
else if (channel.state === 'CHANNELD_AWAITING_LOCKIN' || channel.state === 'DUALOPEND_AWAITING_LOCKIN') {
|
||||
lrBalance.pendingBalance = lrBalance.pendingBalance + channel.our_amount_msat;
|
||||
}
|
||||
});
|
||||
lrBalance = {
|
||||
localBalance: lrBalance.localBalance / 1000,
|
||||
remoteBalance: lrBalance.remoteBalance / 1000,
|
||||
inactiveBalance: lrBalance.inactiveBalance / 1000,
|
||||
pendingBalance: lrBalance.pendingBalance / 1000
|
||||
};
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Onchain', msg: 'Local Remote Balance', data: lrBalance });
|
||||
// Onchain Balance Calculation
|
||||
let onchainBalance = { totalBalance: 0, confBalance: 0, unconfBalance: 0 };
|
||||
body.outputs.forEach((output) => {
|
||||
if (output.status === 'confirmed') {
|
||||
onchainBalance.confBalance = onchainBalance.confBalance + output.amount_msat;
|
||||
}
|
||||
else if (output.status === 'unconfirmed') {
|
||||
onchainBalance.unconfBalance = onchainBalance.unconfBalance + output.amount_msat;
|
||||
}
|
||||
});
|
||||
onchainBalance = {
|
||||
totalBalance: onchainBalance.confBalance / 1000,
|
||||
confBalance: (onchainBalance.confBalance - onchainBalance.unconfBalance) / 1000,
|
||||
unconfBalance: onchainBalance.unconfBalance / 1000
|
||||
};
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Onchain', msg: 'Onchain Balance Received', data: onchainBalance });
|
||||
res.status(200).json({ utxos: body.outputs || [], balance: onchainBalance, localRemoteBalance: lrBalance });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'List Funds Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,188 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { Database } from '../../utils/database.js';
|
||||
import { CollectionFieldsEnum, CollectionsEnum } from '../../models/database.model.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
const databaseService = Database;
|
||||
export const getMemo = (selNode, payment) => {
|
||||
options.url = selNode.settings.lnServerUrl + '/v1/decode';
|
||||
options.body = { string: payment.bolt11 };
|
||||
return request.post(options).then((res) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment Decode Received', data: res });
|
||||
payment.memo = res.description || '';
|
||||
return payment;
|
||||
}).catch((err) => {
|
||||
payment.memo = '';
|
||||
return payment;
|
||||
});
|
||||
};
|
||||
function paymentReducer(accumulator, currentPayment) {
|
||||
const currPayHash = currentPayment.payment_hash;
|
||||
if (!currentPayment.partid) {
|
||||
currentPayment.partid = 0;
|
||||
}
|
||||
if (!accumulator[currPayHash]) {
|
||||
accumulator[currPayHash] = [currentPayment];
|
||||
}
|
||||
else {
|
||||
accumulator[currPayHash].push(currentPayment);
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
function summaryReducer(accumulator, mpp) {
|
||||
if (mpp.status === 'complete') {
|
||||
accumulator.amount_msat = accumulator.amount_msat + mpp.amount_msat;
|
||||
accumulator.amount_sent_msat = accumulator.amount_sent_msat + mpp.amount_sent_msat;
|
||||
accumulator.status = mpp.status;
|
||||
}
|
||||
if (mpp.bolt11) {
|
||||
accumulator.bolt11 = mpp.bolt11;
|
||||
}
|
||||
if (mpp.bolt12) {
|
||||
accumulator.bolt12 = mpp.bolt12;
|
||||
}
|
||||
return accumulator;
|
||||
}
|
||||
function groupBy(payments) {
|
||||
const paymentsInGroups = payments?.reduce(paymentReducer, {});
|
||||
const paymentsGrpArray = Object.keys(paymentsInGroups)?.map((key) => ((paymentsInGroups[key].length && paymentsInGroups[key].length > 1) ? common.sortDescByKey(paymentsInGroups[key], 'partid') : paymentsInGroups[key]));
|
||||
return paymentsGrpArray?.reduce((acc, curr) => {
|
||||
let temp = {};
|
||||
if (curr.length && curr.length === 1) {
|
||||
temp = JSON.parse(JSON.stringify(curr[0]));
|
||||
temp.is_group = false;
|
||||
temp.is_expanded = false;
|
||||
temp.total_parts = 1;
|
||||
delete temp.partid;
|
||||
}
|
||||
else {
|
||||
const paySummary = curr?.reduce(summaryReducer, { amount_msat: 0, amount_sent_msat: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
|
||||
temp = {
|
||||
is_group: true, is_expanded: false, total_parts: (curr.length ? curr.length : 0), status: paySummary.status, payment_hash: curr[0].payment_hash,
|
||||
destination: curr[0].destination, amount_msat: paySummary.amount_msat, amount_sent_msat: paySummary.amount_sent_msat, created_at: curr[0].created_at,
|
||||
mpps: curr
|
||||
};
|
||||
if (paySummary.bolt11) {
|
||||
temp.bolt11 = paySummary.bolt11;
|
||||
}
|
||||
if (paySummary.bolt12) {
|
||||
temp.bolt12 = paySummary.bolt12;
|
||||
}
|
||||
}
|
||||
return acc.concat(temp);
|
||||
}, []);
|
||||
}
|
||||
export const listPayments = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'List Payments..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listsendpays';
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body.payments });
|
||||
body.payments = body.payments && body.payments.length && body.payments.length > 0 ? groupBy(body.payments) : [];
|
||||
return Promise.all(body.payments?.map((payment) => ((payment.bolt11) ? getMemo(req.session.selectedNode, payment) : (payment.memo = '')))).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payments List with Memo Received', data: body.payments });
|
||||
res.status(200).json(body.payments);
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const postPayment = (req, res, next) => {
|
||||
const { paymentType, saveToDB, bolt12, zeroAmtOffer, amount_msat, title, issuer, description } = req.body;
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
const options_body = JSON.parse(JSON.stringify(req.body));
|
||||
if (paymentType === 'KEYSEND') {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Keysend Payment..' });
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/keysend';
|
||||
delete options_body.uiMessage;
|
||||
delete options_body.fromDialog;
|
||||
delete options_body.paymentType;
|
||||
delete options_body.title;
|
||||
delete options_body.issuer;
|
||||
delete options_body.bolt11;
|
||||
delete options_body.description;
|
||||
delete options_body.bolt12;
|
||||
delete options_body.zeroAmtOffer;
|
||||
delete options_body.pubkey;
|
||||
delete options_body.riskfactor;
|
||||
delete options_body.localinvreqid;
|
||||
delete options_body.exclude;
|
||||
delete options_body.maxfee;
|
||||
delete options_body.saveToDB;
|
||||
options.body = options_body;
|
||||
}
|
||||
else {
|
||||
if (paymentType === 'OFFER') {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' });
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' });
|
||||
}
|
||||
if (paymentType === 'OFFER') {
|
||||
// delete amount for zero amt offer also as fetchinvoice already has amount information
|
||||
delete options_body.amount_msat;
|
||||
}
|
||||
delete options_body.uiMessage;
|
||||
delete options_body.fromDialog;
|
||||
delete options_body.paymentType;
|
||||
delete options_body.destination;
|
||||
delete options_body.extratlvs;
|
||||
delete options_body.title;
|
||||
delete options_body.issuer;
|
||||
delete options_body.bolt12;
|
||||
delete options_body.zeroAmtOffer;
|
||||
delete options_body.pubkey;
|
||||
delete options_body.saveToDB;
|
||||
options.body = options_body;
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/pay';
|
||||
}
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body });
|
||||
if (paymentType === 'OFFER') {
|
||||
if (saveToDB && bolt12) {
|
||||
const offerToUpdate = { bolt12: bolt12, amountMSat: (zeroAmtOffer ? 0 : amount_msat), title: title, lastUpdatedAt: new Date(Date.now()).getTime() };
|
||||
if (issuer) {
|
||||
offerToUpdate['issuer'] = issuer;
|
||||
}
|
||||
if (description) {
|
||||
offerToUpdate['description'] = description;
|
||||
}
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
return databaseService.validateDocument(CollectionsEnum.OFFERS, offerToUpdate).then((validated) => {
|
||||
return databaseService.update(req.session.selectedNode, CollectionsEnum.OFFERS, offerToUpdate, CollectionFieldsEnum.BOLT12, bolt12).then((updatedOffer) => {
|
||||
logger.log({ level: 'DEBUG', fileName: 'Payments', msg: 'Offer Updated', data: updatedOffer });
|
||||
return res.status(201).json({ paymentResponse: body, saveToDBResponse: updatedOffer });
|
||||
}).catch((errDB) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB update error', error: errDB });
|
||||
return res.status(201).json({ paymentResponse: body, saveToDBError: errDB });
|
||||
});
|
||||
}).catch((errValidation) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Payments', msg: 'Offer DB validation error', error: errValidation });
|
||||
return res.status(201).json({ paymentResponse: body, saveToDBError: errValidation });
|
||||
});
|
||||
}
|
||||
else {
|
||||
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
|
||||
}
|
||||
}
|
||||
if (paymentType === 'INVOICE') {
|
||||
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
|
||||
}
|
||||
if (paymentType === 'KEYSEND') {
|
||||
return res.status(201).json(body);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,67 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { getAlias } from './network.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getPeers = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'List Peers..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeers';
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
|
||||
const peers = !body.peers ? [] : body.peers;
|
||||
return Promise.all(peers?.map((peer) => getAlias(req.session.selectedNode, peer, 'id'))).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body.peers });
|
||||
res.status(200).json(body.peers || []);
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const postPeer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Connecting Peer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/connect';
|
||||
options.body = req.body;
|
||||
request.post(options).then((connectRes) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: connectRes });
|
||||
const listOptions = common.getOptions(req);
|
||||
listOptions.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listpeers';
|
||||
request.post(listOptions).then((listPeersRes) => {
|
||||
const peers = listPeersRes && listPeersRes.peers ? common.newestOnTop(listPeersRes.peers, 'id', connectRes.id) : [];
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
|
||||
res.status(201).json(peers);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'Connect Peer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'Connect Peer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const deletePeer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Disconnecting Peer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disconnect';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body });
|
||||
res.status(204).json({});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'Detach Peer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,69 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const decodePayment = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payment..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/decode';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Decoded', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'Decode Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const signMessage = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Signing Message..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/signmessage';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Signed', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Message', 'Sign Message Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const verifyMessage = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Verifying Message..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/checkmessage';
|
||||
options.body = req.body;
|
||||
request.post(options, (error, response, body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Verified', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Message', 'Verify Message Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listConfigs = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/listconfigs';
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Utility', 'List Configs Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,117 @@
|
||||
import socketIOClient from 'socket.io-client';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { WSServer } from '../../utils/webSocketServer.js';
|
||||
export class CLWebSocketClient {
|
||||
constructor() {
|
||||
this.logger = Logger;
|
||||
this.common = Common;
|
||||
this.wsServer = WSServer;
|
||||
this.webSocketClients = [];
|
||||
this.reconnectTimeOut = null;
|
||||
this.waitTime = 0.5;
|
||||
this.reconnect = (clWsClt) => {
|
||||
if (this.reconnectTimeOut) {
|
||||
return;
|
||||
}
|
||||
this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2);
|
||||
this.reconnectTimeOut = setTimeout(() => {
|
||||
if (clWsClt.selectedNode) {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Reconnecting to the Core Lightning\'s Websocket Server..' });
|
||||
this.connect(clWsClt.selectedNode);
|
||||
}
|
||||
this.reconnectTimeOut = null;
|
||||
}, this.waitTime * 1000);
|
||||
};
|
||||
this.connect = (selectedNode) => {
|
||||
try {
|
||||
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
|
||||
if (!clientExists) {
|
||||
if (selectedNode.settings.lnServerUrl) {
|
||||
const newWebSocketClient = { selectedNode: selectedNode, reConnect: true, webSocketClient: null };
|
||||
this.connectWithClient(newWebSocketClient);
|
||||
this.webSocketClients.push(newWebSocketClient);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((!clientExists.webSocketClient || !clientExists.webSocketClient.connected) && selectedNode.settings.lnServerUrl) {
|
||||
clientExists.reConnect = true;
|
||||
this.connectWithClient(clientExists);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
this.connectWithClient = (clWsClt) => {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' });
|
||||
try {
|
||||
if (!clWsClt.selectedNode.authentication.runeValue) {
|
||||
clWsClt.selectedNode.authentication.runeValue = this.common.getRuneValue(clWsClt.selectedNode.authentication.runePath);
|
||||
}
|
||||
clWsClt.webSocketClient = socketIOClient(clWsClt.selectedNode.settings.lnServerUrl, {
|
||||
extraHeaders: { rune: clWsClt.selectedNode.authentication.runeValue },
|
||||
transports: ['websocket'],
|
||||
secure: true,
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
clWsClt.webSocketClient.on('connect', () => {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' });
|
||||
this.waitTime = 0.5;
|
||||
});
|
||||
clWsClt.webSocketClient.on('disconnect', (reason) => {
|
||||
if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.lnImplementation === 'CLN') {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...', data: reason });
|
||||
clWsClt.webSocketClient.close();
|
||||
if (clWsClt.reConnect) {
|
||||
this.reconnect(clWsClt);
|
||||
}
|
||||
}
|
||||
});
|
||||
clWsClt.webSocketClient.on('message', (msg) => {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data });
|
||||
this.wsServer.sendEventsToAllLNClients(JSON.stringify({ source: 'CLN', data: msg }), clWsClt.selectedNode);
|
||||
});
|
||||
clWsClt.webSocketClient.on('error', (err) => {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', error: err });
|
||||
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
|
||||
this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
|
||||
clWsClt.webSocketClient.close();
|
||||
if (clWsClt.reConnect) {
|
||||
this.reconnect(clWsClt);
|
||||
}
|
||||
});
|
||||
};
|
||||
this.disconnect = (selectedNode) => {
|
||||
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
|
||||
if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.connected) {
|
||||
this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the Core Lightning\'s Websocket Server..' });
|
||||
clientExists.reConnect = false;
|
||||
clientExists.webSocketClient.close();
|
||||
const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index);
|
||||
this.webSocketClients.splice(clientIdx, 1);
|
||||
}
|
||||
};
|
||||
this.updateSelectedNode = (newSelectedNode) => {
|
||||
const clientIdx = this.webSocketClients.findIndex((wsc) => +wsc.selectedNode.index === +newSelectedNode.index);
|
||||
let newClient = this.webSocketClients[clientIdx];
|
||||
if (!newClient) {
|
||||
newClient = { selectedNode: null, reConnect: true, webSocketClient: null };
|
||||
}
|
||||
newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
|
||||
this.webSocketClients[clientIdx] = newClient;
|
||||
};
|
||||
this.wsServer.eventEmitterCLN.on('CONNECT', (nodeIndex) => {
|
||||
this.connect(this.common.findNode(+nodeIndex));
|
||||
});
|
||||
this.wsServer.eventEmitterCLN.on('DISCONNECT', (nodeIndex) => {
|
||||
this.disconnect(this.common.findNode(+nodeIndex));
|
||||
});
|
||||
}
|
||||
}
|
||||
export const CLWSClient = new CLWebSocketClient();
|
@ -0,0 +1,210 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { createInvoiceRequestCall, listPendingInvoicesRequestCall } from './invoices.js';
|
||||
import { findRouteBetweenNodesRequestCall } from './network.js';
|
||||
import { getSentInfoFromPaymentRequest, sendPaymentToRouteRequestCall } from './payments.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const simplifyAllChannels = (selNode, channels) => {
|
||||
let channelNodeIds = '';
|
||||
const simplifiedChannels = [];
|
||||
channels.forEach((channel) => {
|
||||
channelNodeIds = channelNodeIds + ',' + channel.nodeId;
|
||||
simplifiedChannels.push({
|
||||
nodeId: channel.nodeId ? channel.nodeId : '',
|
||||
channelId: channel.channelId ? channel.channelId : '',
|
||||
state: channel.state ? channel.state : '',
|
||||
announceChannel: channel.data && channel.data.commitments && channel.data.commitments.params && channel.data.commitments.params.channelFlags && channel.data.commitments.params.channelFlags.announceChannel ? channel.data.commitments.params.channelFlags.announceChannel : false,
|
||||
toLocal: (channel.data.commitments.active[0].localCommit.spec.toLocal) ? Math.round(+channel.data.commitments.active[0].localCommit.spec.toLocal / 1000) : 0,
|
||||
toRemote: (channel.data.commitments.active[0].localCommit.spec.toRemote) ? Math.round(+channel.data.commitments.active[0].localCommit.spec.toRemote / 1000) : 0,
|
||||
shortChannelId: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.shortChannelId ? channel.data.channelUpdate.shortChannelId : '',
|
||||
isInitiator: channel.data && channel.data.commitments && channel.data.commitments.params && channel.data.commitments.params.localParams && channel.data.commitments.params.localParams.isInitiator ? channel.data.commitments.params.localParams.isInitiator : false,
|
||||
feeBaseMsat: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.feeBaseMsat ? channel.data.channelUpdate.feeBaseMsat : 0,
|
||||
feeProportionalMillionths: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.feeProportionalMillionths ? channel.data.channelUpdate.feeProportionalMillionths : 0,
|
||||
alias: ''
|
||||
});
|
||||
});
|
||||
channelNodeIds = channelNodeIds.substring(1);
|
||||
options.url = selNode.settings.lnServerUrl + '/nodes';
|
||||
options.form = { nodeIds: channelNodeIds };
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Node Ids to find alias', data: channelNodeIds });
|
||||
return request.post(options).then((nodes) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
|
||||
let foundPeer = null;
|
||||
simplifiedChannels?.map((channel) => {
|
||||
foundPeer = nodes.find((channelWithAlias) => channel.nodeId === channelWithAlias.nodeId);
|
||||
channel.alias = foundPeer ? foundPeer.alias : channel.nodeId.substring(0, 20);
|
||||
return channel;
|
||||
});
|
||||
return simplifiedChannels;
|
||||
}).catch((err) => simplifiedChannels);
|
||||
};
|
||||
export const getChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'List Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/channels';
|
||||
options.form = {};
|
||||
if (req.query && req.query.nodeId) {
|
||||
options.form = req.query;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Channels Node Id', data: options.form });
|
||||
}
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Options', data: options });
|
||||
if (common.read_dummy_data) {
|
||||
common.getDummyData('Channels', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(data); });
|
||||
}
|
||||
else {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Channels List Received', data: body });
|
||||
if (body && body.length) {
|
||||
return simplifyAllChannels(req.session.selectedNode, body).then((simplifiedChannels) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Simplified Channels with Alias Received', data: simplifiedChannels });
|
||||
res.status(200).json(simplifiedChannels);
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Empty Channels List Received' });
|
||||
res.status(200).json([]);
|
||||
}
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
||||
export const getChannelStats = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel States..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/channelstats';
|
||||
const today = new Date(Date.now());
|
||||
const tillToday = (Math.round(today.getTime() / 1000)).toString();
|
||||
const fromLastMonth = (Math.round(new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime() / 1000)).toString();
|
||||
options.form = {
|
||||
from: fromLastMonth,
|
||||
to: tillToday
|
||||
};
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel States Received', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Get Channel Stats Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const openChannel = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/open';
|
||||
options.form = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Params', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Opened', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Open Channel Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const updateChannelRelayFee = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Updating Channel Relay Fee..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/updaterelayfee';
|
||||
options.form = req.query;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Relay Fee Params', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Relay Fee Updated', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Update Relay Fee Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const closeChannel = (req, res, next) => {
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
if (req.query.force !== 'true') {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/close';
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Force Closing Channel..' });
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/forceclose';
|
||||
}
|
||||
options.form = { channelId: req.query.channelId };
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Close URL', data: options.url });
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Close Params', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body });
|
||||
res.status(204).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Close Channel Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const circularRebalance = (req, res, next) => {
|
||||
const { amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds, format, sourceShortChannelId, targetShortChannelId } = req.body;
|
||||
const crInvDescription = 'Circular rebalancing invoice for ' + (amountMsat / 1000) + ' Sats';
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.form = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Rebalance Params', data: options.form });
|
||||
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
|
||||
// Check if unpaid Invoice exists already
|
||||
listPendingInvoicesRequestCall(req.session.selectedNode).then((callRes) => {
|
||||
const foundExistingInvoice = callRes.find((inv) => inv.description.includes(crInvDescription) && inv.amount === amountMsat && inv.expiry && inv.timestamp && ((inv.expiry + inv.timestamp) >= tillToday));
|
||||
// Create new invoice if doesn't exist already
|
||||
const requestCalls = foundExistingInvoice && foundExistingInvoice.serialized ?
|
||||
[findRouteBetweenNodesRequestCall(req.session.selectedNode, amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds, format)] :
|
||||
[findRouteBetweenNodesRequestCall(req.session.selectedNode, amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds, format), createInvoiceRequestCall(req.session.selectedNode, crInvDescription, amountMsat)];
|
||||
Promise.all(requestCalls).then((values) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const routes = values[0]?.routes?.filter((route) => {
|
||||
return !((route.shortChannelIds[0] === sourceShortChannelId && route.shortChannelIds[1] === targetShortChannelId) ||
|
||||
(route.shortChannelIds[1] === sourceShortChannelId && route.shortChannelIds[0] === targetShortChannelId));
|
||||
});
|
||||
const firstRoute = routes[0].shortChannelIds.join() || '';
|
||||
const shortChannelIds = sourceShortChannelId + ',' + firstRoute + ',' + targetShortChannelId;
|
||||
const invoice = (foundExistingInvoice && foundExistingInvoice.serialized ? foundExistingInvoice.serialized : (values[1] ? values[1].serialized : '')) || '';
|
||||
const paymentHash = (foundExistingInvoice && foundExistingInvoice.paymentHash ? foundExistingInvoice.paymentHash : (values[1] ? values[1].paymentHash : '') || '');
|
||||
return sendPaymentToRouteRequestCall(req.session.selectedNode, shortChannelIds, invoice, amountMsat).then((payToRouteCallRes) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
setTimeout(() => {
|
||||
return getSentInfoFromPaymentRequest(req.session.selectedNode, paymentHash).then((sentInfoCallRes) => {
|
||||
const payStatus = sentInfoCallRes.length && sentInfoCallRes.length > 0 ? sentInfoCallRes[sentInfoCallRes.length - 1].status : sentInfoCallRes;
|
||||
return res.status(201).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentRoute: shortChannelIds, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: payStatus });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Sent Info Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentRoute: shortChannelIds, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: { error: err.error } });
|
||||
});
|
||||
}, 3000);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Send Payment To Route Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentRoute: shortChannelIds, paymentHash: paymentHash, paymentDetails: {}, paymentStatus: { error: err.error } });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Find Routes Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: (foundExistingInvoice.serialized || ''), paymentRoute: '', paymentHash: '', paymentDetails: {}, paymentStatus: { error: err.error } });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From List Pending Invoices Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: false, invoice: '', paymentRoute: '', paymentHash: '', paymentDetails: {}, paymentStatus: { error: err.error } });
|
||||
});
|
||||
};
|
@ -0,0 +1,152 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const arrangeFees = (selNode, body, current_time) => {
|
||||
const fees = { daily_fee: 0, daily_txs: 0, weekly_fee: 0, weekly_txs: 0, monthly_fee: 0, monthly_txs: 0 };
|
||||
const week_start_time = current_time - 604800000;
|
||||
const day_start_time = current_time - 86400000;
|
||||
let fee = 0;
|
||||
body.relayed.forEach((relayedEle) => {
|
||||
fee = Math.round((relayedEle.amountIn - relayedEle.amountOut) / 1000);
|
||||
const relayedEleTimestamp = relayedEle.settledAt ? relayedEle.settledAt : relayedEle.timestamp;
|
||||
if (relayedEleTimestamp) {
|
||||
if (relayedEleTimestamp.unix) {
|
||||
if ((relayedEleTimestamp.unix * 1000) >= day_start_time) {
|
||||
fees.daily_fee = fees.daily_fee + fee;
|
||||
fees.daily_txs = fees.daily_txs + 1;
|
||||
}
|
||||
if ((relayedEleTimestamp.unix * 1000) >= week_start_time) {
|
||||
fees.weekly_fee = fees.weekly_fee + fee;
|
||||
fees.weekly_txs = fees.weekly_txs + 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (relayedEleTimestamp >= day_start_time) {
|
||||
fees.daily_fee = fees.daily_fee + fee;
|
||||
fees.daily_txs = fees.daily_txs + 1;
|
||||
}
|
||||
if (relayedEleTimestamp >= week_start_time) {
|
||||
fees.weekly_fee = fees.weekly_fee + fee;
|
||||
fees.weekly_txs = fees.weekly_txs + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
fees.monthly_fee = fees.monthly_fee + fee;
|
||||
fees.monthly_txs = fees.monthly_txs + 1;
|
||||
});
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Fees', msg: 'Arranged Fee Received', data: fees });
|
||||
return fees;
|
||||
};
|
||||
export const arrangePayments = (selNode, body) => {
|
||||
const payments = {
|
||||
sent: body && body.sent ? body.sent : [],
|
||||
received: body && body.received ? body.received : [],
|
||||
relayed: body && body.relayed ? body.relayed : []
|
||||
};
|
||||
payments.sent.forEach((sentEle) => {
|
||||
if (sentEle.recipientAmount) {
|
||||
sentEle.recipientAmount = Math.round(sentEle.recipientAmount / 1000);
|
||||
}
|
||||
sentEle.parts.forEach((part) => {
|
||||
if (part.amount) {
|
||||
part.amount = Math.round(part.amount / 1000);
|
||||
}
|
||||
if (part.feesPaid) {
|
||||
part.feesPaid = Math.round(part.feesPaid / 1000);
|
||||
}
|
||||
if (part.timestamp.unix) {
|
||||
part.timestamp = part.timestamp.unix * 1000;
|
||||
}
|
||||
});
|
||||
if (sentEle.parts && sentEle.parts.length > 0) {
|
||||
sentEle.firstPartTimestamp = sentEle.parts[0].timestamp;
|
||||
}
|
||||
});
|
||||
payments.received.forEach((receivedEle) => {
|
||||
receivedEle.parts.forEach((part) => {
|
||||
if (part.amount) {
|
||||
part.amount = Math.round(part.amount / 1000);
|
||||
}
|
||||
if (part.timestamp.unix) {
|
||||
part.timestamp = part.timestamp.unix * 1000;
|
||||
}
|
||||
});
|
||||
if (receivedEle.parts && receivedEle.parts.length > 0) {
|
||||
receivedEle.firstPartTimestamp = receivedEle.parts[0].timestamp;
|
||||
}
|
||||
});
|
||||
payments.relayed.forEach((relayedEle) => {
|
||||
// Changing the timestamp value to keep the response backward compatible.
|
||||
// ECL < 0.7.0 sent timestamp in unix milliseconds, then in {"iso", "unix"} object.
|
||||
// From v0.10.0, it sends settledAt in {"iso", "unix"} object too.
|
||||
relayedEle.timestamp = relayedEle.settledAt && relayedEle.settledAt.unix ? relayedEle.settledAt.unix * 1000 : relayedEle.timestamp && relayedEle.timestamp.unix ? relayedEle.timestamp.unix * 1000 : relayedEle.timestamp;
|
||||
if (relayedEle.amountIn) {
|
||||
relayedEle.amountIn = Math.round(relayedEle.amountIn / 1000);
|
||||
}
|
||||
if (relayedEle.amountOut) {
|
||||
relayedEle.amountOut = Math.round(relayedEle.amountOut / 1000);
|
||||
}
|
||||
});
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Fees', msg: 'Arranged Payments Received', data: payments });
|
||||
return payments;
|
||||
};
|
||||
export const getFees = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Getting Fees..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/audit';
|
||||
const today = new Date(Date.now());
|
||||
const tillToday = (Math.round(today.getTime() / 1000)).toString();
|
||||
const fromLastMonth = (Math.round(new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime() / 1000)).toString();
|
||||
options.form = {
|
||||
from: fromLastMonth,
|
||||
to: tillToday
|
||||
};
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Fee Audit Options', data: options.form });
|
||||
if (common.read_dummy_data) {
|
||||
common.getDummyData('Fees', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(arrangeFees(req.session.selectedNode, data, Math.round((new Date().getTime())))); });
|
||||
}
|
||||
else {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body });
|
||||
res.status(200).json(arrangeFees(req.session.selectedNode, body, Math.round((new Date().getTime()))));
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Fees', 'Get Fees Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
||||
export const getPayments = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Getting Payments..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/audit';
|
||||
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
|
||||
options.form = { from: 0, to: tillToday };
|
||||
if (req.query.count) {
|
||||
options.form.count = req.query.count;
|
||||
}
|
||||
if (req.query.skip) {
|
||||
options.form.skip = req.query.skip;
|
||||
}
|
||||
if (common.read_dummy_data) {
|
||||
common.getDummyData('Payments', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(arrangePayments(req.session.selectedNode, data)); });
|
||||
}
|
||||
else {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Payments Received', data: body });
|
||||
res.status(200).json(arrangePayments(req.session.selectedNode, body));
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Fees', 'Get Payments Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,47 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { ECLWSClient } from './webSocketClient.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
const eclWsClient = ECLWSClient;
|
||||
export const getInfo = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Eclair Node Information..' });
|
||||
common.logEnvVariables(req);
|
||||
common.setOptions(req);
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/getinfo';
|
||||
options.form = {};
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.lnNode });
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Calling Info from Eclair server url ' + options.url });
|
||||
if (common.read_dummy_data) {
|
||||
common.getDummyData('GetInfo', req.session.selectedNode.lnImplementation).then((data) => {
|
||||
data.lnImplementation = 'Eclair';
|
||||
return res.status(200).json(data);
|
||||
});
|
||||
}
|
||||
else {
|
||||
if (!options.headers || !options.headers.authorization) {
|
||||
const errMsg = 'Eclair Get info failed due to missing or wrong password!';
|
||||
const err = common.handleError({ statusCode: 502, message: 'Missing or Wrong Password', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
else {
|
||||
return request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Connecting to the Eclair\'s Websocket Server.' });
|
||||
body.lnImplementation = 'Eclair';
|
||||
req.session.selectedNode.lnVersion = body.version.split('-')[0] || '';
|
||||
eclWsClient.updateSelectedNode(req.session.selectedNode);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
|
||||
return res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
@ -0,0 +1,158 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
let pendingInvoices = [];
|
||||
export const getReceivedPaymentInfo = (lnServerUrl, invoice) => {
|
||||
let idx = -1;
|
||||
invoice.expiresAt = (!invoice.expiry) ? null : (+invoice.timestamp + +invoice.expiry);
|
||||
if (invoice.amount) {
|
||||
invoice.amount = Math.round(invoice.amount / 1000);
|
||||
}
|
||||
idx = pendingInvoices.findIndex((pendingInvoice) => invoice.serialized === pendingInvoice.serialized);
|
||||
if (idx < 0) {
|
||||
options.url = lnServerUrl + '/getreceivedinfo';
|
||||
options.form = { paymentHash: invoice.paymentHash };
|
||||
return request(options).then((response) => {
|
||||
invoice.status = response.status.type;
|
||||
if (response.status && response.status.type === 'received') {
|
||||
invoice.amountSettled = response.status.amount ? Math.round(response.status.amount / 1000) : 0;
|
||||
invoice.receivedAt = response.status.receivedAt.unix ? response.status.receivedAt.unix : 0;
|
||||
}
|
||||
return invoice;
|
||||
}).catch((err) => {
|
||||
invoice.status = 'unknown';
|
||||
return invoice;
|
||||
});
|
||||
}
|
||||
else {
|
||||
pendingInvoices.splice(idx, 1);
|
||||
invoice.status = 'unpaid';
|
||||
return invoice;
|
||||
}
|
||||
};
|
||||
export const getInvoice = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/getinvoice';
|
||||
options.form = { paymentHash: req.params.paymentHash };
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Found', data: body });
|
||||
const current_time = (Math.round(new Date(Date.now()).getTime() / 1000));
|
||||
body.amount = body.amount ? body.amount / 1000 : 0;
|
||||
body.expiresAt = body.expiresAt ? body.expiresAt : (body.timestamp + body.expiry);
|
||||
body.status = body.status ? body.status : (+body.expiresAt < current_time ? 'expired' : 'unknown');
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoices', 'Get Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listPendingInvoicesRequestCall = (selectedNode, count, skip) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'List Pending Invoices..' });
|
||||
options = selectedNode.authentication.options;
|
||||
options.url = selectedNode.settings.lnServerUrl + '/listpendinginvoices';
|
||||
options.form = { from: 0, to: (Math.round(new Date(Date.now()).getTime() / 1000)).toString() };
|
||||
// Limit the number of invoices till provided count
|
||||
if (count) {
|
||||
options.form.count = count;
|
||||
}
|
||||
if (skip) {
|
||||
options.form.skip = skip;
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((pendingInvoicesResponse) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Pending Invoices List ', data: pendingInvoicesResponse });
|
||||
resolve(pendingInvoicesResponse);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Invoices', 'List Pending Invoices Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const listInvoices = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting List Invoices..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
|
||||
const options1 = JSON.parse(JSON.stringify(options));
|
||||
options1.url = req.session.selectedNode.settings.lnServerUrl + '/listinvoices';
|
||||
options1.form = { from: 0, to: tillToday };
|
||||
if (req.query.count) {
|
||||
options1.form.count = req.query.count;
|
||||
}
|
||||
if (req.query.skip) {
|
||||
options1.form.skip = req.query.skip;
|
||||
}
|
||||
const options2 = JSON.parse(JSON.stringify(options));
|
||||
options2.url = req.session.selectedNode.settings.lnServerUrl + '/listpendinginvoices';
|
||||
options2.form = { from: 0, to: tillToday };
|
||||
if (common.read_dummy_data) {
|
||||
return common.getDummyData('Invoices', req.session.selectedNode.lnImplementation).then(([invoices, pendingInvoicesRes]) => {
|
||||
pendingInvoices = pendingInvoicesRes;
|
||||
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.settings.lnServerUrl, invoice))).
|
||||
then((values) => res.status(200).json(invoices));
|
||||
});
|
||||
}
|
||||
else {
|
||||
return Promise.all([request(options1), request(options2)]).
|
||||
then(([invoices, pendingInvoicesRes]) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: invoices });
|
||||
// pendingInvoices will be used to get the status (paid/unpaid) of the invoice via getReceivedPaymentInfo
|
||||
pendingInvoices = pendingInvoicesRes;
|
||||
if (invoices && invoices.length > 0) {
|
||||
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.settings.lnServerUrl, invoice))).
|
||||
then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Sorted Invoices List Received', data: invoices });
|
||||
return res.status(200).json(invoices);
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoices', 'List Invoices Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Empty List Invoice Received' });
|
||||
return res.status(200).json([]);
|
||||
}
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoices', 'List Invoices Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
||||
export const createInvoiceRequestCall = (selectedNode, description, amount) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = selectedNode.authentication.options;
|
||||
options.url = selectedNode.settings.lnServerUrl + '/createinvoice';
|
||||
options.form = { description: description, amountMsat: amount };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((invResponse) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: invResponse });
|
||||
if (invResponse.amount) {
|
||||
invResponse.amount = Math.round(invResponse.amount / 1000);
|
||||
}
|
||||
resolve(invResponse);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Invoices', 'Create Invoice Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const createInvoice = (req, res, next) => {
|
||||
const { description, amountMsat } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
createInvoiceRequestCall(req.session.selectedNode, description, amountMsat).then((invRes) => {
|
||||
res.status(201).json(invRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
@ -0,0 +1,46 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getNodes = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Node Lookup..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/nodes';
|
||||
options.form = { nodeIds: req.params.id };
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Node Lookup Finished', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Network', 'Node Lookup Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const findRouteBetweenNodesRequestCall = (selectedNode, amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds = [], format = 'shortChannelId') => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Find Route Between Nodes..' });
|
||||
options = selectedNode.authentication.options;
|
||||
options.url = selectedNode.settings.lnServerUrl + '/findroutebetweennodes';
|
||||
options.form = { amountMsat: amountMsat, sourceNodeId: sourceNodeId, targetNodeId: targetNodeId, ignoreNodeIds: ignoreNodeIds, format: format };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Route Lookup Between Nodes Finished', data: body });
|
||||
resolve(body);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Network', 'Route Lookup Between Nodes Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const findRouteBetweenNodes = (req, res, next) => {
|
||||
const { amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds, format } = req.body;
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
findRouteBetweenNodesRequestCall(req.session.selectedNode, amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds, format).then((callRes) => {
|
||||
res.status(200).json(callRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
@ -0,0 +1,93 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const arrangeBalances = (body) => {
|
||||
if (!body.confirmed) {
|
||||
body.confirmed = 0;
|
||||
}
|
||||
if (!body.unconfirmed) {
|
||||
body.unconfirmed = 0;
|
||||
}
|
||||
body.total = +body.confirmed + +body.unconfirmed;
|
||||
body.btc_total = +body.btc_confirmed + +body.btc_unconfirmed;
|
||||
return body;
|
||||
};
|
||||
export const getNewAddress = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Generating New Address..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/getnewaddress';
|
||||
options.form = {};
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'New Address Generated', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'Get New Address Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const getBalance = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Getting On Chain Balance..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/onchainbalance';
|
||||
options.form = {};
|
||||
if (common.read_dummy_data) {
|
||||
common.getDummyData('OnChainBalance', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(arrangeBalances(data)); });
|
||||
}
|
||||
else {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'On Chain Balance Received', data: body });
|
||||
res.status(200).json(arrangeBalances(body));
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'Get Balance Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
||||
export const getTransactions = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Getting On Chain Transactions..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/onchaintransactions';
|
||||
options.form = {
|
||||
count: req.query.count,
|
||||
skip: req.query.skip
|
||||
};
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Getting On Chain Transactions Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'On Chain Transactions Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'Get Transactions Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const sendFunds = (req, res, next) => {
|
||||
const { address, amount, blocks } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Sending On Chain Funds..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/sendonchain';
|
||||
options.form = { address: address, amountSatoshis: amount, confirmationTarget: blocks };
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Onchain', msg: 'Send Funds Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Onchain', msg: 'On Chain Funds Sent', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'OnChain', 'Send Funds Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,155 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getSentInfoFromPaymentRequest = (selNode, payment) => {
|
||||
options.url = selNode.settings.lnServerUrl + '/getsentinfo';
|
||||
options.form = { paymentHash: payment };
|
||||
return request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment Sent Information Received', data: body });
|
||||
body.forEach((sentPayment) => {
|
||||
if (sentPayment.amount) {
|
||||
sentPayment.amount = Math.round(sentPayment.amount / 1000);
|
||||
}
|
||||
if (sentPayment.recipientAmount) {
|
||||
sentPayment.recipientAmount = Math.round(sentPayment.recipientAmount / 1000);
|
||||
}
|
||||
});
|
||||
return body;
|
||||
}).catch((err) => err);
|
||||
};
|
||||
export const getQueryNodes = (selNode, nodeIds) => {
|
||||
options.url = selNode.settings.lnServerUrl + '/nodes';
|
||||
options.form = { nodeIds: nodeIds?.reduce((acc, curr) => acc + ',' + curr) };
|
||||
return request.post(options).then((nodes) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes });
|
||||
return nodes;
|
||||
}).catch((err) => []);
|
||||
};
|
||||
export const decodePayment = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payment..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/parseinvoice';
|
||||
options.form = { invoice: req.params.invoice };
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Decoded', data: body });
|
||||
if (body.amount) {
|
||||
body.amount = Math.round(body.amount / 1000);
|
||||
}
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'Decode Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const postPayment = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Paying Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/payinvoice';
|
||||
options.form = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Invoice Paid', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const queryPaymentRoute = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Querying Payment Route..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/findroutetonode';
|
||||
options.form = {
|
||||
nodeId: req.query.nodeId,
|
||||
amountMsat: req.query.amountMsat
|
||||
};
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body });
|
||||
if (body && body.routes && body.routes.length) {
|
||||
let allRoutesNodeIds = [];
|
||||
allRoutesNodeIds = body.routes?.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
|
||||
return getQueryNodes(req.session.selectedNode, allRoutesNodeIds).then((nodesWithAlias) => {
|
||||
let foundPeer = null;
|
||||
body.routes.forEach((route, i) => {
|
||||
route.nodeIds?.map((node, j) => {
|
||||
foundPeer = nodesWithAlias.find((nodeWithAlias) => node === nodeWithAlias.nodeId);
|
||||
body.routes[i].nodeIds[j] = { nodeId: node, alias: foundPeer ? foundPeer.alias : '' };
|
||||
return node;
|
||||
});
|
||||
});
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Query Routes with Alias Received', data: body });
|
||||
res.status(200).json(body);
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Empty Payment Route Information Received' });
|
||||
res.status(200).json({ routes: [] });
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'Query Route Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const getSentPaymentsInformation = (req, res, next) => {
|
||||
const { payments } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Getting Sent Payment Information..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
if (payments) {
|
||||
const paymentsArr = payments.split(',');
|
||||
return Promise.all(paymentsArr?.map((payment) => getSentInfoFromPaymentRequest(req.session.selectedNode, payment))).
|
||||
then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent Information Received', data: values });
|
||||
return res.status(200).json(values);
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Payments', 'Sent Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Empty Sent Payment Information Received' });
|
||||
return res.status(200).json([]);
|
||||
}
|
||||
};
|
||||
export const sendPaymentToRouteRequestCall = (selectedNode, shortChannelIds, invoice, amountMsat) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = selectedNode.authentication.options;
|
||||
options.url = selectedNode.settings.lnServerUrl + '/sendtoroute';
|
||||
options.form = { shortChannelIds: shortChannelIds, amountMsat: amountMsat, invoice: invoice };
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment To Route Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent To Route', data: body });
|
||||
resolve(body);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Payments', 'Send Payment To Route Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const sendPaymentToRoute = (req, res, next) => {
|
||||
const { shortChannelIds, invoice, amountMsat } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Send Payment To Route..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
sendPaymentToRouteRequestCall(req.session.selectedNode, shortChannelIds, invoice, amountMsat).then((callRes) => {
|
||||
res.status(200).json(callRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
@ -0,0 +1,128 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getFilteredNodes = (selNode, peersNodeIds) => {
|
||||
options.url = selNode.settings.lnServerUrl + '/nodes';
|
||||
options.form = { nodeIds: peersNodeIds };
|
||||
return request.post(options).then((nodes) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Peers', msg: 'Filtered Nodes Received', data: nodes });
|
||||
return nodes;
|
||||
}).catch((err) => []);
|
||||
};
|
||||
export const getPeers = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Getting Peers..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/peers';
|
||||
options.form = {};
|
||||
if (common.read_dummy_data) {
|
||||
common.getDummyData('Peers', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(data); });
|
||||
}
|
||||
else {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
|
||||
if (body && body.length) {
|
||||
let peersNodeIds = '';
|
||||
body.forEach((peer) => { peersNodeIds = peersNodeIds + ',' + peer.nodeId; });
|
||||
peersNodeIds = peersNodeIds.substring(1);
|
||||
return getFilteredNodes(req.session.selectedNode, peersNodeIds).then((peersWithAlias) => {
|
||||
let foundPeer = null;
|
||||
body?.map((peer) => {
|
||||
foundPeer = peersWithAlias.find((peerWithAlias) => peer.nodeId === peerWithAlias.nodeId);
|
||||
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
|
||||
return peer;
|
||||
});
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body });
|
||||
res.status(200).json(body);
|
||||
});
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Empty Peers Received' });
|
||||
res.status(200).json([]);
|
||||
}
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
};
|
||||
export const connectPeer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Conneting Peer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/connect';
|
||||
options.form = {};
|
||||
if (req.query) {
|
||||
options.form = req.query;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Connect Peer Params', data: options.form });
|
||||
}
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: body });
|
||||
if (typeof body === 'string' && body.includes('already connected')) {
|
||||
const err = common.handleError({ statusCode: 500, message: 'Connect Peer Error', error: body }, 'Peers', body, req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
else if (typeof body === 'string' && body.includes('connection failed')) {
|
||||
const err = common.handleError({ statusCode: 500, message: 'Connect Peer Error', error: body }, 'Peers', body, req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/peers';
|
||||
options.form = {};
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List after Connect', data: body });
|
||||
if (body && body.length) {
|
||||
let peersNodeIds = '';
|
||||
body.forEach((peer) => { peersNodeIds = peersNodeIds + ',' + peer.nodeId; });
|
||||
peersNodeIds = peersNodeIds.substring(1);
|
||||
return getFilteredNodes(req.session.selectedNode, peersNodeIds).then((peersWithAlias) => {
|
||||
let foundPeer = null;
|
||||
body?.map((peer) => {
|
||||
foundPeer = peersWithAlias.find((peerWithAlias) => peer.nodeId === peerWithAlias.nodeId);
|
||||
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
|
||||
return peer;
|
||||
});
|
||||
const peers = common.newestOnTop(body || [], 'nodeId', req.query.nodeId ? req.query.nodeId : req.query.uri ? req.query.uri.substring(0, req.query.uri.indexOf('@')) : '');
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
|
||||
res.status(201).json(peers);
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.status(201).json([]);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'Connect Peer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'Connect Peer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const deletePeer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Disconneting Peer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/disconnect';
|
||||
options.form = {};
|
||||
if (req.params.nodeId) {
|
||||
options.form = { nodeId: req.params.nodeId };
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Disconnect Peer Params', data: options.form });
|
||||
}
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body });
|
||||
res.status(204).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Peers', 'Disconnect Peer Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,132 @@
|
||||
import WebSocket from 'ws';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { WSServer } from '../../utils/webSocketServer.js';
|
||||
import { ECLWSEventsEnum } from '../../models/ecl.model.js';
|
||||
export class ECLWebSocketClient {
|
||||
constructor() {
|
||||
this.logger = Logger;
|
||||
this.common = Common;
|
||||
this.wsServer = WSServer;
|
||||
this.webSocketClients = [];
|
||||
this.reconnectTimeOut = null;
|
||||
this.waitTime = 0.5;
|
||||
this.reconnet = (eclWsClt) => {
|
||||
if (this.reconnectTimeOut) {
|
||||
return;
|
||||
}
|
||||
this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2);
|
||||
this.reconnectTimeOut = setTimeout(() => {
|
||||
if (eclWsClt.selectedNode) {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Reconnecting to the Eclair\'s Websocket Server..' });
|
||||
this.connect(eclWsClt.selectedNode);
|
||||
}
|
||||
this.reconnectTimeOut = null;
|
||||
}, this.waitTime * 1000);
|
||||
};
|
||||
this.connect = (selectedNode) => {
|
||||
try {
|
||||
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
|
||||
if (!clientExists) {
|
||||
if (selectedNode.settings.lnServerUrl) {
|
||||
const newWebSocketClient = { selectedNode: selectedNode, reConnect: true, webSocketClient: null };
|
||||
this.connectWithClient(newWebSocketClient);
|
||||
this.webSocketClients.push(newWebSocketClient);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ((!clientExists.webSocketClient || clientExists.webSocketClient.readyState !== WebSocket.OPEN) && selectedNode.settings.lnServerUrl) {
|
||||
clientExists.reConnect = true;
|
||||
this.connectWithClient(clientExists);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
this.connectWithClient = (eclWsClt) => {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connecting to the Eclair\'s Websocket Server..' });
|
||||
const UpdatedLNServerURL = (eclWsClt.selectedNode.settings.lnServerUrl)?.replace(/^http/, 'ws');
|
||||
const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
|
||||
const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + eclWsClt.selectedNode.authentication.lnApiPassword + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
|
||||
eclWsClt.webSocketClient = new WebSocket(WS_LINK);
|
||||
eclWsClt.webSocketClient.onopen = () => {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server..' });
|
||||
this.waitTime = 0.5;
|
||||
this.heartbeat(eclWsClt);
|
||||
};
|
||||
eclWsClt.webSocketClient.onclose = (e) => {
|
||||
if (eclWsClt && eclWsClt.selectedNode && eclWsClt.selectedNode.lnImplementation === 'ECL') {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Web socket disconnected, will reconnect again...' });
|
||||
eclWsClt.webSocketClient.close();
|
||||
if (eclWsClt.reConnect) {
|
||||
this.reconnet(eclWsClt);
|
||||
}
|
||||
}
|
||||
};
|
||||
eclWsClt.webSocketClient.onmessage = (msg) => {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
|
||||
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
|
||||
if (msg.type && msg.type !== ECLWSEventsEnum.PAY_RELAYED && msg.type !== ECLWSEventsEnum.PAY_SETTLING_ONCHAIN && msg.type !== ECLWSEventsEnum.ONION_MESSAGE_RECEIVED) {
|
||||
msg['source'] = 'ECL';
|
||||
const msgStr = JSON.stringify(msg);
|
||||
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
|
||||
}
|
||||
};
|
||||
eclWsClt.webSocketClient.onerror = (err) => {
|
||||
if (eclWsClt.selectedNode.lnVersion === '' || !eclWsClt.selectedNode.lnVersion || this.common.isVersionCompatible(eclWsClt.selectedNode.ln, '0.5.0')) {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'ERROR', fileName: 'ECLWebSocket', msg: 'Web socket error', error: err });
|
||||
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
|
||||
this.wsServer.sendErrorToAllLNClients(errStr, eclWsClt.selectedNode);
|
||||
eclWsClt.webSocketClient.close();
|
||||
if (eclWsClt.reConnect) {
|
||||
this.reconnet(eclWsClt);
|
||||
}
|
||||
}
|
||||
else {
|
||||
eclWsClt.reConnect = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
this.disconnect = (selectedNode) => {
|
||||
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
|
||||
if (clientExists && clientExists.webSocketClient && clientExists.webSocketClient.readyState === WebSocket.OPEN) {
|
||||
this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Disconnecting from the Eclair\'s Websocket Server..' });
|
||||
clientExists.reConnect = false;
|
||||
clientExists.webSocketClient.close();
|
||||
const clientIdx = this.webSocketClients.findIndex((wsc) => wsc.selectedNode.index === selectedNode.index);
|
||||
this.webSocketClients.splice(clientIdx, 1);
|
||||
}
|
||||
};
|
||||
this.updateSelectedNode = (newSelectedNode) => {
|
||||
const clientIdx = this.webSocketClients.findIndex((wsc) => +wsc.selectedNode.index === +newSelectedNode.index);
|
||||
let newClient = this.webSocketClients[clientIdx];
|
||||
if (!newClient) {
|
||||
newClient = { selectedNode: null, reConnect: true, webSocketClient: null };
|
||||
}
|
||||
newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
|
||||
this.webSocketClients[clientIdx] = newClient;
|
||||
};
|
||||
this.heartbeat = (eclWsClt) => {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Websocket Server Heartbeat..' });
|
||||
if (!eclWsClt.webSocketClient) {
|
||||
return;
|
||||
}
|
||||
if (eclWsClt.webSocketClient.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
eclWsClt.webSocketClient.ping();
|
||||
setTimeout(() => {
|
||||
this.heartbeat(eclWsClt);
|
||||
}, 59 * 1000);
|
||||
};
|
||||
this.wsServer.eventEmitterECL.on('CONNECT', (nodeIndex) => {
|
||||
this.connect(this.common.findNode(+nodeIndex));
|
||||
});
|
||||
this.wsServer.eventEmitterECL.on('DISCONNECT', (nodeIndex) => {
|
||||
this.disconnect(this.common.findNode(+nodeIndex));
|
||||
});
|
||||
}
|
||||
}
|
||||
export const ECLWSClient = new ECLWebSocketClient();
|
@ -0,0 +1,35 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getBlockchainBalance = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Getting Balance..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/balance/blockchain';
|
||||
options.qs = req.query;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Balance', msg: 'Request params', data: req.params });
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Balance', msg: 'Request Query', data: req.query });
|
||||
request(options).then((body) => {
|
||||
if (body) {
|
||||
if (!body.total_balance) {
|
||||
body.total_balance = 0;
|
||||
}
|
||||
if (!body.confirmed_balance) {
|
||||
body.confirmed_balance = 0;
|
||||
}
|
||||
if (!body.unconfirmed_balance) {
|
||||
body.unconfirmed_balance = 0;
|
||||
}
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Balance', msg: 'Balance Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Balance', 'Get Balance Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
@ -0,0 +1,275 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const getAliasForChannel = (selNode, channel) => {
|
||||
const pubkey = (channel.remote_pubkey) ? channel.remote_pubkey : (channel.remote_node_pub) ? channel.remote_node_pub : '';
|
||||
options.url = selNode.settings.lnServerUrl + '/v1/graph/node/' + pubkey;
|
||||
return request(options).then((aliasBody) => {
|
||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Alias Received', data: aliasBody.node.alias });
|
||||
channel.remote_alias = aliasBody.node.alias && aliasBody.node.alias !== '' ? aliasBody.node.alias : aliasBody.node.pub_key.slice(0, 20);
|
||||
return channel;
|
||||
}).catch((err) => {
|
||||
channel.remote_alias = pubkey.slice(0, 20);
|
||||
return channel;
|
||||
});
|
||||
};
|
||||
export const getAllChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels';
|
||||
options.qs = req.query;
|
||||
let local = 0;
|
||||
let remote = 0;
|
||||
let total = 0;
|
||||
request(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Channels List Received', data: body });
|
||||
if (body.channels) {
|
||||
return Promise.all(body.channels?.map((channel) => {
|
||||
local = (channel.local_balance) ? +channel.local_balance : 0;
|
||||
remote = (channel.remote_balance) ? +channel.remote_balance : 0;
|
||||
total = local + remote;
|
||||
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
|
||||
return getAliasForChannel(req.session.selectedNode, channel);
|
||||
})).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sorted Channels List Received', data: body });
|
||||
return res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Get All Channel Aliases Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
else {
|
||||
body.channels = [];
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Empty Channels List Received' });
|
||||
return res.status(200).json(body);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const getPendingChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Pending Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/pending';
|
||||
options.qs = req.query;
|
||||
request(options).then((body) => {
|
||||
if (!body.total_limbo_balance) {
|
||||
body.total_limbo_balance = 0;
|
||||
}
|
||||
const promises = [];
|
||||
if (body.pending_open_channels && body.pending_open_channels.length > 0) {
|
||||
body.pending_open_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
|
||||
}
|
||||
if (body.pending_force_closing_channels && body.pending_force_closing_channels.length > 0) {
|
||||
body.pending_force_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
|
||||
}
|
||||
if (body.pending_closing_channels && body.pending_closing_channels.length > 0) {
|
||||
body.pending_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
|
||||
}
|
||||
if (body.waiting_close_channels && body.waiting_close_channels.length > 0) {
|
||||
body.waiting_close_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
|
||||
}
|
||||
return Promise.all(promises).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Pending Channels List Received', data: body });
|
||||
return res.status(200).json(body);
|
||||
}).
|
||||
catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Get Pending Channel Aliases Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Pending Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const getClosedChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Closed Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/closed';
|
||||
options.qs = req.query;
|
||||
request(options).then((body) => {
|
||||
if (body.channels && body.channels.length > 0) {
|
||||
return Promise.all(body.channels?.map((channel) => {
|
||||
channel.close_type = (!channel.close_type) ? 'COOPERATIVE_CLOSE' : channel.close_type;
|
||||
return getAliasForChannel(req.session.selectedNode, channel);
|
||||
})).then((values) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closed Channels List Received', data: body });
|
||||
return res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Get Closed Channel Aliases Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
}
|
||||
else {
|
||||
body.channels = [];
|
||||
return res.status(200).json(body);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Closed Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const postChannel = (req, res, next) => {
|
||||
const { node_pubkey, private: privateChannel, spend_unconfirmed, local_funding_amount, trans_type, trans_type_value, commitment_type } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels';
|
||||
options.form = {
|
||||
node_pubkey_string: node_pubkey,
|
||||
local_funding_amount: local_funding_amount,
|
||||
private: privateChannel,
|
||||
spend_unconfirmed: spend_unconfirmed
|
||||
};
|
||||
if (trans_type === '1') {
|
||||
options.form.target_conf = trans_type_value;
|
||||
}
|
||||
else if (trans_type === '2') {
|
||||
options.form.sat_per_byte = trans_type_value;
|
||||
}
|
||||
if (commitment_type) {
|
||||
options.form.commitment_type = commitment_type;
|
||||
}
|
||||
options.form = JSON.stringify(options.form);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Channel Open Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Opened', data: body });
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Open Channel Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const postTransactions = (req, res, next) => {
|
||||
const { paymentReq, paymentAmount, feeLimit, outgoingChannel, allowSelfPayment, lastHopPubkey } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sending Payment..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/transaction-stream';
|
||||
options.form = { payment_request: paymentReq };
|
||||
if (paymentAmount) {
|
||||
options.form.amt = paymentAmount;
|
||||
}
|
||||
if (feeLimit) {
|
||||
options.form.fee_limit = feeLimit;
|
||||
}
|
||||
if (outgoingChannel) {
|
||||
options.form.outgoing_chan_id = outgoingChannel;
|
||||
}
|
||||
if (allowSelfPayment) {
|
||||
options.form.allow_self_payment = allowSelfPayment;
|
||||
}
|
||||
if (lastHopPubkey) {
|
||||
options.form.last_hop_pubkey = Buffer.from(lastHopPubkey, 'hex').toString('base64');
|
||||
}
|
||||
options.form = JSON.stringify(options.form);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
body = body.result ? body.result : body;
|
||||
if (body.payment_error) {
|
||||
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Payment Sent', data: body });
|
||||
res.status(201).json(body);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const closeChannel = (req, res, next) => {
|
||||
try {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
||||
if (!req.session.selectedNode) {
|
||||
const err = common.handleError({ message: 'Session Expired after a day\'s inactivity.', statusCode: 401 }, 'Session Expired', 'Session Expiry Error', null);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
const channelpoint = req.params.channelPoint?.replace(':', '/');
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/' + channelpoint + '?force=' + req.query.force;
|
||||
if (req.query.target_conf) {
|
||||
options.url = options.url + '&target_conf=' + req.query.target_conf;
|
||||
}
|
||||
if (req.query.sat_per_byte) {
|
||||
options.url = options.url + '&sat_per_byte=' + req.query.sat_per_byte;
|
||||
}
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel Options URL', data: options.url });
|
||||
request.delete(options);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Close Requested' });
|
||||
res.status(202).json({ message: 'Close channel request has been submitted.' });
|
||||
}
|
||||
catch (error) {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Channels', msg: 'Close Channel Error', error: error.message });
|
||||
return res.status(500).json({ message: 'Close Channel Error', error: error.message });
|
||||
}
|
||||
};
|
||||
export const postChanPolicy = (req, res, next) => {
|
||||
const { chanPoint, baseFeeMsat, feeRate, timeLockDelta, max_htlc_msat, min_htlc_msat } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Updating Channel Policy..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/chanpolicy';
|
||||
if (chanPoint === 'all') {
|
||||
options.form = JSON.stringify({
|
||||
global: true,
|
||||
base_fee_msat: baseFeeMsat,
|
||||
fee_rate: parseFloat((feeRate / 1000000).toString()),
|
||||
time_lock_delta: parseInt(timeLockDelta)
|
||||
});
|
||||
}
|
||||
else {
|
||||
const breakPoint = chanPoint.indexOf(':');
|
||||
const txid_str = chanPoint.substring(0, breakPoint);
|
||||
const output_idx = chanPoint.substring(breakPoint + 1, chanPoint.length);
|
||||
const optionsBody = {
|
||||
base_fee_msat: baseFeeMsat,
|
||||
fee_rate: parseFloat((feeRate / 1000000).toString()),
|
||||
time_lock_delta: parseInt(timeLockDelta),
|
||||
chan_point: { funding_txid_str: txid_str, output_index: parseInt(output_idx) }
|
||||
};
|
||||
if (max_htlc_msat) {
|
||||
optionsBody['max_htlc_msat'] = max_htlc_msat;
|
||||
}
|
||||
if (min_htlc_msat) {
|
||||
optionsBody['min_htlc_msat'] = min_htlc_msat;
|
||||
optionsBody['min_htlc_msat_specified'] = true;
|
||||
}
|
||||
options.form = JSON.stringify(optionsBody);
|
||||
}
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Policy Updated', data: body });
|
||||
if (body.failed_updates && body.failed_updates.length && body.failed_updates[0].update_error) {
|
||||
const err = common.handleError({ error: body.failed_updates[0].update_error }, 'Channels', 'Update Channel Policy Error', req.session.selectedNode);
|
||||
return res.status(500).json({ message: err.message, error: err.error });
|
||||
}
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Update Channel Policy Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|