Compare commits

...

456 Commits

Author SHA1 Message Date
ShahanaFarooqui d875d928d7 Update trigger tag regex 1 day ago
ShahanaFarooqui 3f2b3786d7
GitHub actions docker (#1421)
Add Github Actions with Buildx for Docker publish
1 day ago
ShahanaFarooqui f92e1473ca
Updating Circle CI config to move to Buildx setup (#1419) 2 days ago
ShahanaFarooqui 125a3b61ae
Release 0.15.2 (#1418)
* Check for authentication obj before delete operation #1415 (#1416)

* Updating version number
4 days ago
ShahanaFarooqui 5a6b5f2cae
Check for authentication obj before delete operation #1415 (#1416) (#1417) 6 days ago
ShahanaFarooqui b6dbd23ae7
Lint Fix (#1408)
Lint bug Fix
2 weeks ago
ShahanaFarooqui f01815bd04
Circle CI build and lint fixes (#1407) 2 weeks ago
ShahanaFarooqui 22ab6d1154
Release 0.15.1 (#1406)
* rm .DS_Store

* Add watchfrontenddev command for npm

* Fix toggle issues in sidenav (pinning and on page refresh)

* Add copy-to-clipboard fallback if navigator.clipboard is not available (#1336)

* add copy-to-clipboard fallback if navigator.clipboard is not available

* amend copy fallback

* clipboard copy lint fixes and frontend build

* fix: add missing boltz state `transaction.lockupFailed` (#1349)

* fix: boltzd docs link (#1354)

* exit gracefully (#1356)

* allow for eclair updated relayed audit format (#1363)

* feat: add boltz service to cln (#1352)

* lint fix

* Request Params Cleanup

* cln: Boltz auto-send (#1366)

* Bug-fix (CLN Boltz): Hide claim tx id and routing fee for non-zero conf reverse swap

* cln: Boltz auto-send

- Added auto send option for Swap In
- Checking compatiblity with v2.0.0 and above

* Test import fixes

* Update help.component.ts (#1379)

Fixed broken link under "Help" -> "Node Settings"

* Backend config fix (#1382)

* Updating Common Application Configuration

* Fixed get RTL Conf

* Update Application Settings

* application and settings case change

* Unified config models

* Default node update

* 2FA and Password reset

* Final application settings update

* Config Settings and Authentication case fixed

* Node Setting Fix

* Fiat currency Symbol fix

* CLN: Fiat symbol fix

* All: Fiat symbol fix

* Update node settings

* Services UI fix

* CLN: Removed child node settings

* All: Removed child node settings

* Test fixes

* mempool links for onchain information (#1383)

* Tests fix

Tests fix

* UI for Block Explorer Configuration (#1385)

* Bump fee with mempool information (#1386)

* Mempool openchannel minfee (#1388)

Open channel model block if min fee is higher

* Show error on login screen if rune is incorrect and getinfo throws error (#1391)

* cln: Removed channel lookup call for update policy (#1392)

* ECL: On-chain Transactions, Invoice and Payments pagination (#1393)

Done most of the UI changes to accommodate pagination on transactions, payments and invoices tables but true pagination cannot be implemented till total number of records are missing from the API response.

Once the issue https://github.com/ACINQ/eclair/issues/2855 is fixed, I will uncomment pagination changes in the frontend.

* lnd: Onchain CPFP (#1394)

- UTXO label bug fix
- Warning on utxo label for "sweep" in text.

* Bug fixes after testing

* Testing bug fixes (#1401)

* Bug fix 2: lnd: Link channel point to explorer and show fee on close channel too

* lnd: explorer link on pending channels

* Node lookup link on view channel peer pubkey

* Testing bug fixes (#1402)

* Bug fix 2: lnd: Link channel point to explorer and show fee on close channel too

* lnd: explorer link on pending channels

* Node lookup link on view channel peer pubkey

* test fixes

* ng update to v18.0.x

* Updating install with  --legacy-peer-deps

---------

Co-authored-by: Grzegorz Kućmierz <gkucmierz@gmail.com>
Co-authored-by: lacksfish <lacksfish@gmail.com>
Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com>
Co-authored-by: Kilian <19181985+kilrau@users.noreply.github.com>
Co-authored-by: Taylor King <taylorbradleyking@gmail.com>
Co-authored-by: Fishcake <128653975+fishcakeday@users.noreply.github.com>
Co-authored-by: Ant <72945059+2140data@users.noreply.github.com>
2 weeks ago
saubyk ea7f300360 Nodejs update for CI & Docker and updated documentation 7 months ago
ShahanaFarooqui 43a46b65e1 Lint fix 7 months ago
ShahanaFarooqui 475b47b7ea
Release 0.15.0 (#1334)
c-lightning-REST to clnrest migration.
7 months ago
ShahanaFarooqui d083be1196
Merge pull request #1314 from Ride-The-Lightning/Release-0.14.1
Release 0.14.1
9 months ago
ShahanaFarooqui ec58a24ed4
Merge pull request #1310 from Ride-The-Lightning/ng-lint
Codebase Linting
9 months ago
ShahanaFarooqui 82d87b32c1 Codebase Linting 9 months ago
ShahanaFarooqui 13514c7a62
Merge pull request #1308 from Ride-The-Lightning/ng-16
Updated to Angular 16 and removed vulnerabilities
9 months ago
ShahanaFarooqui e4a2ef9a21 Updated to Angular 16 and removed vulnerabilities 9 months ago
ShahanaFarooqui 36c5895d4f
Merge pull request #1307 from Ride-The-Lightning/ux-link-fixes
Bug fix: Manage button link filter #1294 and Page Settings Error
9 months ago
ShahanaFarooqui 14fd866d1a Bug fix: Manage button link filter #1294 and Page Settings Error 9 months ago
ShahanaFarooqui ed5a493654
Merge pull request #1305 from Ride-The-Lightning/lnd-taproot
LND Taproot Channel
9 months ago
ShahanaFarooqui 74af120c8f Changed from channel_type to commitment_type 9 months ago
ShahanaFarooqui d478356076 LND Taproot Channel 9 months ago
ShahanaFarooqui 53eb5f086b
Merge pull request #1304 from Ride-The-Lightning/rtl-donation
Add RTL donations link
9 months ago
ShahanaFarooqui b603045a26 Add RTL donations link 9 months ago
ShahanaFarooqui 66bc43543d
Merge pull request #1303 from Ride-The-Lightning/csv-download
Changed date format for csv downloaded files
9 months ago
ShahanaFarooqui 5ed34f0aa5 Fixed tests 9 months ago
ShahanaFarooqui 95c0b7feeb Changed date format 9 months ago
ShahanaFarooqui 1acbfab1de Lint config 9 months ago
ShahanaFarooqui 0040d38b50
Merge pull request #1301 from Ride-The-Lightning/ecl-routing-fee
Bug fix: ECL routing fee msat to sat conversion #1251
9 months ago
ShahanaFarooqui 52ad99941e Bug fix: ECL routing fee msat to sat conversion #1251 9 months ago
ShahanaFarooqui 89d8a44164 Jasmin test fix 9 months ago
ShahanaFarooqui 3b2f17873a
Merge pull request #1300 from Ride-The-Lightning/lnd-payments-sort
Bug fix: LND Payment sort #1295
9 months ago
ShahanaFarooqui f8393d570e Bug fix: LND Payment sort 9 months ago
ShahanaFarooqui 6079faacce CL to CLN migration cleanup 9 months ago
ShahanaFarooqui aa9bf3549c Typescript Configuration 9 months ago
ShahanaFarooqui 5523794f47
Merge pull request #1299 from Ride-The-Lightning/loop-version
Show loop version
9 months ago
ShahanaFarooqui 656ab99f88 Show loop version 9 months ago
ShahanaFarooqui 15080d6749
Merge pull request #1293 from Ride-The-Lightning/ecl-channel-response-update
Updated channels response columns and migrated pagesettings
9 months ago
ShahanaFarooqui 36e1d39170 Updated channels response columns and migrated pagesettings 9 months ago
ShahanaFarooqui 168aa3bc90
Merge pull request #1292 from Ride-The-Lightning/cln-prefix-version-fix
Bug Fix: CLN version check error with prefix `basedon`
9 months ago
ShahanaFarooqui 281e51eb7a Bug Fix: CLN version check error with prefix `basedon`
Bug Fix: CLN version check error with prefix `basedon`
9 months ago
ShahanaFarooqui 9768963862 bug fix: id to channel_id 11 months ago
Shahana Farooqui d875779ba6 Go To Routing Message
Go To Routing Message
1 year ago
Shahana Farooqui 9f0d2bfadf Updated Version 1 year ago
Shahana Farooqui 895b1de27d ECL Channel Rebalance bug fix 1 year ago
Shahana Farooqui fb891b9390 Version 0.14.0 build 1 year ago
ShahanaFarooqui 12f828e5c2
Merge pull request #1205 from kn0wmad/copy-fix
Minor grammatical fix
1 year ago
ShahanaFarooqui 5cb6e10bd2
Merge pull request #1248 from Ride-The-Lightning/Release-0.14.0
Release 0.14.0
1 year ago
ShahanaFarooqui 23ff7ac1ec
Merge branch 'Release-0.14.0' into copy-fix 1 year ago
Shahana Farooqui cea4c9dec4 Functional Route Guard
Functional Route Guard
1 year ago
ShahanaFarooqui 6a72950fbe
Merge pull request #1247 from Ride-The-Lightning/cln-msat-migration
msatoshi migration without backward compatibility
1 year ago
ShahanaFarooqui 37606b5889
Merge branch 'Release-0.13.7' into cln-msat-migration 1 year ago
Shahana Farooqui 1fcad6306f msatoshi migration without backward compatibility
msatoshi migration without backward compatibility
1 year ago
Shahana Farooqui d262c2d88f ECL Rebalance bug fix 1 year ago
Shahana Farooqui e650f45b9f Bug fix for cln utxo selection for open channel 1 year ago
Shahana Farooqui e08f2ebc28 Bug fixes: broken open channel links & backward compatible listChannels
Bug fixes: broken open channel links & backward compatible listChannels
1 year ago
Shahana Farooqui 89c2291cbd Test fixes 1 year ago
Shahana Farooqui beb6ac3b46 Removing PR Stats
Removing PR Stats
1 year ago
Shahana Farooqui 0aa4ee74e6 Document update for default/optional options 1 year ago
ShahanaFarooqui ed9634ef25
Merge pull request #1244 from Ride-The-Lightning/cln-msat-migration
Cln msatoshi migration
1 year ago
Shahana Farooqui 40173091e9 cln reports msat migration
cln reports msat migration
1 year ago
Shahana Farooqui acd9325451 cln routing msat migration
cln routing msat migration
1 year ago
Shahana Farooqui bd86fe79bb cln lookup & feerate msat migration 1 year ago
Shahana Farooqui 8bce41276b cln channels & transactions msat migration 1 year ago
ShahanaFarooqui 7a18d934f2
Merge pull request #1241 from Ride-The-Lightning/cln-channel-filter
Bug fix: Active channel filtered as inactive #1240
1 year ago
Shahana Farooqui 98cf3ae648 Bug fix: Active channel filtered as inactive #1240 1 year ago
ShahanaFarooqui 7f1fac9a91
Merge pull request #1236 from Ride-The-Lightning/cln-htlc-list
Fixing Server lint bugs
1 year ago
Shahana Farooqui 2e4ed0c963 Fixing Server lint bugs
Fixing Server lint bugs
1 year ago
ShahanaFarooqui 500437ff4c
Merge pull request #1235 from Ride-The-Lightning/cln-htlc-list
Add pending htlcs to all channels tabs
1 year ago
Shahana Farooqui 804ba91d7b Add pending htlcs to all channels tabs #1086
Add pending htlcs to all channels tabs #1086
1 year ago
ShahanaFarooqui f45bbc4ad2
Merge pull request #1234 from naturallaw777/patch-1
Update README.md
1 year ago
SovranSystems.com e14e5e56c9
Update README.md
Added https://sovransystems.com that uses RTL based on (https://github.com/fort-nix/nix-bitcoin).
1 year ago
Shahana Farooqui 0cb1e575fc Lint error fix 1 year ago
ShahanaFarooqui e46a5623ba
Merge pull request #1232 from Ride-The-Lightning/ecl-circular-rebalance
Circular Rebalance #842
1 year ago
Shahana 3863653a5e Circular Rebalance #842
Circular Rebalance #842
1 year ago
ShahanaFarooqui 575e0581f8
Merge pull request #1230 from Ride-The-Lightning/lnd-lease-utxo
Bug fix: LND Lease UTXO not working #1227
1 year ago
Shahana efdaa13244 Bug fix: LND Lease UTXO not working #1227
Bug fix: LND Lease UTXO not working #1227
1 year ago
Shahana a2d1833a36 Bug fix: Circular rebalance infographics #1216
Bug fix: Circular rebalance infographics #1216
1 year ago
ShahanaFarooqui 6c943e1e93
Merge pull request #1224 from Ride-The-Lightning/home-redirect-filter
Bug fix: Filtering is not working after redirect from dashboard #1216
1 year ago
Shahana Farooqui 859d882018 Bug fix: Filtering is not working after redirect from dashboard #1216
Bug fix: Filtering is not working after redirect from dashboard #1216
1 year ago
ShahanaFarooqui 2d3aa63ce6
Merge pull request #1223 from Ride-The-Lightning/modal-to-graph-lookup
Modal to graph lookup
1 year ago
Shahana Farooqui 57a931c161 Link to graph lookup from channel & peer modals LND & ECL #1103
Link to graph lookup from channel & peer modals LND & ECL #1103
1 year ago
Shahana Farooqui ee9ec62253 Link to graph lookup from channel & peer modals CLN #1103
Link to graph lookup from channel & peer modals CLN #1103
1 year ago
Shahana Farooqui 61d563e952 Lint and test fixes 1 year ago
ShahanaFarooqui fed5cd9d38
Merge pull request #1222 from Ride-The-Lightning/connect-node-on-lookup
Fix: Dark theme, lnd payment message, node lookup connect, Issues 121…
1 year ago
ShahanaFarooqui 17137a1a55
Merge branch 'Release-0.13.7' into connect-node-on-lookup 1 year ago
Shahana Farooqui 1bd901feab Fix: Dark theme, lnd payment message, node lookup connect, Issues 1211, 1210, 1134
Fix: Dark theme, lnd payment message, node lookup connect, Issues 1211, 1210, 1134
1 year ago
ShahanaFarooqui d847830836 Router lint bug fix 1 year ago
ShahanaFarooqui bd72be79bb Lint errors fix
Lint errors fix
1 year ago
ShahanaFarooqui ea75a70734
Merge pull request #1207 from Ride-The-Lightning/Release-0.13.6
Release 0.13.6
1 year ago
ShahanaFarooqui 813eb218e3 UI tab text clipping bug fix 1 year ago
ShahanaFarooqui d79fb62b70 Offers Fix 1 year ago
ShahanaFarooqui 56e5558bf7 Offers Update #1206 1 year ago
kn0wmad cc42ce627c
Minor grammatical fix 1 year ago
ShahanaFarooqui ffdcd2c78b
Merge pull request #1202 from Ride-The-Lightning/Release-0.13.5
Release 0.13.5
1 year ago
ShahanaFarooqui eb7216d068
Merge pull request #1201 from Ride-The-Lightning/default-invoice-expiry
Default Invoice expiry to seven days #1159
1 year ago
ShahanaFarooqui a90e35e631 Default Invoice expiry to seven days #1159
Default Invoice expiry to seven days #1159
1 year ago
ShahanaFarooqui 6bcffb6bdc Dependency Updates
Dependency Updates
1 year ago
ShahanaFarooqui 9663a0b75b
Merge pull request #1199 from Ride-The-Lightning/configurable-dblocation
Make db location configurable #1177
1 year ago
ShahanaFarooqui 8e65dd2c9e Make db location configurable #1177
Make db location configurable #1177
1 year ago
ShahanaFarooqui ceceab04af
Merge pull request #1198 from Ride-The-Lightning/switch-toggle
AnnounceChannel and FiatConverstion switch #1197
1 year ago
ShahanaFarooqui c991464229 AnnounceChannel and FiatConverstion switch #1197
AnnounceChannel and FiatConverstion switch #1197
1 year ago
ShahanaFarooqui 01ee444941
Merge pull request #1196 from Ride-The-Lightning/enhanced-error-message
Updated error handling function
1 year ago
ShahanaFarooqui 03dca70aa1 Updated error handling function
Updated error handling function
1 year ago
ShahanaFarooqui d1de24310d
Merge pull request #1167 from Ride-The-Lightning/bugfix-password-change-button
Bug fix for change password buttons
1 year ago
ShahanaFarooqui 9a6281f234 Bug fix for change password buttons
Bug fix for change password buttons
1 year ago
ShahanaFarooqui b603181ba2
Merge pull request #1165 from Ride-The-Lightning/Release-0.13.4
Release 0.13.4
1 year ago
ShahanaFarooqui c21d1f5b59 Removed server lint errors 2 years ago
ShahanaFarooqui ada3c6caf3 lint fix 2 years ago
ShahanaFarooqui 26ebb316be github action test script fix 2 years ago
ShahanaFarooqui 0efaf136ff
Merge pull request #1162 from Ride-The-Lightning/fix-2fa
Removing lint and strict typecheck
2 years ago
ShahanaFarooqui d29a1fbdb8 Removing lint and strict typecheck 2 years ago
ShahanaFarooqui 9e91f06530
Merge pull request #1161 from Ride-The-Lightning/fix-2fa
Fix for 2FA Bug
2 years ago
ShahanaFarooqui 541b1cfa3c backend compiled 2 years ago
ShahanaFarooqui 5107c300eb 2FA Fix
2FA Fix
2 years ago
ShahanaFarooqui 1e52024fab temp 2 years ago
ShahanaFarooqui 2b32f96be3 Circle CI Fix
Circle CI Fix
2 years ago
ShahanaFarooqui 27d210c295
Merge pull request #1157 from Ride-The-Lightning/Release-0.13.3
Release 0.13.3
2 years ago
ShahanaFarooqui ef502511af Removing pinging message from log 2 years ago
ShahanaFarooqui 54b1d5b8b9 Pinging ECL WS server 2 years ago
ShahanaFarooqui 79f3344f7e Adding heartbeat to ECL WS server 2 years ago
ShahanaFarooqui aad8547a8a Modified icon size and overflow
Modified icon size and overflow
2 years ago
ShahanaFarooqui 2887a5a80f increasing icons width 2 years ago
abhishandy 997e41a0c8 chore: build frontend 2 years ago
abhishandy a099af5bc4 fix: color code or inactive channel 2 years ago
ShahanaFarooqui 9db65d9786 Adding request dependency 2 years ago
ShahanaFarooqui 3eb784c479 ECL Remove subscription from payment relayed 2 years ago
ShahanaFarooqui ff50809df8 Adding env for lint 2 years ago
ShahanaFarooqui 4b29474f3e lint error fix & localhost to ip docs 2 years ago
ShahanaFarooqui 296d7f95e0
Merge pull request #1149 from Ride-The-Lightning/ecl-default-filter-bugfix
ECL Default invoice & payment filtering bug fix #1143
2 years ago
ShahanaFarooqui 1cdacb9535 ECL Default invoice & payment filtering bug fix #1143
ECL Default invoice & payment filtering bug fix #1143
2 years ago
ShahanaFarooqui b1e3af15dd Removed environments 2 years ago
ShahanaFarooqui a5079bcd61 Replacing environment for XSRF bug 2 years ago
ShahanaFarooqui 44bb84d6f8 Material Icon and Roboto Fonts 2 years ago
ShahanaFarooqui 0e48676c5c Removed LN Config Auth for SSO 2 years ago
ShahanaFarooqui 6804fc762d Sort refresh bug fix 2 years ago
ShahanaFarooqui 670141a097 Table Sorting Fix 2 years ago
ShahanaFarooqui d7e15cbfaf Mobile resolution fixes 2 years ago
ShahanaFarooqui 8339de2a50 Table action select style fix 2 years ago
ShahanaFarooqui 7a5e78b8cd ng build 2 years ago
ShahanaFarooqui 251c450431 ngrx, charts and qrcode updates 2 years ago
ShahanaFarooqui 90dd0ac1c7 light theme fixed 2 years ago
ShahanaFarooqui 47f8d3d972 Replaced placeholder with label for required check 2 years ago
ShahanaFarooqui 006775a0ce dark mode fixed 2 years ago
ShahanaFarooqui 795a643f39 dark color fixes 2 years ago
ShahanaFarooqui a7dc8c541a ng Material 15 UI adjustments
ng Material 15 UI adjustments
2 years ago
ShahanaFarooqui 439fd68077 Updated cdk 15 2 years ago
ShahanaFarooqui 72140a6d1e ng animations updated 2 years ago
ShahanaFarooqui 9141658976 Downgrading material to 14 due to flex layout
Downgrading material to 14 due to flex layout
2 years ago
ShahanaFarooqui 00a231372a html linting 2 years ago
ShahanaFarooqui 8501afe9b5 ng Lint Fixes 2 years ago
ShahanaFarooqui 893fcd964b Build and test error fixes 2 years ago
ShahanaFarooqui ca28744c33 Git Workflow Fix 2 years ago
ShahanaFarooqui 6b61e0852f package-lock and document update 2 years ago
ShahanaFarooqui 7e40c4ea30 All Dependencies update 2 years ago
ShahanaFarooqui be34d70670 ng material v15 update 2 years ago
ShahanaFarooqui be0d1775d0 ng v15 update 2 years ago
ShahanaFarooqui 86d4d15552 ng material v14 update 2 years ago
ShahanaFarooqui b4e65e2098 Updated to ng 14 2 years ago
ShahanaFarooqui baa284b0b9 package-lock update 2 years ago
ShahanaFarooqui 9cc7dcd412
Merge pull request #1123 from NateNate60/patch-1
Update with instructions on how to use Apache2
2 years ago
ShahanaFarooqui a90d4c48b3 Tiny UI fixes
Tiny UI fixes
2 years ago
ShahanaFarooqui 411a602727 Version update 2 years ago
ShahanaFarooqui abcf1de46a Rearranged instructions
Rearranged instructions
2 years ago
ShahanaFarooqui 04f909329e
Merge pull request #1137 from Ride-The-Lightning/Release-0.13.2
Release 0.13.2
2 years ago
ShahanaFarooqui 57208d1029 lnd AMP Invoice
lnd AMP Invoice
2 years ago
ShahanaFarooqui 9e2c9e05c5 LND blank remote alias bug fix 2 years ago
ShahanaFarooqui 921fb6850e More bug fixes
More bug fixes
2 years ago
ShahanaFarooqui 94a060d489 CLN Liquid Check 2 years ago
ShahanaFarooqui dee4f75d3d ECL Removed Channel Type 2 years ago
ShahanaFarooqui 7c45b044d7 Page Layout Accordion 2 years ago
ShahanaFarooqui 7331ba1e50
Merge pull request #1133 from Ride-The-Lightning/unannounced-channel-config
Unannounced Channel Configuration #1036
2 years ago
ShahanaFarooqui 2c113030b4 Unannounced Channel Configuration #1036 2 years ago
ShahanaFarooqui 6ce2ae82c8
Merge pull request #1131 from Ride-The-Lightning/ecl-channeltype-remove
Removing channel type selection
2 years ago
ShahanaFarooqui b505931c82 Removing channel type selection 2 years ago
ShahanaFarooqui a0f037df4a
Merge pull request #1128 from Ride-The-Lightning/lnd-synced-to-chain
Show Synced to chain status #1126
2 years ago
ShahanaFarooqui 65f73057ca Show Synced to chain status #1126
Show Synced to chain status #1126
2 years ago
ShahanaFarooqui fc459774ef
Merge pull request #1127 from Ride-The-Lightning/page-layout
Page layout
2 years ago
ShahanaFarooqui 38a02839d6 Page Layout Finished 2 years ago
ShahanaFarooqui ac6b9d8002 ECL Manual Testing Done 2 years ago
ShahanaFarooqui 5fd902cab8 LND Manual Testing Done 2 years ago
ShahanaFarooqui c70911cdd3 CLN Manual Testing Done 2 years ago
ShahanaFarooqui 7f275be31e Changed Filters date pipe to match view format 2 years ago
ShahanaFarooqui c4c98fdf13 Shared filter on selected columns 2 years ago
ShahanaFarooqui 0dc879038a ECL Filter by selected columns 2 years ago
ShahanaFarooqui c0a1472eef LND Filter by selected columns 2 years ago
ShahanaFarooqui 2807a408a8 CLN Filter mapping with constants 2 years ago
ShahanaFarooqui 7fea447975 CLN Filter by selected column 2 years ago
ShahanaFarooqui 38e2c84957 Fixed multiple 'All's in filter 2 years ago
ShahanaFarooqui 0a22082987 Bug fix: default config doesn't have password
Bug fix: default config doesn't have password
2 years ago
ShahanaFarooqui 3b8ca9177a Selected Column Filter Incomplete
Selected Column Filter Incomplete
2 years ago
ShahanaFarooqui 211b6fdca4 Frontend Built 2 years ago
ShahanaFarooqui fa7a4a4c7b Grid Filter By 2 years ago
ShahanaFarooqui b13e18a843 Pipe Declaration 2 years ago
ShahanaFarooqui 44321d62e5 Removing Spaces 2 years ago
ShahanaFarooqui 40b1b80503 Column Labels Complete 2 years ago
ShahanaFarooqui 7277bcb187 LND & ECL Column Labels 2 years ago
ShahanaFarooqui 96b4810020 Changed Tooltip position 2 years ago
ShahanaFarooqui a7c65268a5 DB Files error fix if indices are changed 2 years ago
ShahanaFarooqui f8166e67e8 Offer Update & ECL camelCase Fix 2 years ago
ShahanaFarooqui 2b368c4e39 Column width dynamic 2 years ago
ShahanaFarooqui 4d163a8216 db and ellipsis fix 2 years ago
ShahanaFarooqui 2e54ba92ac Fixed grid paddings 2 years ago
ShahanaFarooqui 0ce5107e86 Load Table after dispalyed columns calculated 2 years ago
ShahanaFarooqui dcf29c5e96 Tooltip on blank headers 2 years ago
ShahanaFarooqui 7363309801 Bug fix: Table null data error 2 years ago
ShahanaFarooqui 44dbc2691d LND Pending tables fixed 2 years ago
ShahanaFarooqui 6163f24268 Page setting bug fix 2 years ago
Nathan Lim 8b2aec049a
Update with instructions on how to use Apache2
Some users prefer to use Apache2 instead of Nginx as their reverse proxy (because some people already have Apache2 up for some other reason). This adds a guide on how to do that.
2 years ago
ShahanaFarooqui c2755acb33 lnd on chain transaction page layout 2 years ago
ShahanaFarooqui c4b2e21139 on chain UTXOs page layout 2 years ago
ShahanaFarooqui 76afb82dae lnd all routing & reports page layout 2 years ago
ShahanaFarooqui 381be53e40 lnd forwarding history page layout 2 years ago
ShahanaFarooqui 198866791e LND Graph Lookup Page layout 2 years ago
ShahanaFarooqui 4a9689feca lnd all peers channels page layout 2 years ago
ShahanaFarooqui 20632b6ac3 lnd closed channels page layout 2 years ago
ShahanaFarooqui b1b1de5add lnd open channels page layout 2 years ago
ShahanaFarooqui 65e4c35226 lnd peers page layout 2 years ago
ShahanaFarooqui 5c79a078fa lnd pending channels page layout 2 years ago
ShahanaFarooqui cc1cda3e56 lnd payments page layout 2 years ago
ShahanaFarooqui 246b6ee5d8 LND Invoices Page Layout incomplete
LND Invoices Page Layout incomplete
2 years ago
ShahanaFarooqui 243b5fd293 LND layout component basic & removed default sort 2 years ago
ShahanaFarooqui e62e7473f1 ECL Page Setup Finished 2 years ago
ShahanaFarooqui 72beafa26c Adding lnd peerswap check 2 years ago
ShahanaFarooqui 815446587e ECL UI incomplete 2 years ago
ShahanaFarooqui 64034aa017 ECL Page Layout Default Settings 2 years ago
ShahanaFarooqui 6a19acb351 CLN Query Routes Page Layout 2 years ago
ShahanaFarooqui 5abe432a2d ECL Page Layout Base 2 years ago
ShahanaFarooqui c930b8e121 Boltz Page Layout 2 years ago
ShahanaFarooqui 8986fb6771 LND Services Page Layout 2 years ago
ShahanaFarooqui f5fe776cd0 CLN Page Layout Complete 2 years ago
ShahanaFarooqui 744775a197 CLN Routing Page Layout 2 years ago
ShahanaFarooqui 4f6cebc98d CLN Forwarding History Page Layout 2 years ago
ShahanaFarooqui c0c9d798a1 CLN Liquidity Ads Page Layout 2 years ago
ShahanaFarooqui eafc7835f8 CLN Peers and Channels Page Layout 2 years ago
ShahanaFarooqui c487379898 Sorting in table settings 2 years ago
ShahanaFarooqui d98064ca2c CLN Offers and Bookmarks Page Layout
CLN Offers and Bookmarks Page Layout
2 years ago
ShahanaFarooqui e7b03f4b2f CLN On-chain page settings
CLN On-chain page settings
2 years ago
ShahanaFarooqui 149561dedb CLN Invoices Page Settings
CLN Invoices Page Settings
2 years ago
ShahanaFarooqui d350f1c0f9 CLN Payments tiny adjustments
CLN Payments tiny adjustments
2 years ago
ShahanaFarooqui e74c5bc635 CLN Payment Sort 2 years ago
ShahanaFarooqui 772633b0c1 CLN Payment record/page & showcolumns except sort 2 years ago
ShahanaFarooqui 24013f6f89 Payments incomplete & max column validation 2 years ago
ShahanaFarooqui 7a0bd37d91 Validate Document First 2 years ago
ShahanaFarooqui cfd8f095a3 DB Validations 2 years ago
ShahanaFarooqui c27ee4ed6a More validation 2 years ago
ShahanaFarooqui d6185dd409 Whole Setup Validation
Whole Setup Validation
2 years ago
ShahanaFarooqui a72f00a97b Page Settings
Page Settings
2 years ago
ShahanaFarooqui 8c4443a873 Page Settings Incomplete
Page Settings Incomplete
2 years ago
ShahanaFarooqui 6945d084b4 Base Config Page
Base Config Page
2 years ago
ShahanaFarooqui dd180cba25 Updated Package lock after PR #1070 2 years ago
ShahanaFarooqui daa4c735fb
Merge pull request #1070 from erikarvstedt/fix-dependencies
Move dev-only deps to `devDependencies`
2 years ago
ShahanaFarooqui f7ed36c5ac
Merge pull request #1119 from Ride-The-Lightning/github-stats
yearly
2 years ago
ShahanaFarooqui 02390fef1c yearly 2 years ago
ShahanaFarooqui 5853ffdfd1
Merge pull request #1118 from Ride-The-Lightning/github-stats
Yearly
2 years ago
ShahanaFarooqui 556af1e65d Yearly 2 years ago
ShahanaFarooqui 1307fbc919
Merge pull request #1117 from Ride-The-Lightning/github-stats
GitHub stats
2 years ago
ShahanaFarooqui a0c5da1d5b workflow dispatch 2 years ago
ShahanaFarooqui 1c365479ad closed PR 2 years ago
ShahanaFarooqui c3e217f708
Merge pull request #1116 from Ride-The-Lightning/github-stats
PR Stats
2 years ago
ShahanaFarooqui 487165c197 PR Stats
PR Stats
2 years ago
ShahanaFarooqui 9b519e0267
Merge pull request #1113 from Ride-The-Lightning/help-section-update
Help section update #1112
2 years ago
ShahanaFarooqui 4d5bb8e3dc Help section update #1112
Help section update #1112
Liquidity Ads LeaseFeeBase unit bug fix
Bitcoind config bug fix
2 years ago
ShahanaFarooqui 65ee024e8d Testcase fix
Testcase fix
2 years ago
ShahanaFarooqui 444c657158
Merge pull request #1107 from Ride-The-Lightning/lnd-ping-time
Unit for Ping Time #1048
2 years ago
ShahanaFarooqui 2e7c9456ea Unit for Ping Time #1048
Unit for Ping Time #1048
2 years ago
ShahanaFarooqui 1211294ddb
Merge pull request #1106 from Ride-The-Lightning/lnd-taproot
Add Taproot Address Type #1075
2 years ago
ShahanaFarooqui c9d389cbd7 Add Taproot Address Type #1075
Add Taproot Address Type #1075
2 years ago
ShahanaFarooqui 43d5b52d96
Merge pull request #1104 from Ride-The-Lightning/blank-invoice
Bug fix: Blank invoice preview #1019
2 years ago
ShahanaFarooqui 72fecba40a Bug fix: Blank invoice preview #1019
Bug fix: Blank invoice preview #1019
2 years ago
ShahanaFarooqui 2565d11f19
Merge pull request #1102 from Ride-The-Lightning/hide-rpcuser
Auth on lnconfig navigation & lint
2 years ago
ShahanaFarooqui 2fe9c9f129 Auth on lnconfig navigation & lint
Auth on lnconfig navigation & lint
2 years ago
ShahanaFarooqui 88f481d120
Merge pull request #1100 from Ride-The-Lightning/hide-rpcuser
Hide rpcuser value from ln config #861
2 years ago
ShahanaFarooqui 69a7e3010b Hide rpcuser value from ln config #861
Hide rpcuser value from ln config #861
2 years ago
ShahanaFarooqui f63e21b9d9
Merge pull request #1094 from Ride-The-Lightning/ecl-channeltype-private
Added ChannelType in Open Channel #1093 & Private bug fix #1092
2 years ago
ShahanaFarooqui 9f5ee4eb5b Added ChannelType in Open Channel #1093 & Private bug fix #1092
Added ChannelType in Open Channel #1093 & Private bug fix #1092
2 years ago
ShahanaFarooqui 4a470feae9
Merge pull request #1079 from Ride-The-Lightning/cln-peer-swap
Configure Peerswap Service #1071
2 years ago
ShahanaFarooqui 9cff990441
Merge branch 'Release-0.13.2' into cln-peer-swap 2 years ago
ShahanaFarooqui 59bae5d33a Merge branch 'master' into Release-0.13.2 2 years ago
ShahanaFarooqui cf0cb040d7 Liquidity Ads Private Channel Bug Fix
Liquidity Ads Private Channel Bug Fix
2 years ago
ShahanaFarooqui b4f1b47cf1 Service Directory Structure Update
Service Directory Structure Update
2 years ago
ShahanaFarooqui 96d1ebaa5a UI bug fix
UI bug fix
2 years ago
ShahanaFarooqui 25ba51c467 TS Lint Errors Fixes
TS Lint Errors Fixes
2 years ago
ShahanaFarooqui 20bb789c47 CLN Transactions page load bug fix
CLN Transactions page load bug fix
2 years ago
ShahanaFarooqui 86fd2a7b29 CLN Forwarding history filter and lint fixes
CLN Forwarding history filter and lint fixes
2 years ago
Chris Guida 6f13a03ec8 fix liquidity adds 'suplemented' typo 2 years ago
ShahanaFarooqui 966791fd80 Dashboard Layout Fix
Dashboard Layout Fix
2 years ago
ShahanaFarooqui 7f00e3de9c Configure Peerswap Service #1071
Configure Peerswap Service #1071
2 years ago
ShahanaFarooqui 9bfa04a712 Service Directory Structure Update
Service Directory Structure Update
2 years ago
Erik Arvstedt c5de2592e6 Move non-runtime deps to `devDependencies` 2 years ago
ShahanaFarooqui f775f2814f UI bug fix
UI bug fix
2 years ago
ShahanaFarooqui 32adf76a5a TS Lint Errors Fixes
TS Lint Errors Fixes
2 years ago
ShahanaFarooqui c16913402b CLN Transactions page load bug fix
CLN Transactions page load bug fix
2 years ago
ShahanaFarooqui e2eeb8b919 CLN Forwarding history filter and lint fixes
CLN Forwarding history filter and lint fixes
2 years ago
Chris Guida ba2a326b6c fix liquidity adds 'suplemented' typo 2 years ago
ShahanaFarooqui 1ec90e9949 Dashboard Layout Fix
Dashboard Layout Fix
2 years ago
ShahanaFarooqui 66a5dd7c24 Fee bump incorrect unit #1028
Fee bump incorrect unit #1028
2 years ago
ShahanaFarooqui cd2324944c Liquidity Ads address Type Chips
Liquidity Ads address Type Chips
2 years ago
ShahanaFarooqui 89e7ebadaa LND update fee policy with min max htlc
LND update fee policy with min max htlc
2 years ago
ShahanaFarooqui 07f8d326d8 Merge branch 'pr/1039' into Release-0.13.0 2 years ago
ShahanaFarooqui 75b49ef122 Merge branch 'pr/630' into Release-0.13.0 2 years ago
ShahanaFarooqui 76cd5f3359 Merge branch 'pr/630' into Release-0.13.0 2 years ago
ShahanaFarooqui 05aa2d883f DB Error Message
DB Error Message
2 years ago
Shahana Farooqui d0b651f8db Test error fix
Test error fix
2 years ago
Shahana Farooqui a1a507a44d Fixing lint and test errors
Fixing lint and test errors
2 years ago
Shahana Farooqui 9165ae6a58 Removed list forwards from dashboard and pagination
Removed list forwards from dashboard and pagination
2 years ago
Shahana Farooqui da405a866b ng test fix
ng test fix
2 years ago
Shahana Farooqui ce3abf2fa7 ng tests fix for github actions
ng tests fix for github actions
2 years ago
Shahana Farooqui da96875acb Docker compose update for multiarch
Docker compose update for multiarch
2 years ago
Shahana Farooqui dd328cad1c Dependency updates
Dependency updates
2 years ago
Shahana Farooqui a4c29ac35b --legacy-peer-deps
--legacy-peer-deps
2 years ago
Shahana Farooqui c1f7622d45 Removing multiarch
Removing multiarch
2 years ago
Shahana Farooqui 07f8b53546 Adding Multiarch setup
Adding Multiarch setup
2 years ago
Shahana Farooqui 098788b01e Removing multiarch setup
Removing multiarch setup
2 years ago
Shahana Farooqui 428e9a5fd9 Final Liquidity Ads Updates
Final Liquidity Ads Updates
2 years ago
Shahana Farooqui 70abf7af25 Removed sorting from CLN paginated list forwards
Removed sorting from CLN paginated list forwards
2 years ago
Shahana Farooqui 5b39581b82 Cookie connect.sid removed
Cookie connect.sid removed
2 years ago
Shahana Farooqui 64173a8b2d Documentation update for npm install error #1049
Documentation update for npm install error #1049
2 years ago
ShahanaFarooqui 5f873ca50f
CLN Regtest network fix #974 (#1043)
CLN Regtest network fix #974
2 years ago
Frank van de Pol 6728077914 lnd: add advanced channel policy min/max_htlc_msat 2 years ago
ShahanaFarooqui 592074679b
Adding CSRF Header for Browser Extension (#1037)
Adding CSRF Header for Browser Extension
2 years ago
Shahana Farooqui b8477e3613 CLN Liq Ads Node Explorer Links
CLN Liq Ads Node Explorer Links
2 years ago
ShahanaFarooqui 7d8a8a15d6
Cln forwards pagination (#1033)
List Forwards Pagination
List Forwards Backward Compatibility
2 years ago
Shahana Farooqui 976b6f0e27 Forwarding events pagination
Forwarding events pagination
2 years ago
Shahana Farooqui 3a9c436c32 Forwarding events pagination incomplete 2
Forwarding events pagination incomplete
2 years ago
Shahana Farooqui 1580c296cd Forwarding events pagination incomplete
Forwarding events pagination incomplete
2 years ago
Shahana Farooqui c7fd9fad08 Liquidity Ads Page except channel count and capacity filters
Liquidity Ads Page except channel count and capacity filters
2 years ago
Shahana Farooqui 9da3eed1b6 Open Liquidity Channel
Open Liquidity Channel
2 years ago
Shahana Farooqui 3795851acf Liquidity Ads Page Incomplete
Liquidity Ads Page Incomplete
2 years ago
Shahana Farooqui 346e414181 Liquidity Ads List Display Incomplete
Liquidity Ads List Display Incomplete
2 years ago
Shahana Farooqui 47011011d9 Liquidity Ads - Configuration
Liquidity Ads - Configuration
2 years ago
Shahana Farooqui df5f5768c1 Funder Update POST
Funder Update POST
2 years ago
Shahana Farooqui 2fa8760422 Do not run Lint & Test action on feature branch
Do not run Lint & Test action on feature branch
2 years ago
Shahana Farooqui 1e6786a850 Enable/Disable with listConfigs
Enable/Disable with listConfigs
2 years ago
Shahana Farooqui 81dfa053dc Funder policy enable flag
Funder policy enable flag
2 years ago
Shahana Farooqui 519c18bafc Funder Policy Update
Funder Policy Update
2 years ago
Shahana Farooqui ee096d7e1b Network address overflows bug fix #1016
Network address overflows bug fix #1016
2 years ago
Shahana Farooqui 9bb484d3fe Updated Server Message #1013
Updated Server Message #1013
2 years ago
Shahana Farooqui af547f922e Adding Report By option for Routing Reports #805
Adding Report By option for Routing Reports #805
2 years ago
Shahana Farooqui f3375191fc Documentation update for Sample file location
Documentation update for Sample file location
2 years ago
Shahana Farooqui 987d5d833f Merge branch 'pr/1006' into Release-0.12.4 2 years ago
Shahana Farooqui 7059df2a5c Merge branch 'pr/1011' into Release-0.12.4 2 years ago
Shahana Farooqui fa3e2f77d1 Version Update
Version Update
2 years ago
Shahana Farooqui f84719e2c3 Removed test script from build
Removed test script from build
2 years ago
Shahana Farooqui 5d945a234c Separate script for test and build
Separate script for test and build
2 years ago
ShahanaFarooqui 44412d357e
Release 0.12.3 (#1012)
LND Palemoon UX extension panel bug
Cookie file not generated for BTCPayServer #990
ECL Missing fee calculation on Dashboard #975
CLT channel filter on alias bug fix #982
CLightning to Code Lightning #997
Added Infographics for Channel Rebalance
CLN Base fee zero bug fix #987
ECL Query Route bug fix #1007
LND faster initial load
LND Transactions Lookup #1002
LND Bug fix for Routing Fee Calculation
2 years ago
Lee Salminen ddb63c1bae
Disambiguate multiple channels with same node
This PR disambiguates the display of available channels to perform a circular rebalance with. This is very helpful if a node has multiple channels with the same peer.
2 years ago
benarc 34eabeb622 typo for doc location 2 years ago
Shahana Farooqui 386ff5afa6 Typescript types fix
Typescript types fix
2 years ago
Shahana Farooqui 87128c3a94 Vulnerabilities fix
Vulnerabilities fix
2 years ago
ShahanaFarooqui 3910284d58
Release 0.12.2 (#964)
Release 0.12.2.

Co-authored-by: saiy2k <saiy2k@gmail.com>
Co-authored-by: bota87 <52842374+bota87@users.noreply.github.com>
Co-authored-by: Bota <Bota@ASUS>
Co-authored-by: Harshvardhan R <harshvardhanr.181it217@nitk.edu.in>
2 years ago
ShahanaFarooqui 9c59954205
Release 0.12.1 (#932)
Offers QR Code bug fix
Websocket Authcheck csrf cookie validation
Bug Fix: Wrong year in Date #918
Improved INFO & DEBUG Logging
LND: Bug fix Color Setting in Config #925
2FA button toggle #906
Bug Fix: HTLC viewing #924
All Tooltips on form controls are updated with mat-icon:info

Co-authored-by: saiy2k <saiy2k@gmail.com>
2 years ago
Suheb 89fcb11c39
Update README.md 3 years ago
Suheb f5c1c03b22
Update README.md 3 years ago
ShahanaFarooqui bea5980c6f
Release 0.12.0 (#916)
Release 0.12.0
3 years ago
ShahanaFarooqui cf66708df9
Release 0.11.2 (#786)
Fee Report Refresh Bug Fix #776
LND Connect Peer Bug Fix #778
npm audit fix
Increased CircleCI timeout to 20 mins
3 years ago
ShahanaFarooqui 87f65803e0
Release 0.11.1 (#774)
Release 0.11.1 (#774)
3 years ago
Shahana Farooqui 4ef50a1f85 Refresh Invoice Bug Fix
Refresh Invoice Bug Fix
3 years ago
ShahanaFarooqui c1d7e22642
Release 0.11.1 (#770)
Release 0.11.1
3 years ago
Suheb c6e435a151
Update Application_configurations 3 years ago
ShahanaFarooqui e45d6d598a
Release 0.11.0 (#713)
- Package updates
    Updated docker NodeJS from 10 to 14
    Updated Angular from 11 to 12
    Updated Material from 11 to 12
    Updated Angular cli from 11 to 12
    Updated Karma from 5 to 6
    Updated rxjs from 6 to 7
    Updated ngrx from 10 to 12
    Updated angularx-qrcode from 10 to 11
    Updated @angular/flex-layout from 11 to 12
    Updated angular-user-idle from 2.2.4 to 2.2.5
    Updated typescript from 4.0.2 to 4.2.4
    Updated zone.js from 0.10.2 to 0.11.4
    Migrated from TSLint to ESLint
    Installed save-dev crypto-browserify & stream-browserify

- Mask password with fixed length #689
- CSRF Token (#696)
- Route lock default password (#700)
- ECL Invoice amount mislabeled #694
- ECL & LND Fee report time zone offset bug fixes #692 & #693
- Loop remove max routing fee validation #690
- Child route refresh bug
- Adding Password Blacklist (#704)
- Fee rate in percentage #621 (#705)
- ECL Adding BaseFee and FeeRate on Channels
- LND Invoice and Payment pagination fix (#707)
- Keysend missing QR code bug fix
- Login page XS layout fix
- Reports tables load time improved (#709)
- Report initial table load bug fix
3 years ago
ShahanaFarooqui e4d6256803
Release 0.10.2 (#666)
High CPU usage by browser when session inactivity dialog is showing #624
Block Altcoins #627
Remove slide right animation on route change #642
Update the initiator field for Loop APIs #643
Filter Bug fix #623
Transaction id for pending waiting channel #603
Empty cookie security risk bug fix #610
Material container repositions on Mac Firefox #268 & #619 
Mask config file passwords #636
Downloaded all channels backup fails to restore #614
CLT Routing list disappears on navigation #652
Update Bump Fee modal #628
LND Paying zero amount invoice fails #657
Open channel fails after adding peer with uri #662
Update Fee Policy Bug Fix #659
Changed default password from `changeme` to `password` (#653) (Contributed By: Andrew Leschinsky <andrew@leschinsky.com>)
3 years ago
psysc0rpi0n a9074f2efc Updated TOR configuration instructions for Linux and Android only 3 years ago
psysc0rpi0n 4001c83d46 Updated SSL configuration instructions (https) 3 years ago
you wish to know 492c0f45c9 Updated SSL configuration instructions (https) 3 years ago
Aaron Dewes de0e8294b1
Remove node-sass from README (#609) 3 years ago
ShahanaFarooqui f817ae39bc
Release 0.10.1 (#605)
Dahboard layout fix #576
Boltz integration (#590)
Merged verify token and password authentication
Channel mobile view update
ECL Adding inactive channels force close
Flag Dust UTXOs
Bug fix for forwarding history browser crash #596
Label and Lease UTXOs
UI Table Fixes
App settings (#604)
Settings updates
UTXO lease confirmation box
Login and Settings page  without add new node
3 years ago
Suheb d040ab891c
Update README.md 3 years ago
ShahanaFarooqui 5a38585b71
Release 0.10.0 (#571)
Channel backup download file bug fix #536
Added macaroon authentication for Loop (#543)
Adding Label for Loop In & Loop Out #538
Fee Report & Routing Enhancements (#555)
Payments report #559
Transactions Report #357
Material table sorting bug fix #556
CL & ECL ng Routing #551 & Hocon Read Fix #560 (#561)
CLT & ECL Reports (#562)
UI Bug fixes for tables group sort, pagination, dialog and spinner close
Increased request body size #544 (#564)
App lock after 5 attempts #542 & DatePicker default adapter #532 (#566)
Upgade Angular 11 (#568)
Loop amount validation #569
Loop https document updates
4 years ago
Suheb 6e036d8382
Update README.md 4 years ago
Suheb a1b177b11d
Add files via upload 4 years ago
Suheb 33bde83c19
Delete LND-Channel-Management.png 4 years ago
Suheb e017125a83
Add files via upload 4 years ago
Suheb 98c5b8d250
Add files via upload 4 years ago
Suheb 58dd1904ec
Add files via upload 4 years ago
Suheb 3c4ba33baf
Add files via upload 4 years ago
Suheb cdf7b0f0a5
Add files via upload 4 years ago
Suheb e3e37303f5
Add files via upload 4 years ago
ShahanaFarooqui 80516e1ffe
Release 0.9.3 (#532)
ECL - Channel Force Close Bug Fix #523
ECL - Routing Filter Bug Fix #525
ECL - Adding Routing Peers Tab #527
LND - Open Channel Bump Fee #529
4 years ago
ShahanaFarooqui 58c46ebf71
Release 0.9.2 (#521)
ECL Decode Payment #517
Sweep All asking for password from SSO users #519
4 years ago
saubyk a390dbda3f
changes to contributing.md 4 years ago
ShahanaFarooqui 0ec41f2de1
Release 0.9.1 (#513)
ECL Enable MPP #452
Authentication layout fix on Safari #473
Updated error message for download backup #471
Increase badge width with number #453
CLT Sweep All #451
CLT Invoice incorrect fiat conversion #454
UTXO Hint, MPP Indentation, ECL Inactive Peer Reconnect #459, #460, #462
LND filter outgoing channel for send payment #463
Show Description on Payments Info #467
Added Description in Payments Info #489
ECL Added Settled Amount in Invoices list #441
List the UTXOs for the wallet #420
Loop upgraded to 9.0 with backward compatibility #472
Confirmation message updated for force close
Display the active HTLCs #498
Path information missing for transaction #497
Problem parsing eclair.conf #383
Created Contributing.md
Separate fee and payments calls #428
Updated Forwarding History Table and View Info #437 (#507)
Showing channel ID when alias is missing #495 & #481
Add payment description #510
Force close pending channels #447
4 years ago
saubyk bf8c687d6d
channel backup doc update 4 years ago
saubyk 6be6378c65
backup restore documentation 4 years ago
Shahana Farooqui 12fd25ef98 CLT update channel fee bug fix
CLT update channel fee bug fix
4 years ago
Shahana Farooqui d8d9ca501c CLT POST bug fix
CLT POST bug fix
4 years ago
ShahanaFarooqui 4166f4f06c
Circular rebalance info alert (#449)
Circular rebalance info alert
4 years ago
ShahanaFarooqui 5aac044a13
list invoice htlcs (#448)
list invoice htlcs
4 years ago
ShahanaFarooqui 25bdec8a70
lnd multi part payment #445 (#446)
lnd multi part payment #445
4 years ago
ShahanaFarooqui 4da3b66bc7
Channel closing with pending htlc & inactive without priority #393 & #410 (#444)
Channel closing with pending htlc & inactive without priority #393 & #410
4 years ago
ShahanaFarooqui 18ec1b70fe
Add memo to payments CSV & view info #346 (#443)
Add memo to payments CSV & view info #346
4 years ago
ShahanaFarooqui 6c64b076d1
Version checks for coin selection and keysend features #436 (#442)
Version checks for coin selection and keysend features #436
Removing 'v' from CLT version
4 years ago
ShahanaFarooqui 03ca93bb0c
CLT multi part payment sort, total and status fix (#440)
CLT Update payments lists multiple entries for a payment hash
CLT multi part payment sort, total and status fix
4 years ago
ShahanaFarooqui c72e2ed2c0
CLT Update payments lists multiple entries for a payment hash (#438)
CLT Update payments lists multiple entries for a payment hash
4 years ago
ShahanaFarooqui b50c58a72e
ECL missing Alias on Channels page #429 (#435)
ECL missing Alias on Channels page #429
4 years ago
ShahanaFarooqui 536f26389a
Remove version from server urls #369 (#433)
Remove version from server urls #369
4 years ago
ShahanaFarooqui 8586b18a00
keysend initial (#427)
keysend initial
4 years ago
ShahanaFarooqui 65882678ca
Rounded swap routing fee fix #425 (#426)
Rounded swap routing fee
Closing #425
4 years ago
Shahana Farooqui e8424cc035 Coin selection ux fixes
Coin selection ux fixes
4 years ago
saubyk c30d0942ed
LND API coverage removed from doc links 4 years ago
saubyk 23f207cca1
readme update 4 years ago
ShahanaFarooqui da553bc055
Closing coin selection on open channel #409 (#423)
Closing coin selection on open channel #409
4 years ago
ShahanaFarooqui 1409042275
Send fund coin selection #406 (#422)
Send fund coin selection #406
4 years ago
ShahanaFarooqui da4d163d35
Loop infographics on the mobile resolution bug fix #408 (#421)
Loop infographics on the mobile resolution bug fix #408
4 years ago
ShahanaFarooqui 86cc31f4b9
LND On-chain payment amount unit conversion bug fix #398 (#413)
LND On-chain payment amount unit conversion bug fix #398
4 years ago
Shahana Farooqui d33bff42f9 Doc update: docker example update
Doc update: docker example update
4 years ago
ShahanaFarooqui 6a03e1d133
CLT Include amount and amount settled in the invoice list #402 (#412)
CLT Include amount and amount settled in the invoice list #402
4 years ago
ShahanaFarooqui 541e47b6e5
CLT Show the output lists on the On-chain section #403 (#411)
CLT Show the output lists on the On-chain section #403
4 years ago
ShahanaFarooqui d953277d83
Release 0.8.4 (#404)
Material tables numeric sorting fix
Show channel visibility to open channel table (#359)
Removing host value from config for BTCPayServer
4 years ago
Joseph Goulden 8080b1bca8
Feat: Show channel visibility to open channel table (#359)
Feat: Show channel visibility to open channel table
4 years ago
saubyk e16fd44b36
readme update 4 years ago
Shahana Farooqui 378d86db0d Material table sort fix
Material table sort fix
4 years ago
Shahana Farooqui b7fdaaac21 v0.8.3 build after merge
v0.8.3 build after merge
4 years ago
ShahanaFarooqui ed9ce410a7
Release 0.8.3 (#397)
Table sort and Loop #388 fix
Material table sort case insensitive
4 years ago
Shahana Farooqui be627fa38d Adding node alias on title bar
Adding node alias on title bar
4 years ago
ShahanaFarooqui da24d3fcc7
Release 0.8.3 (#396)
Channel Rebalance and UI alignement fix #384 & 385
Force close inactive channel with priority disabled
Replace CLT forwarding history short channel id with alias & show node alias on welcome message.
Showing pending htlc length in channel info
4 years ago
saubyk 2f5dbd9ae2
fixing tests 4 years ago
saubyk 1cd61c0e22
sign components test fix 4 years ago
saubyk 9e3e6d4a5d
testing specs fixed 4 years ago
Shahana Farooqui 3364e44dd5 Documentation updated for ECL lnApiPassword
Documentation updated for ECL lnApiPassword
4 years ago
Shahana Farooqui 1847e04a43 Documentation updated for ECL lnApiPassword
Documentation updated for ECL lnApiPassword
4 years ago
Andrew Camilleri fb269e3cfe
Update Application_configurations (#380) 4 years ago
saubyk b302b774fd
readme updated with stable release version 4 years ago
saubyk 7066cd9dda
readme update 4 years ago
ShahanaFarooqui 1443e795ea
Added LN_API_PASSWORD for BTCPayserver (#378)
Added LN_API_PASSWORD for BTCPayserver
4 years ago
saubyk 3ac835cabd
updated c-lightning and eclair readme 4 years ago
ShahanaFarooqui fe1ee623a9
Fixed ngcc error and removed node-sass dependency (#377)
Fixed ngcc error and removed node-sass dependency
4 years ago
saubyk 2588ecaf1c
updated eclair readme 4 years ago
saubyk b307c8f5f0
updated eclair readme 4 years ago
saubyk ceb336c9a8
updated readme 4 years ago
Shahana Farooqui 06bf136568 Firefox Lookup Alignment
Firefox Lookup Alignment
4 years ago
saubyk 173d8d8d3a
updated eclair readme 4 years ago
ShahanaFarooqui 848d450732
Release 0.8.0 (#375)
Support for Eclair #224 
set Hourglass animation when logging to RTL enhancement #277 
C-Lightning: Remove 'Local Features' and 'Global Features' fields from Peers grid #350 
Export channel backup files #362 
LND Sweep all: Unable to decode address error #352 
Merchant dashboard alignment issue for Safari #348 
Update Angular and Material to version 9 #295
4 years ago
ShahanaFarooqui 1b86a8bc84
Eclair Implementation (#371)
Eclair Implementation, Closing issue #224
4 years ago
saubyk f83fa33a34
C-Lightning command coverage added 4 years ago
saubyk d116db5fc6
c-lightning instructions updated 4 years ago
Martin Milata c07796e360
Fix typo (#354) 4 years ago
saubyk b6f1e06757
roadmap update 4 years ago
saubyk c139dce06c
making application feature list current # 4 4 years ago
saubyk 6b13d67151
making application feature list current # 3 4 years ago
saubyk ea2e4769f3
making application feature list current # 2 4 years ago
saubyk a33b79a359
making application feature list current # 1 4 years ago
saubyk 5c796c104e
Updated lnd api coverage 4 years ago
saubyk 1de6015247
Updated readme 4 years ago
saubyk 407774834a
Merge branch 'master' of https://github.com/Ride-The-Lightning/RTL 4 years ago
saubyk 9eed0d94de
Updated c-lightning readme 4 years ago
Julian Simon de Castro 932d26b9bd
Add Bitcon full node BCubium (#351) 4 years ago
saubyk 88c14bb0bc
Updated readme for setup guide links 4 years ago
saubyk b8fbcc8f0c
updated readme for install instructions 4 years ago
Shahana Farooqui d2958650e6 Server host config bug fix
Server host config bug fix
4 years ago
Shahana Farooqui a42e76edce Rebalancing & directory creation bug fix
Rebalancing & directory creation bug fix
4 years ago
Martin Habovštiak 4c94197bc2
Added hashbang (#304)
* Added hashbang
* Updated the env style hashbang
4 years ago
Martin Habovštiak 99b3dbb798
Fixed error handling in createDirecctory (#311)
The error handling in `createDirecctory` had three major issues:

* Racy check for existence of a directory - the directory could've been
  created or deleted in the meantime.
* Attempt to handle race outside the reduce function wouldn't let it
  continue creating the remaining directories.
* All errors that were **not** `EEXISTS` or `ENOENT` were silently
  ignored, leading to lot of frustration and hair pulling, because the
  code continued executing and then failed at attempt to open a file.

These problems were fixed by moving `try` into the reduce closure,
ignoring `EEXISTS` only and rethrowing all other errors. The logic
modifying the error message for `ENOENT` was kept.
4 years ago
ShahanaFarooqui a980d9c3fa
Release 0.7.1 (#340)
* Peers And Channels Redesign (#294)
* Send payment without confirmation
* Removed vulnerabilities & added missing 2FAuth button
* Invoice Redesign with invoice info modal opening
* Firefox Payment Failure Issue #283 (#309)
* On chain redesign (#315)
* Bug fix #312 Bogus warning about unsecure connection (#316)
* Peer Channels Redesign #290 (#317)
* CLT transactions redesign #290 (#319)
* CLT On-chain redesign #290 (#320)
* Wallet error alert bug fix #298 & #299 (#321)
* Configurable server host #303 (#322)
* Receive tab on top #292 (#323)
* Settings Layout Cleanup (#325)
* Alias Autocomplete (#326)
* Redirect to Dashboard after first RTL login
* Bug fix Channel Uptime wrong #284 (#329)
* Channel info redesigned Issue #328 (#331)
* Fixed Transaction Info Scroll #324 (#333)
* Remove macaroon info before logging #306
* Added Peer Alias for Closed And Pending Channels Tabs #275 & #285 (#336)
* Show multiple URIs on show pubkey modal #337 (#338)
4 years ago
ShahanaFarooqui 685372fc5b Update issue templates 4 years ago
Suheb deb6ef307a
Update README.md 4 years ago
Shahana Farooqui af2dc43444 Minor vulnerabilities fix
Minor vulnerabilities fix
4 years ago

BIN
.DS_Store vendored

Binary file not shown.

@ -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

@ -1,4 +1,4 @@
# Editor configuration, see http://editorconfig.org
# Editor configuration, see https://editorconfig.org
root = true
[*]
@ -8,6 +8,9 @@ indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md]
max_line_length = off
trim_trailing_whitespace = false

@ -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"
}
}
]
}

@ -1,6 +1,9 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
@ -14,19 +17,15 @@ Steps to reproduce the behavior:
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
Tell us what happens instead
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Your environment**
* version of `lnd`
* operating system (`uname -a` on *Nix)
* version of `btcd`, `bitcoind`, or other backend
* Version of `RTL`
* Version of `lnd`/`core lightning`/`eclair`
* Version of `btcd`, `bitcoind`, or other backend
* Browser & browser version
* Operating system (`uname -a` on *Nix)
* any other relevant environment details
**Additional context**

@ -1,6 +1,9 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---

@ -1,11 +1,10 @@
## Ride The Lightning (RTL)
![](screenshots/RTL-LND-Dashboard.png)
![](./screenshots/RTL-LND-Dashboard.png)
<a href="https://snyk.io/test/github/Ride-The-Lightning/RTL"><img src="https://snyk.io/test/github/Ride-The-Lightning/RTL/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/Ride-The-Lightning/RTL" style="max-width:100%;"></a>
[![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE)
### Stable Release: v0.6.8
**Intro** -- [Application Features](docs/Application_features.md) -- [Road Map](docs/Roadmap.md) -- [LND API Coverage](docs/LNDAPICoverage.md) -- [Application Configurations](docs/Application_configurations) -- [C-lightning](docs/C-Lightning-setup.md)
**Intro** -- [Application Features](./docs/Application_features.md) -- [Road Map](./docs/Roadmap.md) -- [Application Configurations](./docs/Application_configurations.md) -- [Core Lightning](./docs/Core_lightning_setup.md) -- [Eclair](./docs/Eclair_setup.md) -- [Contribution](./docs/Contributing.md)
* [Introduction](#intro)
* [Architecture](#arch)
@ -18,17 +17,13 @@
### <a name="intro"></a>Introduction
RTL is a full function, device agnostic, web user interface to help manage lightning node operations.
RTL is available on LND and C-Lightning implementations.
RTL is available on [LND](https://github.com/lightningnetwork/lnd), [CoreLightning](https://github.com/ElementsProject/lightning) and [Eclair](https://github.com/ACINQ/eclair) implementations.
This page covers instructions for LND. For C-lightning, refer to [this](docs/C-Lightning-setup.md) page.
* Core Lightning users, refer to [this](./docs/Core_lightning_setup.md) page for install instructions.
* Eclair users, refer to [this](./docs/Eclair_setup.md) page for install instructions.
* LND users, follow the instructions below
Lightning Network Daemon(LND) is an implementation of Lightning Network BOLT protocol by [Lightning Labs](https://lightning.engineering/).
Pre-requisite for running RTL is a functioning and synced LND node. You can setup your own node, by following the below guides:
* Windows/Mac users can follow Pierre Rochard's [Node Launcher](https://github.com/lightning-power-users/node-launcher)
* Linux or Raspberry Pi users can follow Stadicus's [guide](https://github.com/Stadicus/guides/blob/master/raspibolt/README.md)
For detailed screenshots and UI operation guide you can visit our [medium post](https://medium.com/@suheb.khan/how-to-ride-the-lightning-447af999dcd2)
Pre-requisite for running RTL is a functioning and synced LND node. If you are a Raspberry Pi or a Linux user, you can follow the famous Stadicus's [guide](https://stadicus.github.io/RaspiBolt/) to setup a Bitcoin + Lighting node.
RTL is available on the below platforms/services:
* [RaspiBlitz](https://github.com/rootzoll/raspiblitz)
@ -37,45 +32,49 @@ RTL is available on the below platforms/services:
* [Blockdaemon](https://blockdaemon.com/bitcoin-lightning-protocol-details)
* [myNode](http://mynodebtc.com)
* [Lux Node](https://luxnode.io/product/lux-node/)
* [BCubium](https://bgeometrics.com)
* [Start9Labs](https://start9labs.com)
* [Umbrel](https://github.com/getumbrel/umbrel)
* [Sovran Systems](https://sovransystems.com)
Docker Image: https://hub.docker.com/r/shahanafarooqui/rtl
### <a name="arch"></a>Architecture
![](screenshots/RTL-LND-Arch-2.png)
![](./screenshots/RTL-LND-Arch-2.png)
### <a name="prereq"></a>Prerequisites
* Functioning and synced LND lightning node.
* Node.js, which can be downloaded [here](https://nodejs.org/en/download/)
* On Ubuntu, `g++` is required to install the node-sass dependency. This is available in the `build-essential` package.
* The Most recent versions of node.js might give errors while installing node-sass. Use node.js LTS version 8 or 10 as a solution.
* Recommended Browsers: Chrome, Firefox, MS Edge
### <a name="install"></a>Installation
#### First time setup
* Fetch sources from RTL git repository, by executing the below on the command prompt:
`$ git clone https://github.com/Ride-The-Lightning/RTL.git`
* Change directory to RTL folder:
`$ cd RTL`
* Fetch the production dependencies by running:
`$ npm install --only=prod`
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 dependencies
```
$ cd RTL
$ git reset --hard HEAD
$ git clean -f -d
$ git pull
$ npm install --only=prod
$ 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.
*Advanced users can refer to [this page](docs/Multi-Node-setup.md), for config settings required to manage multiple nodes*
*Advanced users can refer to [this page](./docs/Multi_node_setup.md), for config settings required to manage multiple nodes*
* Rename `sample-RTL-Config.json` file to `RTL-Config.json`.
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`.
* Locate the complete path of the readable macroon file (admin.macroon) on your node and the lnd.conf file.
* Modify the `RTL-Config.json` file per the example file below
@ -85,6 +84,7 @@ Example RTL-Config.json:
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -95,26 +95,33 @@ Example RTL-Config.json:
"index": 1,
"lnNode": "LND Testnet",
"lnImplementation": "LND",
"Authentication": {
"macaroonPath": "<Complete path of the folder containing admin.macaroon for the node # 1>",
"configPath": "<Optional:Path of the lnd.conf if present locally or empty>"
"authentication": {
"macaroonPath": "<Complete path of the folder containing LND admin.macaroon for the node>",
"runePath": "<Complete path including filename for CLN rune for the node, rune format 'LIGHTNING_RUNE="your-rune"'>",
"lnApiPassword": "<Can be used to provide password in ECL implementation>",
"swapMacaroonPath": "<Complete path of the folder containing Loop's loop.macaroon for the node>",
"boltzMacaroonPath": "<Complete path of the folder containing Boltz admin.macaroon for the node>",
"configPath": "<Optional:Path of the .conf if present locally or empty>",
},
"Settings": {
"settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
"channelBackupPath": "C:\\RTL\\backup\\node-1",
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"enableLogging": true,
"logLevel": "INFO",
"fiatConversion": false,
"lnServerUrl": "<url for LND REST APIs for node #1 e.g. https://192.168.0.1:8080/v1>",
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. http://localhost:8081/v1>"
"unannouncedChannels": false,
"lnServerUrl": "<url for LND REST APIs for node #1 e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. https://127.0.0.1:8081>",
"boltzServerUrl": "<url for boltz server REST APIs for the node. e.g. https://127.0.0.1:9003>",
"blockExplorerUrl": "<url for local or centralized block explorer. e.g. https://mempool.space>"
}
}
]
}
```
For details on all the configuration options refer to [this page](./docs/Application_configurations).
For details on all the configuration options refer to [this page](./docs/Application_configurations.md).
#### User Authentication on RTL
RTL requires the user to be authenticated by the application first, before allowing access to LND functions.
@ -127,7 +134,7 @@ Run the following command:
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`
`$ Server is up and running, please open the UI at http://localhost:3000 or your proxy configured url.`
#### Optional: Running RTL as a service (Rpi or Linux platform users)
In case you are running a headless Rpi or a Linux node, you can configure RTL as a service.
@ -172,11 +179,11 @@ Open your browser at the following address: http://localhost:3000 to access the
* Determine the IP address of your node to access the application.
E.g. if the IP address of your node is 192.168.0.15 then open your browser at the following address: http://192.168.0.15:3000 to access RTL.
3. Config tweaks for running RTL server and LND on separate devices on the same network can be found [here](docs/RTL_setups.md).
3. Config tweaks for running RTL server and LND on separate devices on the same network can be found [here](./docs/RTL_setups.md).
4. Any Other setup: **Please be advised, if you are accessing your node remotely via RTL, its critical to encrypt the communication via use of https. You can use solutions like nginx and letsencrypt or TOR to setup secure access for RTL.**
- Sample SSL setup guide can be found [here](docs/RTL_SSL_setup.md)
- (For advanced users) A sample SSL guide to serve remote access over an encrypted Tor connection can be found [here](docs/RTL_TOR_setup.md)
- Sample SSL setup guide can be found [here](./docs/RTL_SSL_setup.md)
- (For advanced users) A sample SSL guide to serve remote access over an encrypted Tor connection can be found [here](./docs/RTL_TOR_setup.md)
### <a name="trouble"></a>Troubleshooting
In case you are running into issues with the application or if you have feedback, feel free to open issues on our github repo.

@ -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

@ -14,3 +14,20 @@ Channel backups will be taken at the following instances:
- Server startup: RTL will automatically execute all channel backup, everytime the server is started.
- Channel open/close: RTL will automatically execute all channel backup, everytime a new channel is opened or an existing channel is closed.
- Manual backups: A user can also manually execute the backup commands. The menu options are available on the `Backup` page under `Channels`. User can take all channel backups or individual channel backups as well as verify all/individual channel backup files.
#### Process to restore from the channel backup files
##### Assumptions
- The node has been restored with a LND's cipher seed mnemonic.
- RTL generated channel backup file/s is available (all channel backup file is `channel-all.bak`).
##### Steps to recover the funds
- Create a `restore` folder in your backup location folder, as specified in the RTL config file.
- Place the channel backup file in the `restore` folder.
- Access the `Restore` page under the `Channels` section of RTL.
- RTL will list the options to restore funds from the all channel file or individual channel backup file.
- Click on the `Restore` button on the grid to restore the funds.
- Once the restore function is executed successfully, RTL will rename the backup file and it will no longer be visible on the page.
- Restore function will force close the channels and recover the funds from them.
- The pending close channels can be viewed on the Pending page under `Channels`.
- The corresponding pending on-chain transactions can also be viewed on the Transactions list under the `On-Chain` page.
- Once the transactions are confirmed, the channels funds will be restored to your LND Wallet.

@ -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.

@ -16,16 +16,22 @@ This step is only required to configure the nodes, which will be remotely connec
4. Restart LND
#### 2. Configure 'RTL-Config.json'
1. Rename the `sample-RTL-Config.json` on the root RTL location to `RTL-Config.json`
1. Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`.
2. Set `multiPass` to the preferred password. This password will be used to authenticate the user for RTL. Once authenticated, the user will be able to access all the nodes configured in the json file
3. Set the `port` to the preferred port number over which to run RTL
4. Set the `defaultNodeIndex` to configure the default start up node at server restart
5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
7. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
8. `lnServerUrl` must be set to the service url for LND/C Lightining REST APIs for each node, with the unique ip address of the node hosting lnd/clightning e.g. https://192.168.0.1:8080/v1 OR https://192.168.0.1:3001/v1. In this case the ip address of the node hosting lnd/clightning is '192.168.0.1'
9. `swapServerUrl` must be set to the swap service url. e.g. http://localhost:8081/v1.
10. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
5. `dbDirectoryPath` should be set to the folder where RTL's database will be saved.
6. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
7. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
8. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
9. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop.
10. `boltzMacaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for boltz swaps.
11. `lnServerUrl` must be set to the service url for LND/Core Lightining REST APIs for each node, with the unique ip address of the node hosting LND/Core Lightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting LND/Core Lightning is '192.168.0.1'
12. `swapServerUrl` must be set to the swap service url. e.g. https://127.0.0.1:8081.
13. `boltzServerUrl` must be set to the boltz service url. e.g. https://127.0.0.1:9003.
14. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
15. `lnApiPassword` is mandatory if the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations.
16. `runePath` is mandatory for CLN implementation. It should be set to the local path of the folder including filename containing rune value. This rune value in the file should be saved in `LIGHTNING_RUNE="your-rune"` format.
#### 3. Restart RTL

@ -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!

@ -15,6 +15,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"multiPass": "<password in plain text, Default 'password'>",
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -25,20 +26,25 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"index": 1,
"lnNode": "LND Testnet",
"lnImplementation": "LND",
"Authentication": {
"authentication": {
"macaroonPath": "<Path of the folder containing 'admin.macaroon' on the device running RTL>",
"swapMacaroonPath": "<Path of the folder containing 'loop.macaroon' on the device running RTL>",
"boltzMacaroonPath": "<Path of the folder containing 'admin.macaroon' on the device running RTL>",
"configPath": "<Optional:Path of the lnd.conf if present locally or empty>"
},
"Settings": {
"settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
"channelBackupPath": "<RTL Root path + \backup\node-1>",
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"enableLogging": false,
"logLevel": "INFO",
"fiatConversion": false,
"lnServerUrl": "<https://<ip-address-of-device-running-lnd>:8080/v1; e.g. https://192.168.0.1:8080/v1>",
"swapServerUrl": "<http://<localhost>:8081/v1>",
"unannouncedChannels": false,
"lnServerUrl": "<https://<ip-address-of-device-running-lnd>:8080; e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<https://<localhost>:8081>",
"boltzServerUrl": "<https://<localhost>:9003>",
"blockExplorerUrl": "<Default: https://mempool.space>"
}
}
]

@ -1,4 +1,4 @@
[Intro](../README.md) -- [Application Features](Application_features.md) -- **Road Map** -- [LND API Coverage](LNDAPICoverage.md) -- [Application Configurations](Application_configurations)
[Intro](../README.md) -- [Application Features](Application_features.md) -- **Road Map** -- [Application Configurations](Application_configurations.md)
# Product Roadmap for RTL Application
@ -12,23 +12,11 @@ We believe UX improvement is a never-ending cycle. And, we must keep the UI/UX f
### Automated Testing
As the functional complexity increases, we need to add automated testing to ensure quality and less bugs. Another area, where developer contribution is more than welcome.
### LND Loop Integration
LND's Loop Out and Loop In are important tools for channel re-balancing and will be integrated with RTL UI in the near future.
* Loop Out
![](../screenshots/Loop-Out-info.png)
* Loop In
![](../screenshots/Loop-In-info.png)
### Eclair
Support [Eclair](https://github.com/ACINQ/eclair) implementation.
### Advanced Node Monitoring
Active node monitoring may be required to ensure reliability of routing nodes. Monitoring can include generating alerts for out-of-balance channels, inactive channels, disconnected peers, low activity channels etc. This feature will be required for professional node operaters running commercial routing nodes with a need to react to signals, requiring specific action to be taken.
### Advanced Multi-node Management
RTL currently allows managing multiple nodes (LND or C-Lightning), via single UI. More sophistication can be built on multi-node management, with advanced top level dashboards, which summarize node level summary in a single dashboard. This feature may be required for professional node operators, who are running commercial routing nodes.
RTL currently allows managing multiple nodes (LND or Core Lightning or Eclair), via single UI. More sophistication can be built on multi-node management, with advanced top level dashboards, which summarize node level summary in a single dashboard. This feature may be required for professional node operators, who are running commercial routing nodes.
### RTL installer
Automate RTL setup so that installation process is simpler than the current method of following the steps provided in the Readme file. This should also help with configuration of nginx and letsencrypt, to enable access via https. Contribution on this is more than welcome.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

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 }}

20
.gitignore vendored

@ -6,6 +6,9 @@
# dependencies
/node_modules
/node_modules_old
/node_modules_prod
/node_modules_dev
# IDEs and editors
/.idea
@ -24,19 +27,24 @@
!.vscode/extensions.json
# misc
/.angular/cache
/.sass-cache
/connect.lock
/coverage
/db
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
channels-backup
logs
cookies
# System Files
.DS_Store
Thumbs.db
/database/*
/logs/*
/cookies/*
/backup/*
@ -49,3 +57,13 @@ RTL.conf
RTL-1.conf
RTL-Multi-Node-Conf-1.json
RTL-Config-for-BTC-Testing.json
ECLDummyData.log
_config.yml
.vscode/launch.json
RTL-Config-Docker.json
dockerfiles/RTL-Config.json
dockerfiles/.env
RTL-Config-Regtest.json
RTL-Config-Signet.json
RTL-Config-Testnet.json
RTL-Config-All.json

@ -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,16 +1,36 @@
FROM node:10-alpine
ARG BASE_DISTRO="node:alpine"
RUN apk add --no-cache tini
FROM --platform=${BUILDPLATFORM} ${BASE_DISTRO} as builder
WORKDIR /RTL
COPY package.json /RTL/package.json
COPY package-lock.json /RTL/package-lock.json
# Install dependencies
RUN npm install --only=prod
RUN npm install --legacy-peer-deps
COPY . .
# Build the Angular application
RUN npm run buildfrontend
# Build the Backend from typescript server
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --omit=dev --legacy-peer-deps
FROM --platform=$BUILDPLATFORM ${BASE_DISTRO} as runner
RUN apk add --no-cache tini
WORKDIR /RTL
COPY . /RTL
COPY --from=builder /RTL/rtl.js ./rtl.js
COPY --from=builder /RTL/package.json ./package.json
COPY --from=builder /RTL/frontend ./frontend
COPY --from=builder /RTL/backend ./backend
COPY --from=builder /RTL/node_modules/ ./node_modules
EXPOSE 3000

@ -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

@ -4,133 +4,118 @@
"newProjectRoot": "projects",
"projects": {
"RTLApp": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "rtl",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "rtl",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "angular",
"baseHref": "/rtl/",
"outputPath": "frontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"zone.js",
"src/polyfills.ts"
],
"tsConfig": "src/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
],
"styles": [
"src/app/shared/theme/styles/styles.scss"
"src/app/shared/theme/styles/styles.scss",
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
],
"scripts": []
"scripts": [],
"allowedCommonJsDependencies": [
"buffer",
"rfdc",
"sha256",
"qrcode",
"otplib",
"pdfmake/build/pdfmake",
"pdfmake/build/vfs_fonts"
]
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "4mb",
"maximumError": "5mb"
"maximumWarning": "20mb",
"maximumError": "50mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20mb",
"maximumError": "50mb"
}
]
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "RTLApp:build"
},
"configurations": {
"production": {
"browserTarget": "RTLApp:build:production"
"buildTarget": "RTLApp:build:production"
},
"development": {
"buildTarget": "RTLApp:build:development"
}
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "RTLApp:build"
"buildTarget": "RTLApp:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"src/app/shared/theme/styles/styles.scss"
"polyfills": [
"zone.js",
"zone.js/testing",
"src/polyfills.ts"
],
"scripts": [],
"tsConfig": "src/tsconfig.spec.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"RTLApp-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "RTLApp:serve"
},
"configurations": {
"production": {
"devServerTarget": "RTLApp:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
"styles": [
"src/app/shared/theme/styles/styles.scss",
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
],
"scripts": []
}
}
}
}
},
"defaultProject": "RTLApp"
}
"cli": {
"analytics": false
}
}

File diff suppressed because one or more lines are too long

@ -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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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()}([]);

File diff suppressed because one or more lines are too long

@ -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 });
});
};

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save