Compare commits

...

100 Commits

Author SHA1 Message Date
ShahanaFarooqui b6dbd23ae7
Lint Fix (#1408)
Lint bug Fix
6 days ago
ShahanaFarooqui f01815bd04
Circle CI build and lint fixes (#1407) 6 days 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>
6 days ago
saubyk ea7f300360 Nodejs update for CI & Docker and updated documentation 6 months ago
ShahanaFarooqui 43a46b65e1 Lint fix 6 months ago
ShahanaFarooqui 475b47b7ea
Release 0.15.0 (#1334)
c-lightning-REST to clnrest migration.
6 months ago
ShahanaFarooqui d083be1196
Merge pull request #1314 from Ride-The-Lightning/Release-0.14.1
Release 0.14.1
8 months ago
ShahanaFarooqui ec58a24ed4
Merge pull request #1310 from Ride-The-Lightning/ng-lint
Codebase Linting
8 months ago
ShahanaFarooqui 82d87b32c1 Codebase Linting 8 months ago
ShahanaFarooqui 13514c7a62
Merge pull request #1308 from Ride-The-Lightning/ng-16
Updated to Angular 16 and removed vulnerabilities
8 months ago
ShahanaFarooqui e4a2ef9a21 Updated to Angular 16 and removed vulnerabilities 8 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
8 months ago
ShahanaFarooqui 14fd866d1a Bug fix: Manage button link filter #1294 and Page Settings Error 8 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 10 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

BIN
.DS_Store vendored

Binary file not shown.

@ -24,7 +24,6 @@ typings/
_config.yml
.classpath
.DS_Store
.eslintrc.json
.gitattributes
.gitignore
.idea

@ -1,7 +1,7 @@
{
"root": true,
"ignorePatterns": [
"src/**/*.js"
"backend/**/*.js"
],
"overrides": [
{
@ -20,17 +20,21 @@
"extends": [
"plugin:@angular-eslint/all",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/recommended--extra"
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"deprecation/deprecation": "error",
"@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": ["error", { "multiline": { "delimiter": "semi", "requireLast": true}, "singleline": { "delimiter": "comma", "requireLast": false }}],
"@typescript-eslint/member-delimiter-style": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/type-annotation-spacing": "error",
"@typescript-eslint/type-annotation-spacing": 0,
"quotes": ["error", "single"],
"comma-dangle": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
@ -124,7 +128,7 @@
"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": 6 }],
"max-depth": ["error", { "max": 7 }],
"max-nested-callbacks": "error",
"max-statements-per-line": ["error", { "max": 3 }],
"no-array-constructor": "error",
@ -188,19 +192,22 @@
],
"rules": {
"@angular-eslint/arrow-body-style": "off",
"@angular-eslint/component-selector": ["error", { "prefix": "rtl", "style": "kebab-case", "type": "element" }],
"@angular-eslint/directive-selector": ["error", { "style": "camelCase", "type": "attribute" }],
"@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"
}
}

18
.github/README.md vendored

@ -35,6 +35,7 @@ RTL is available on the below platforms/services:
* [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
@ -54,7 +55,7 @@ To download from master (*not recommended*):
```
$ git clone https://github.com/Ride-The-Lightning/RTL.git
$ cd RTL
$ npm install --omit=dev
$ npm install --omit=dev --legacy-peer-deps
```
#### Or: Update existing dependencies
```
@ -62,7 +63,7 @@ $ cd RTL
$ git reset --hard HEAD
$ git clean -f -d
$ git pull
$ npm install --omit=dev
$ npm install --omit=dev --legacy-peer-deps
```
#### Error on npm install
@ -83,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": "",
@ -93,14 +95,15 @@ Example RTL-Config.json:
"index": 1,
"lnNode": "LND Testnet",
"lnImplementation": "LND",
"Authentication": {
"macaroonPath": "<Complete path of the folder containing LND's admin.macaroon for the node # 1>",
"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>",
"lnApiPassword": "<Optional:Can be used to provide password in ECL implementation>"
},
"Settings": {
"settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
@ -111,7 +114,8 @@ Example RTL-Config.json:
"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>"
"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>"
}
}
]

@ -9,36 +9,39 @@ parameters have `default` values for initial setup and can be updated after RTL
"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), default 0, Required>,
"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, Default 'Node 1', Required>",
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL. Default 'LND', Required>",
"Authentication": {
"macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN>",
"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>",
"lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>"
},
"Settings": {
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT, OPERATOR. Default MERCHANT, Required>",
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Required>",
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK, YELLOW. Default PURPLE, Required>",
"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, Required>,
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
"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', Required",
"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>"
"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>"
}
}
]
@ -49,15 +52,17 @@ parameters have `default` values for initial setup and can be updated after RTL
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, Required)<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', Required)<br />
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080) (Required)<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' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN)<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 />

@ -1,71 +0,0 @@
### Core Lightning Commands Covered on RTL
=== bitcoin ===
- [x] feerates
- [x] newaddr
- [ ] txdiscard
- [ ] txprepare
- [ ] txsend
- [x] withdraw
=== channels ===
- [x] close
- [ ] fundchannel_cancel
- [ ] fundchannel_complete
- [ ] fundchannel_start
- [x] getroute
- [x] listchannels
- [x] listforwards
- [x] setchannelfee
=== network ===
- [x] connect
- [x] disconnect
- [x] listnodes
- [x] listpeers
- [ ] ping
=== payment ===
- [ ] createonion
- [x] decodepay
- [x] delexpiredinvoice
- [ ] delinvoice
- [x] invoice
- [x] listinvoices
- [x] listsendpays
- [ ] listtransactions
- [ ] sendonion
- [ ] sendpay
- [ ] waitanyinvoice
- [ ] waitinvoice
- [ ] waitsendpay
=== plugin ===
- [ ] autocleaninvoice
- [ ] estimatefees
- [x] fundchannel
- [ ] getchaininfo
- [ ] getrawblockbyheight
- [ ] getutxout
- [x] listpays
- [x] pay
- [ ] paystatus
- [ ] plugin
- [ ] sendrawtransaction
=== utility ===
- [ ] check
- [x] checkmessage
- [x] getinfo
- [ ] getlog
- [ ] getsharedsecret
- [ ] help
- [ ] listconfigs
- [x] listfunds
- [x] signmessage
- [ ] stop
- [ ] waitblockheight
=== developer ===
- [ ] dev-listaddrs
- [ ] dev-rescan-outputs

@ -1,4 +1,4 @@
![](../screenshots/RTL-CLN-Dashboard.png)
![](../screenshots/RTL-CLN-Arch-3.png)
## RTL Core lightning setup
@ -10,15 +10,16 @@
* [Start the server and access the app](#start)
### <a name="intro"></a>Introduction
RTL is now enabled to manage lightning nodes running Core Lightning.
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.
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. Cl-REST - Ensure that `cl-rest` API server is installed and running. Install instructions [here](https://github.com/Ride-The-Lightning/c-lightning-REST)
4. Copy the `access.macaroon` file from `cl-rest` to the device, on which RTL will be installed
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)
@ -32,7 +33,7 @@ To download from master (*not recommended*):
```
$ git clone https://github.com/Ride-The-Lightning/RTL.git
$ cd RTL
$ npm install --omit=dev
$ npm install --omit=dev --legacy-peer-deps
```
#### Or: Update existing build
@ -41,30 +42,31 @@ $ cd RTL
$ git reset --hard HEAD
$ git clean -f -d
$ git pull
$ npm install --omit=dev
$ 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 `access.macaroon` from `cl-rest` on your node.
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.
* `macaroonPath` - Path of the folder containing `access.macaroon` file from cl-rest server.
* `lnServerUrl` - complete url with ip address and port of the cl-rest 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.
* `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": "",
@ -75,11 +77,11 @@ Ensure that the follow values are correct per your config:
"index": 1,
"lnNode": "Core Lightning Testnet # 1",
"lnImplementation": "CLN",
"Authentication": {
"macaroonPath": "<Modify to include the path of the folder with access.macaroon>",
"authentication": {
"runePath": "<Modify to include the path of the folder including filename which contains `rune`>",
"configPath": "<Optional - Config file path for core lightning>"
},
"Settings": {
"settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
@ -87,7 +89,8 @@ Ensure that the follow values are correct per your config:
"logLevel": "INFO",
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "https://<cl-rest api server ip address>:3001"
"lnServerUrl": "https://<CLNRest api server ip address>:3001",
"blockExplorerUrl": "<Default: https://mempool.space>"
}
}
]
@ -100,9 +103,9 @@ 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 or your proxy configured url.`
`$ 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.
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.
For detailed config and access options and other information, view the main readme page

@ -28,7 +28,7 @@ To download from master (*not recommended*) follow the below instructions:
```
$ git clone https://github.com/Ride-The-Lightning/RTL.git
$ cd RTL
$ npm install --omit=dev
$ npm install --omit=dev --legacy-peer-deps
```
#### Or: Update existing build
```
@ -36,7 +36,7 @@ $ cd RTL
$ git reset --hard HEAD
$ git clean -f -d
$ git pull
$ npm install --omit=dev
$ npm install --omit=dev --legacy-peer-deps
```
#### Error on npm install
@ -60,6 +60,7 @@ Ensure that the follow values are correct per your config:
"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": "",
@ -70,11 +71,11 @@ Ensure that the follow values are correct per your config:
"index": 1,
"lnNode": "Eclair Testnet # 1",
"lnImplementation": "ECL",
"Authentication": {
"authentication": {
"configPath": "<Optional - Config file path, including .conf file>",
"lnApiPassword": "<Mandatory if the configPath is missing - Password used for API authentication>",
},
"Settings": {
"settings": {
"userPersona": "OPERATOR",
"themeMode": "DAY",
"themeColor": "PURPLE",
@ -82,7 +83,8 @@ Ensure that the follow values are correct per your config:
"logLevel": "INFO",
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "http://<eclair api server ip address>:port"
"lnServerUrl": "http://<eclair api server ip address>:port",
"blockExplorerUrl": "<Default: https://mempool.space>"
}
}
]

@ -1,57 +0,0 @@
[Intro](../README.md) -- [Application Features](Application_features.md) -- [Road Map](Roadmap.md) -- **LND API Coverage** -- [Application Configurations](Application_configurations.md)
- [x] GenSeed
- [x] InitWallet
- [x] UnlockWallet
- [ ] ChangePassword
- [x] WalletBalance
- [x] ChannelBalance
- [x] GetTransactions
- [ ] EstimateFee
- [x] SendCoins
- [ ] ListUnspent
- [ ] SubscribeTransactions
- [ ] SendMany
- [x] NewAddress
- [x] SignMessage
- [x] VerifyMessage
- [x] ConnectPeer
- [x] DisconnectPeer
- [x] ListPeers
- [x] GetInfo
- [x] PendingChannels
- [x] ListChannels
- [ ] SubscribeChannelEvents
- [x] ClosedChannels
- [ ] OpenChannelSync
- [x] OpenChannel
- [x] CloseChannel
- [ ] AbandonChannel
- [x] SendPayment
- [ ] SendPaymentSync
- [ ] SendToRoute
- [ ] SendToRouteSync
- [x] AddInvoice
- [x] ListInvoices
- [ ] LookupInvoice
- [ ] SubscribeInvoices
- [x] DecodePayReq
- [x] ListPayments
- [ ] DeleteAllPayments
- [ ] DescribeGraph
- [x] GetChanInfo
- [x] GetNodeInfo
- [x] QueryRoutes
- [x] GetNetworkInfo
- [ ] StopDaemon
- [ ] SubscribeChannelGraph
- [ ] DebugLevel
- [x] FeeReport
- [x] UpdateChannelPolicy
- [x] ForwardingHistory
- [x] ExportChannelBackup
- [x] ExportAllChannelBackups
- [x] VerifyChanBackup
- [x] RestoreChannelBackups
- [ ] SubscribeChannelBackups
- [ ] Messages

@ -20,16 +20,18 @@ This step is only required to configure the nodes, which will be remotely connec
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. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop.
9. `boltzMacaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for boltz swaps.
10. `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'
11. `swapServerUrl` must be set to the swap service url. e.g. https://127.0.0.1:8081.
12. `boltzServerUrl` must be set to the boltz service url. e.g. https://127.0.0.1:9003.
13. `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.
14. `lnApiPassword` is mandatory in 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.
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

@ -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,13 +26,13 @@ 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",
@ -42,7 +43,8 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"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>"
"boltzServerUrl": "<https://<localhost>:9003>",
"blockExplorerUrl": "<Default: https://mempool.space>"
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

@ -22,7 +22,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
node-version: 18.x
- name: Cache node_modules
uses: actions/cache@v2
@ -46,7 +46,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
node-version: 18.x
- name: Cache node_modules
uses: actions/cache@v2
@ -59,12 +59,9 @@ jobs:
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci --legacy-peer-deps
- name: Lint Scripts
- name: Lint Src and Server
run: npm run lint
- name: Lint Server Script
run: npm run lintServer
test:
name: Test
runs-on: ubuntu-latest
@ -78,7 +75,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
node-version: 18.x
- name: Cache node_modules
uses: actions/cache@v2

@ -23,7 +23,7 @@ jobs:
- name: Setup Node
uses: actions/setup-node@v2
with:
node-version: 16.x
node-version: 18.x
- name: Cache node_modules
uses: actions/cache@v2

@ -1,23 +0,0 @@
name: Pull Request Stats
on:
push:
branches: [ master, 'Release-*' ]
tags: [ 'v*' ]
release:
types: [released]
# Triggers the workflow only when merging pull request to the branches.
pull_request:
types: [opened, closed]
branches: [ master, 'Release-*', '*' ]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
stats:
runs-on: ubuntu-latest
steps:
- name: Run pull request stats
uses: flowwer-dev/pull-request-stats@master
with:
period: 365

@ -0,0 +1,13 @@
{
"eslint.enable": true,
"eslint.validate": [
"typescript",
"HTML"
],
"eslint.options": {
"configFile": ".eslintrc.json"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
}
}

@ -2,6 +2,7 @@
"multiPass": "password",
"port": "3000",
"defaultNodeIndex": 1,
"dbDirectoryPath": "C:\\Users\\xyz\\RTL",
"SSO": {
"rtlSSO": 0,
"rtlCookiePath": "",
@ -12,13 +13,13 @@
"index": 1,
"lnNode": "Node 1",
"lnImplementation": "LND",
"Authentication": {
"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": {
"settings": {
"userPersona": "MERCHANT",
"themeMode": "DAY",
"themeColor": "PURPLE",
@ -28,7 +29,8 @@
"swapServerUrl": "https://127.0.0.1:8081",
"boltzServerUrl": "https://127.0.0.1:9003",
"fiatConversion": false,
"unannouncedChannels": false
"unannouncedChannels": false,
"blockExplorerUrl": "https://mempool.space"
}
}
]

@ -77,10 +77,10 @@
"builder": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"browserTarget": "RTLApp:build:production"
"buildTarget": "RTLApp:build:production"
},
"development": {
"browserTarget": "RTLApp:build:development"
"buildTarget": "RTLApp:build:development"
}
},
"defaultConfiguration": "development"
@ -88,7 +88,7 @@
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "RTLApp:build"
"buildTarget": "RTLApp:build"
}
},
"test": {
@ -111,17 +111,11 @@
],
"scripts": []
}
},
"lint": {
"builder": "@angular-eslint/builder:lint",
"options": {
"lintFilePatterns": [
"src/**/*.ts",
"src/**/*.html"
]
}
}
}
}
}
},
"cli": {
"analytics": false
}
}

@ -1,30 +0,0 @@
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 getBalance = (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.ln_server_url + '/v1/getBalance';
request(options).then((body) => {
if (!body.totalBalance) {
body.totalBalance = 0;
}
if (!body.confBalance) {
body.confBalance = 0;
}
if (!body.unconfBalance) {
body.unconfBalance = 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 });
});
};

@ -1,31 +1,29 @@
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 listChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
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.ln_server_url + '/v1/channel/listChannels';
request(options).then((body) => {
body?.map((channel) => {
if (!channel.alias || channel.alias === '') {
channel.alias = channel.id.substring(0, 20);
}
const local = (channel.msatoshi_to_us) ? channel.msatoshi_to_us : 0;
const remote = (channel.msatoshi_to_them) ? channel.msatoshi_to_them : 0;
const total = channel.msatoshi_total ? channel.msatoshi_total : 0;
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
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 || []);
});
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channels List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'List Channels Error', req.session.selectedNode);
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 });
});
};
@ -35,7 +33,7 @@ export const openChannel = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/openChannel';
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) => {
@ -52,7 +50,7 @@ export const setChannelFee = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/setChannelFee';
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) => {
@ -70,10 +68,10 @@ export const closeChannel = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const unilateralTimeoutQuery = req.query.force ? '?unilateralTimeout=1' : '';
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/closeChannel/' + req.params.channelId + unilateralTimeoutQuery;
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.delete(options).then((body) => {
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) => {
@ -81,37 +79,19 @@ export const closeChannel = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const getLocalRemoteBalance = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Local & Remote Balances..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/localremotebal';
request(options).then((body) => {
if (!body.localBalance) {
body.localBalance = 0;
}
if (!body.remoteBalance) {
body.remoteBalance = 0;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Local Remote Balance Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Local Remote Balance 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.ln_server_url + '/v1/channel/listForwards?status=' + (req.query.status ? req.query.status : 'settled');
request.get(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.query.status, data: body });
res.status(200).json(body);
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 });
@ -123,38 +103,14 @@ export const funderUpdatePolicy = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/funderUpdate';
if (req.body && req.body.policy) {
options.body = req.body;
}
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 });
body.channel_fee_max_base_msat = (body.channel_fee_max_base_msat && typeof body.channel_fee_max_base_msat === 'string' && body.channel_fee_max_base_msat.includes('msat')) ? +body.channel_fee_max_base_msat?.replace('msat', '') : body.channel_fee_max_base_msat;
body.lease_fee_base_msat = (body.lease_fee_base_msat && typeof body.lease_fee_base_msat === 'string' && body.lease_fee_base_msat.includes('msat')) ? +body.lease_fee_base_msat?.replace('msat', '') : body.channel_fee_max_base_msat;
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 });
});
};
export const listForwardsPaginated = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Paginated List Forwards..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const { status, maxLen, offset } = req.query;
let queryStr = '?status=' + (status ? status : 'settled');
queryStr = queryStr + '&maxLen=' + (maxLen ? maxLen : '10');
queryStr = queryStr + '&offset=' + (offset ? offset : '0');
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listForwardsPaginated' + queryStr;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Paginated Forwarding History url' + options.url });
request.get(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Paginated Forwarding History Received For Status ' + req.query.status, data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Paginated Forwarding History Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

@ -1,24 +0,0 @@
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 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.ln_server_url + '/v1/getFees';
request(options).then((body) => {
if (!body.feeCollected) {
body.feeCollected = 0;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body });
res.status(200).json(body);
}).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 });
});
};

@ -1,5 +1,4 @@
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { CLWSClient } from './webSocketClient.js';
@ -7,7 +6,6 @@ let options = null;
const logger = Logger;
const common = Common;
const clWsClient = CLWSClient;
const databaseService = Database;
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);
@ -16,56 +14,44 @@ export const getInfo = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.ln_node });
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.macaroon) {
const errMsg = 'Core lightning get info failed due to bad or missing macaroon!';
const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode);
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(options).then((body) => {
const body_str = (!body) ? '' : JSON.stringify(body);
const search_idx = (!body) ? -1 : body_str.search('Not Found');
if (!body || search_idx > -1 || body.error) {
if (body && !body.error) {
body.error = 'Error From Server!';
}
const err = common.handleError(body, 'GetInfo', 'Get Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
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 {
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.api_version = body.api_version || '';
req.session.selectedNode.ln_version = body.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);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
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 });

@ -10,9 +10,9 @@ export const deleteExpiredInvoice = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const queryStr = req.query.maxExpiry ? '?maxexpiry=' + req.query.maxExpiry : '';
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/delExpiredInvoice' + queryStr;
request.delete(options).then((body) => {
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) => {
@ -26,10 +26,10 @@ export const listInvoices = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const labelQuery = req.query.label ? '?label=' + req.query.label : '';
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/listInvoices' + labelQuery;
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(options).then((body) => {
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) => {
@ -43,7 +43,7 @@ export const addInvoice = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/genInvoice';
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 });

@ -10,38 +10,28 @@ export const getRoute = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/network/getRoute/' + req.params.destPubkey + '/' + req.params.amount;
request(options).then((body) => {
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 });
res.status(200).json({ routes: 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 listNode = (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.ln_server_url + '/v1/network/listNode/' + req.params.id;
request(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 listChannel = (req, res, next) => {
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.ln_server_url + '/v1/network/listChannel/' + req.params.channelShortId;
request(options).then((body) => {
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) => {
@ -50,14 +40,16 @@ export const listChannel = (req, res, next) => {
});
};
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.ln_server_url + '/v1/network/feeRates/' + req.params.feeRateStyle;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Fee Rates Received for ' + req.params.feeRateStyle, data: body });
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);
@ -65,28 +57,43 @@ export const feeRates = (req, res, next) => {
});
};
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 });
}
const queryStr = req.query.liquidity_ads ? '?liquidity_ads=' + req.query.liquidity_ads : '';
options.url = req.session.selectedNode.ln_server_url + '/v1/network/listNodes' + queryStr;
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(options).then((body) => {
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
body.forEach((node) => {
if (node.option_will_fund) {
node.option_will_fund.lease_fee_base_msat = (node.option_will_fund.lease_fee_base_msat && typeof node.option_will_fund.lease_fee_base_msat === 'string' &&
node.option_will_fund.lease_fee_base_msat.includes('msat')) ? node.option_will_fund.lease_fee_base_msat?.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.channel_fee_max_base_msat = (node.option_will_fund.channel_fee_max_base_msat && typeof node.option_will_fund.channel_fee_max_base_msat === 'string' &&
node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? node.option_will_fund.channel_fee_max_base_msat?.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
}
return node;
});
res.status(200).json(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;
});
};

@ -18,10 +18,11 @@ export const listOfferBookmarks = (req, res, next) => {
});
};
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, req.params.offerStr).then((deleteRes) => {
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(req.params.offerStr);
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 });
@ -33,15 +34,9 @@ export const listOffers = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/offers/listoffers';
if (req.query.offer_id) {
options.url = options.url + '?offer_id=' + req.query.offer_id;
}
if (req.query.active_only) {
options.url = options.url + '?active_only=' + req.query.active_only;
}
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(options).then((body) => {
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) => {
@ -55,7 +50,7 @@ export const createOffer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/offers/offer';
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 });
@ -71,7 +66,7 @@ export const fetchOfferInvoice = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/offers/fetchInvoice';
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) => {
@ -88,8 +83,9 @@ export const disableOffer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/offers/disableOffer/' + req.params.offerID;
request.delete(options).then((body) => {
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) => {

@ -10,8 +10,9 @@ export const getNewAddress = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/newaddr?addrType=' + req.query.type;
request(options).then((body) => {
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) => {
@ -25,7 +26,7 @@ export const onChainWithdraw = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/withdraw';
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) => {
@ -42,10 +43,47 @@ export const getUTXOs = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/listFunds';
request(options).then((body) => {
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 });
res.status(200).json(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 });

@ -7,6 +7,18 @@ 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) {
@ -22,8 +34,8 @@ function paymentReducer(accumulator, currentPayment) {
}
function summaryReducer(accumulator, mpp) {
if (mpp.status === 'complete') {
accumulator.msatoshi = accumulator.msatoshi + mpp.msatoshi;
accumulator.msatoshi_sent = accumulator.msatoshi_sent + mpp.msatoshi_sent;
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) {
@ -47,10 +59,10 @@ function groupBy(payments) {
delete temp.partid;
}
else {
const paySummary = curr?.reduce(summaryReducer, { msatoshi: 0, msatoshi_sent: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
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, msatoshi: paySummary.msatoshi, msatoshi_sent: paySummary.msatoshi_sent, created_at: curr[0].created_at,
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) {
@ -69,50 +81,85 @@ export const listPayments = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/pay/listPayments';
request(options).then((body) => {
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 });
res.status(200).json(groupBy(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 });
}
if (req.body.paymentType === 'KEYSEND') {
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.ln_server_url + '/v1/pay/keysend';
options.body = req.body;
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 (req.body.paymentType === 'OFFER') {
if (paymentType === 'OFFER') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' });
options.body = { invoice: req.body.invoice };
}
else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' });
options.body = req.body;
}
options.url = req.session.selectedNode.ln_server_url + '/v1/pay';
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 (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB && req.body.bolt12) {
const offerToUpdate = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.vendor) {
offerToUpdate['vendor'] = req.body.vendor;
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 (req.body.description) {
offerToUpdate['description'] = req.body.description;
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, req.body.bolt12).then((updatedOffer) => {
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) => {
@ -128,10 +175,10 @@ export const postPayment = (req, res, next) => {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}
}
if (req.body.paymentType === 'INVOICE') {
if (paymentType === 'INVOICE') {
return res.status(201).json({ paymentResponse: body, saveToDBResponse: 'NA' });
}
if (req.body.paymentType === 'KEYSEND') {
if (paymentType === 'KEYSEND') {
return res.status(201).json(body);
}
}).catch((errRes) => {

@ -1,6 +1,7 @@
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;
@ -10,15 +11,14 @@ export const getPeers = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/listPeers';
request(options).then((body) => {
body.forEach((peer) => {
if (!peer.alias || peer.alias === '') {
peer.alias = peer.id.substring(0, 20);
}
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 || []);
});
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers with Alias Received', data: body });
res.status(200).json(body || []);
}).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 });
@ -30,13 +30,14 @@ export const postPeer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/connect';
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 });
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/listPeers';
request(options).then((listPeersRes) => {
const peers = listPeersRes ? common.newestOnTop(listPeersRes, 'id', req.body.id) : [];
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) => {
@ -54,8 +55,9 @@ export const deletePeer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/peer/disconnect/' + req.params.peerId + '?force=' + req.query.force;
request.delete(options).then((body) => {
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) => {

@ -4,44 +4,15 @@ import { Common } from '../../utils/common.js';
let options = null;
const logger = Logger;
const common = Common;
export const decodePaymentFromPaymentRequest = (selNode, payment) => {
options.url = selNode.ln_server_url + '/v1/utility/decode/' + payment;
return request(options).then((res) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment Decode Received', data: res });
return res;
}).catch((err) => { });
};
export const decodePayments = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payments List..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr?.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment List Decoded', data: values });
res.status(200).json(values);
}).
catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'Decode Payments 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 Payment List Decoded' });
return res.status(200).json([]);
}
};
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.ln_server_url + '/v1/utility/decode/' + req.params.payReq;
request(options).then((body) => {
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) => {
@ -55,8 +26,8 @@ export const signMessage = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/utility/signMessage';
options.form = { message: req.body.message };
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);
@ -71,8 +42,9 @@ export const verifyMessage = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/utility/checkMessage/' + req.body.message + '/' + req.body.signature;
request.get(options, (error, response, body) => {
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) => {
@ -86,8 +58,8 @@ export const listConfigs = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/utility/listConfigs';
request(options).then((body) => {
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) => {

@ -1,6 +1,4 @@
import * as fs from 'fs';
import { join } from 'path';
import WebSocket from 'ws';
import socketIOClient from 'socket.io-client';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
@ -12,7 +10,7 @@ export class CLWebSocketClient {
this.webSocketClients = [];
this.reconnectTimeOut = null;
this.waitTime = 0.5;
this.reconnet = (clWsClt) => {
this.reconnect = (clWsClt) => {
if (this.reconnectTimeOut) {
return;
}
@ -29,14 +27,14 @@ export class CLWebSocketClient {
try {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
if (!clientExists) {
if (selectedNode.ln_server_url) {
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.ln_server_url) {
if ((!clientExists.webSocketClient || !clientExists.webSocketClient.connected) && selectedNode.settings.lnServerUrl) {
clientExists.reConnect = true;
this.connectWithClient(clientExists);
}
@ -48,47 +46,50 @@ export class CLWebSocketClient {
};
this.connectWithClient = (clWsClt) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' });
const WS_LINK = (clWsClt.selectedNode.ln_server_url)?.replace(/^http/, 'ws') + '/v1/ws';
const mcrnHexEncoded = Buffer.from(fs.readFileSync(join(clWsClt.selectedNode.macaroon_path, 'access.macaroon'))).toString('hex');
clWsClt.webSocketClient = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false });
clWsClt.webSocketClient.onopen = () => {
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.onclose = (e) => {
if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLN') {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...' });
});
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.reconnet(clWsClt);
this.reconnect(clWsClt);
}
}
};
clWsClt.webSocketClient.onmessage = (msg) => {
});
clWsClt.webSocketClient.on('message', (msg) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'DEBUG', fileName: 'CLWebSocket', msg: 'Received message from the server..', data: msg.data });
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
msg['source'] = 'CLN';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
};
clWsClt.webSocketClient.onerror = (err) => {
if (clWsClt.selectedNode.api_version === '' || !clWsClt.selectedNode.api_version || this.common.isVersionCompatible(clWsClt.selectedNode.api_version, '0.6.0')) {
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.reconnet(clWsClt);
}
}
else {
clWsClt.reConnect = false;
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.readyState === WebSocket.OPEN) {
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();

@ -1,6 +1,9 @@
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;
@ -13,20 +16,18 @@ export const simplifyAllChannels = (selNode, channels) => {
nodeId: channel.nodeId ? channel.nodeId : '',
channelId: channel.channelId ? channel.channelId : '',
state: channel.state ? channel.state : '',
announceChannel: channel.data && channel.data.commitments && channel.data.commitments.channelFlags && channel.data.commitments.channelFlags.announceChannel ? channel.data.commitments.channelFlags.announceChannel : false,
toLocal: (channel.data.commitments.localCommit.spec.toLocal) ? Math.round(+channel.data.commitments.localCommit.spec.toLocal / 1000) : 0,
toRemote: (channel.data.commitments.localCommit.spec.toRemote) ? Math.round(+channel.data.commitments.localCommit.spec.toRemote / 1000) : 0,
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 : '',
isFunder: channel.data && channel.data.commitments && channel.data.commitments.localParams && channel.data.commitments.localParams.isFunder ? channel.data.commitments.localParams.isFunder : false,
buried: channel.data && channel.data.buried ? channel.data.buried : false,
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,
feeRatePerKw: (channel.data.commitments.localCommit.spec.feeratePerKw) ? channel.data.commitments.localCommit.spec.feeratePerKw : 0,
feeProportionalMillionths: channel.data && channel.data.channelUpdate && channel.data.channelUpdate.feeProportionalMillionths ? channel.data.channelUpdate.feeProportionalMillionths : 0,
alias: ''
});
});
channelNodeIds = channelNodeIds.substring(1);
options.url = selNode.ln_server_url + '/nodes';
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) => {
@ -46,7 +47,7 @@ export const getChannels = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/channels';
options.url = req.session.selectedNode.settings.lnServerUrl + '/channels';
options.form = {};
if (req.query && req.query.nodeId) {
options.form = req.query;
@ -54,7 +55,7 @@ export const getChannels = (req, res, next) => {
}
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.ln_implementation).then((data) => { res.status(200).json(data); });
common.getDummyData('Channels', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(data); });
}
else {
request.post(options).then((body) => {
@ -82,7 +83,7 @@ export const getChannelStats = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/channelstats';
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();
@ -104,7 +105,7 @@ export const openChannel = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/open';
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) => {
@ -121,7 +122,7 @@ export const updateChannelRelayFee = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/updaterelayfee';
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) => {
@ -139,11 +140,11 @@ export const closeChannel = (req, res, next) => {
}
if (req.query.force !== 'true') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
options.url = req.session.selectedNode.ln_server_url + '/close';
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.ln_server_url + '/forceclose';
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 });
@ -156,3 +157,54 @@ export const closeChannel = (req, res, next) => {
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 } });
});
};

@ -11,23 +11,24 @@ export const arrangeFees = (selNode, body, current_time) => {
let fee = 0;
body.relayed.forEach((relayedEle) => {
fee = Math.round((relayedEle.amountIn - relayedEle.amountOut) / 1000);
if (relayedEle.timestamp) {
if (relayedEle.timestamp.unix) {
if ((relayedEle.timestamp.unix * 1000) >= day_start_time) {
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 ((relayedEle.timestamp.unix * 1000) >= week_start_time) {
if ((relayedEleTimestamp.unix * 1000) >= week_start_time) {
fees.weekly_fee = fees.weekly_fee + fee;
fees.weekly_txs = fees.weekly_txs + 1;
}
}
else {
if (relayedEle.timestamp >= day_start_time) {
if (relayedEleTimestamp >= day_start_time) {
fees.daily_fee = fees.daily_fee + fee;
fees.daily_txs = fees.daily_txs + 1;
}
if (relayedEle.timestamp >= week_start_time) {
if (relayedEleTimestamp >= week_start_time) {
fees.weekly_fee = fees.weekly_fee + fee;
fees.weekly_txs = fees.weekly_txs + 1;
}
@ -78,9 +79,10 @@ export const arrangePayments = (selNode, body) => {
}
});
payments.relayed.forEach((relayedEle) => {
if (relayedEle.timestamp.unix) {
relayedEle.timestamp = relayedEle.timestamp.unix * 1000;
}
// 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);
}
@ -97,7 +99,7 @@ export const getFees = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/audit';
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();
@ -107,7 +109,7 @@ export const getFees = (req, res, next) => {
};
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.ln_implementation).then((data) => { res.status(200).json(arrangeFees(req.session.selectedNode, data, Math.round((new Date().getTime())))); });
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) => {
@ -125,11 +127,17 @@ export const getPayments = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/audit';
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.ln_implementation).then((data) => { res.status(200).json(arrangePayments(req.session.selectedNode, 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) => {

@ -1,5 +1,4 @@
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { ECLWSClient } from './webSocketClient.js';
@ -7,7 +6,6 @@ let options = null;
const logger = Logger;
const common = Common;
const eclWsClient = ECLWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting Eclair Node Information..' });
common.logEnvVariables(req);
@ -16,12 +14,12 @@ export const getInfo = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/getinfo';
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.ln_node });
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.ln_implementation).then((data) => {
common.getDummyData('GetInfo', req.session.selectedNode.lnImplementation).then((data) => {
data.lnImplementation = 'Eclair';
return res.status(200).json(data);
});
@ -36,9 +34,8 @@ export const getInfo = (req, res, next) => {
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.ln_version = body.version.split('-')[0] || '';
req.session.selectedNode.lnVersion = body.version.split('-')[0] || '';
eclWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}).catch((errRes) => {

@ -19,7 +19,7 @@ export const getReceivedPaymentInfo = (lnServerUrl, invoice) => {
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 ? Math.round(response.status.receivedAt / 1000) : 0;
invoice.receivedAt = response.status.receivedAt.unix ? response.status.receivedAt.unix : 0;
}
return invoice;
}).catch((err) => {
@ -39,7 +39,7 @@ export const getInvoice = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/getinvoice';
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 });
@ -53,6 +53,27 @@ export const getInvoice = (req, res, next) => {
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);
@ -60,29 +81,33 @@ export const listInvoices = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
const options1 = JSON.parse(JSON.stringify(options));
options1.url = req.session.selectedNode.ln_server_url + '/listinvoices';
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.ln_server_url + '/listpendinginvoices';
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.ln_implementation).then((body) => {
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];
pendingInvoices = (!body[1] || body[1].length <= 0) ? [] : body[1];
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
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((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];
pendingInvoices = (!body[1] || body[1].length <= 0) ? [] : body[1];
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.ln_server_url, invoice))).
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);
@ -103,22 +128,31 @@ export const listInvoices = (req, res, next) => {
});
}
};
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 });
}
options.url = req.session.selectedNode.ln_server_url + '/createinvoice';
options.form = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body });
if (body.amount) {
body.amount = Math.round(body.amount / 1000);
}
res.status(201).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Invoices', 'Create Invoice Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.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 }));
};

@ -10,7 +10,7 @@ export const getNodes = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/nodes';
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 });
@ -20,3 +20,27 @@ export const getNodes = (req, res, next) => {
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 }));
};

@ -21,7 +21,7 @@ export const getNewAddress = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/getnewaddress';
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 });
@ -37,10 +37,10 @@ export const getBalance = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/onchainbalance';
options.url = req.session.selectedNode.settings.lnServerUrl + '/onchainbalance';
options.form = {};
if (common.read_dummy_data) {
common.getDummyData('OnChainBalance', req.session.selectedNode.ln_implementation).then((data) => { res.status(200).json(arrangeBalances(data)); });
common.getDummyData('OnChainBalance', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(arrangeBalances(data)); });
}
else {
request.post(options).then((body) => {
@ -59,7 +59,7 @@ export const getTransactions = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/onchaintransactions';
options.url = req.session.selectedNode.settings.lnServerUrl + '/onchaintransactions';
options.form = {
count: req.query.count,
skip: req.query.skip
@ -74,17 +74,14 @@ export const getTransactions = (req, res, next) => {
});
};
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.ln_server_url + '/sendonchain';
options.form = {
address: req.body.address,
amountSatoshis: req.body.amount,
confirmationTarget: req.body.blocks
};
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 });

@ -5,7 +5,7 @@ let options = null;
const logger = Logger;
const common = Common;
export const getSentInfoFromPaymentRequest = (selNode, payment) => {
options.url = selNode.ln_server_url + '/getsentinfo';
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 });
@ -21,7 +21,7 @@ export const getSentInfoFromPaymentRequest = (selNode, payment) => {
}).catch((err) => err);
};
export const getQueryNodes = (selNode, nodeIds) => {
options.url = selNode.ln_server_url + '/nodes';
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 });
@ -34,7 +34,7 @@ export const decodePayment = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/parseinvoice';
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 });
@ -53,7 +53,7 @@ export const postPayment = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/payinvoice';
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) => {
@ -70,7 +70,7 @@ export const queryPaymentRoute = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/findroutetonode';
options.url = req.session.selectedNode.settings.lnServerUrl + '/findroutetonode';
options.form = {
nodeId: req.query.nodeId,
amountMsat: req.query.amountMsat
@ -104,13 +104,14 @@ export const queryPaymentRoute = (req, res, next) => {
});
};
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 (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
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 });
@ -126,3 +127,29 @@ export const getSentPaymentsInformation = (req, res, next) => {
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 }));
};

@ -5,7 +5,7 @@ let options = null;
const logger = Logger;
const common = Common;
export const getFilteredNodes = (selNode, peersNodeIds) => {
options.url = selNode.ln_server_url + '/nodes';
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 });
@ -18,10 +18,10 @@ export const getPeers = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/peers';
options.url = req.session.selectedNode.settings.lnServerUrl + '/peers';
options.form = {};
if (common.read_dummy_data) {
common.getDummyData('Peers', req.session.selectedNode.ln_implementation).then((data) => { res.status(200).json(data); });
common.getDummyData('Peers', req.session.selectedNode.lnImplementation).then((data) => { res.status(200).json(data); });
}
else {
request.post(options).then((body) => {
@ -58,7 +58,7 @@ export const connectPeer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/connect';
options.url = req.session.selectedNode.settings.lnServerUrl + '/connect';
options.form = {};
if (req.query) {
options.form = req.query;
@ -74,7 +74,7 @@ export const connectPeer = (req, res, next) => {
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.ln_server_url + '/peers';
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 });
@ -112,7 +112,7 @@ export const deletePeer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/disconnect';
options.url = req.session.selectedNode.settings.lnServerUrl + '/disconnect';
options.form = {};
if (req.params.nodeId) {
options.form = { nodeId: req.params.nodeId };

@ -28,14 +28,14 @@ export class ECLWebSocketClient {
try {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
if (!clientExists) {
if (selectedNode.ln_server_url) {
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.ln_server_url) {
if ((!clientExists.webSocketClient || clientExists.webSocketClient.readyState !== WebSocket.OPEN) && selectedNode.settings.lnServerUrl) {
clientExists.reConnect = true;
this.connectWithClient(clientExists);
}
@ -47,9 +47,9 @@ export class ECLWebSocketClient {
};
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.ln_server_url)?.replace(/^http/, 'ws');
const UpdatedLNServerURL = (eclWsClt.selectedNode.settings.lnServerUrl)?.replace(/^http/, 'ws');
const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + eclWsClt.selectedNode.ln_api_password + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
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..' });
@ -57,7 +57,7 @@ export class ECLWebSocketClient {
this.heartbeat(eclWsClt);
};
eclWsClt.webSocketClient.onclose = (e) => {
if (eclWsClt && eclWsClt.selectedNode && eclWsClt.selectedNode.ln_implementation === 'ECL') {
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) {
@ -75,7 +75,7 @@ export class ECLWebSocketClient {
}
};
eclWsClt.webSocketClient.onerror = (err) => {
if (eclWsClt.selectedNode.ln_version === '' || !eclWsClt.selectedNode.ln_version || this.common.isVersionCompatible(eclWsClt.selectedNode.ln, '0.5.0')) {
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);

@ -10,7 +10,7 @@ export const getBlockchainBalance = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/balance/blockchain';
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 });

@ -6,7 +6,7 @@ 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.ln_server_url + '/v1/graph/node/' + pubkey;
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);
@ -22,7 +22,7 @@ export const getAllChannels = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channels';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels';
options.qs = req.query;
let local = 0;
let remote = 0;
@ -60,7 +60,7 @@ export const getPendingChannels = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/pending';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/pending';
options.qs = req.query;
request(options).then((body) => {
if (!body.total_limbo_balance) {
@ -98,7 +98,7 @@ export const getClosedChannels = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/closed';
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) {
@ -123,25 +123,30 @@ export const getClosedChannels = (req, res, next) => {
});
};
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.ln_server_url + '/v1/channels';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels';
options.form = {
node_pubkey_string: req.body.node_pubkey,
local_funding_amount: req.body.local_funding_amount,
private: req.body.private,
spend_unconfirmed: req.body.spend_unconfirmed
node_pubkey_string: node_pubkey,
local_funding_amount: local_funding_amount,
private: privateChannel,
spend_unconfirmed: spend_unconfirmed
};
if (req.body.trans_type === '1') {
options.form.target_conf = req.body.trans_type_value;
if (trans_type === '1') {
options.form.target_conf = trans_type_value;
}
else if (req.body.trans_type === '2') {
options.form.sat_per_byte = req.body.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);
@ -151,31 +156,33 @@ export const postChannel = (req, res, next) => {
});
};
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.ln_server_url + '/v1/channels/transaction-stream';
options.form = { payment_request: req.body.paymentReq };
if (req.body.paymentAmount) {
options.form.amt = req.body.paymentAmount;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/transaction-stream';
options.form = { payment_request: paymentReq };
if (paymentAmount) {
options.form.amt = paymentAmount;
}
if (req.body.feeLimit) {
options.form.fee_limit = req.body.feeLimit;
if (feeLimit) {
options.form.fee_limit = feeLimit;
}
if (req.body.outgoingChannel) {
options.form.outgoing_chan_id = req.body.outgoingChannel;
if (outgoingChannel) {
options.form.outgoing_chan_id = outgoingChannel;
}
if (req.body.allowSelfPayment) {
options.form.allow_self_payment = req.body.allowSelfPayment;
if (allowSelfPayment) {
options.form.allow_self_payment = allowSelfPayment;
}
if (req.body.lastHopPubkey) {
options.form.last_hop_pubkey = Buffer.from(req.body.lastHopPubkey, 'hex').toString('base64');
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 });
@ -201,7 +208,7 @@ export const closeChannel = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const channelpoint = req.params.channelPoint?.replace(':', '/');
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/' + channelpoint + '?force=' + req.query.force;
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;
}
@ -219,35 +226,36 @@ export const closeChannel = (req, res, next) => {
}
};
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.ln_server_url + '/v1/chanpolicy';
if (req.body.chanPoint === 'all') {
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/chanpolicy';
if (chanPoint === 'all') {
options.form = JSON.stringify({
global: true,
base_fee_msat: req.body.baseFeeMsat,
fee_rate: parseFloat((req.body.feeRate / 1000000).toString()),
time_lock_delta: parseInt(req.body.timeLockDelta)
base_fee_msat: baseFeeMsat,
fee_rate: parseFloat((feeRate / 1000000).toString()),
time_lock_delta: parseInt(timeLockDelta)
});
}
else {
const breakPoint = req.body.chanPoint.indexOf(':');
const txid_str = req.body.chanPoint.substring(0, breakPoint);
const output_idx = req.body.chanPoint.substring(breakPoint + 1, req.body.chanPoint.length);
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: req.body.baseFeeMsat,
fee_rate: parseFloat((req.body.feeRate / 1000000).toString()),
time_lock_delta: parseInt(req.body.timeLockDelta),
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 (req.body.max_htlc_msat) {
optionsBody['max_htlc_msat'] = req.body.max_htlc_msat;
if (max_htlc_msat) {
optionsBody['max_htlc_msat'] = max_htlc_msat;
}
if (req.body.min_htlc_msat) {
optionsBody['min_htlc_msat'] = req.body.min_htlc_msat;
if (min_htlc_msat) {
optionsBody['min_htlc_msat'] = min_htlc_msat;
optionsBody['min_htlc_msat_specified'] = true;
}
options.form = JSON.stringify(optionsBody);

@ -39,15 +39,15 @@ export const getBackup = (req, res, next) => {
let channel_backup_file = '';
let message = '';
if (req.params.channelPoint === 'ALL') {
channel_backup_file = req.session.selectedNode.channel_backup_path + sep + 'channel-all.bak';
channel_backup_file = req.session.selectedNode.settings.channelBackupPath + sep + 'channel-all.bak';
message = 'All Channels Backup Successful.';
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/backup';
}
else {
channel_backup_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
channel_backup_file = req.session.selectedNode.settings.channelBackupPath + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
message = 'Channel Backup Successful.';
const channelpoint = req.params.channelPoint?.replace(':', '/');
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup/' + channelpoint;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/backup/' + channelpoint;
const exists = fs.existsSync(channel_backup_file);
if (exists) {
fs.writeFile(channel_backup_file, '', () => { });
@ -86,13 +86,13 @@ export const postBackupVerify = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup/verify';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/backup/verify';
let channel_verify_file = '';
let message = '';
let verify_backup = '';
if (req.params.channelPoint === 'ALL') {
message = 'All Channels Verify Successful.';
channel_verify_file = req.session.selectedNode.channel_backup_path + sep + 'channel-all.bak';
channel_verify_file = req.session.selectedNode.settings.channelBackupPath + sep + 'channel-all.bak';
const exists = fs.existsSync(channel_verify_file);
if (exists) {
verify_backup = fs.readFileSync(channel_verify_file, 'utf-8');
@ -116,7 +116,7 @@ export const postBackupVerify = (req, res, next) => {
}
else {
message = 'Channel Verify Successful.';
channel_verify_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
channel_verify_file = req.session.selectedNode.settings.channelBackupPath + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
const exists = fs.existsSync(channel_verify_file);
if (exists) {
verify_backup = fs.readFileSync(channel_verify_file, 'utf-8');
@ -146,13 +146,13 @@ export const postRestore = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup/restore';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/backup/restore';
let channel_restore_file = '';
let message = '';
let restore_backup = '';
if (req.params.channelPoint === 'ALL') {
message = 'All Channels Restore Successful.';
channel_restore_file = req.session.selectedNode.channel_backup_path + sep + 'restore' + sep;
channel_restore_file = req.session.selectedNode.settings.channelBackupPath + sep + 'restore' + sep;
const exists = fs.existsSync(channel_restore_file + 'channel-all.bak');
const downloaded_exists = fs.existsSync(channel_restore_file + 'backup-channel-all.bak');
if (exists) {
@ -188,7 +188,7 @@ export const postRestore = (req, res, next) => {
}
else {
message = 'Channel Restore Successful.';
channel_restore_file = req.session.selectedNode.channel_backup_path + sep + 'restore' + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
channel_restore_file = req.session.selectedNode.settings.channelBackupPath + sep + 'restore' + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
const exists = fs.existsSync(channel_restore_file);
if (exists) {
restore_backup = fs.readFileSync(channel_restore_file, 'utf-8');
@ -208,7 +208,7 @@ export const postRestore = (req, res, next) => {
channel_restore_file = channel_restore_file + 'channel-all.bak';
}
fs.rename(channel_restore_file, channel_restore_file + '.restored', () => {
getFilesList(req.session.selectedNode.channel_backup_path, (getFilesListRes) => {
getFilesList(req.session.selectedNode.settings.channelBackupPath, (getFilesListRes) => {
if (getFilesListRes.error) {
const errMsg = getFilesListRes.error;
const err = common.handleError({ statusCode: 500, message: 'Restore Channel Error', error: errMsg }, 'ChannelBackup', errMsg, req.session.selectedNode);
@ -228,7 +228,7 @@ export const postRestore = (req, res, next) => {
}
};
export const getRestoreList = (req, res, next) => {
getFilesList(req.session.selectedNode.channel_backup_path, (getFilesListRes) => {
getFilesList(req.session.selectedNode.settings.channelBackupPath, (getFilesListRes) => {
if (getFilesListRes.error) {
return res.status(getFilesListRes.statusCode).json(getFilesListRes);
}

@ -11,7 +11,7 @@ export const getFees = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/fees';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/fees';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body });
const today = new Date(Date.now());

@ -1,5 +1,4 @@
import request from 'request-promise';
import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { LNDWSClient } from './webSocketClient.js';
@ -7,7 +6,6 @@ let options = null;
const logger = Logger;
const common = Common;
const lndWsClient = LNDWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting LND Node Information..' });
common.logEnvVariables(req);
@ -16,8 +14,8 @@ export const getInfo = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Selected Node ' + req.session.selectedNode.ln_node });
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 LND server url ' + options.url });
if (!options.headers || !options.headers['Grpc-Metadata-macaroon']) {
const errMsg = 'LND Get info failed due to bad or missing macaroon! Please check RTL-Config.json to verify the setup!';
@ -26,7 +24,7 @@ export const getInfo = (req, res, next) => {
}
else {
common.nodes?.map((node) => {
if (node.ln_implementation === 'LND') {
if (node.lnImplementation === 'LND') {
common.getAllNodeAllChannelBackup(node);
}
return node;
@ -42,9 +40,8 @@ export const getInfo = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
else {
req.session.selectedNode.ln_version = body.version.split('-')[0] || '';
req.session.selectedNode.lnVersion = body.version.split('-')[0] || '';
lndWsClient.updateSelectedNode(req.session.selectedNode);
databaseService.loadDatabase(req.session);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
}

@ -5,7 +5,7 @@ let options = null;
const logger = Logger;
const common = Common;
export const getAliasFromPubkey = (selNode, pubkey) => {
options.url = selNode.ln_server_url + '/v1/graph/node/' + pubkey;
options.url = selNode.settings.lnServerUrl + '/v1/graph/node/' + pubkey;
return request(options).then((res) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Graph', msg: 'Alias Received', data: res.node.alias });
return res.node.alias;
@ -18,7 +18,7 @@ export const getDescribeGraph = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/graph';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/graph';
request.get(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Network Graph Received', data: body });
res.status(200).json(body);
@ -33,7 +33,7 @@ export const getGraphInfo = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/graph/info';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/graph/info';
request.get(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Graph Information Received', data: body });
res.status(200).json(body);
@ -48,7 +48,7 @@ export const getGraphNode = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/graph/node/' + req.params.pubKey;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/graph/node/' + req.params.pubKey;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Graph Node Information Received', data: body });
res.status(200).json(body);
@ -63,7 +63,7 @@ export const getGraphEdge = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/graph/edge/' + req.params.chanid;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/graph/edge/' + req.params.chanid;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Graph Edge Information Received', data: body });
res.status(200).json(body);
@ -78,7 +78,7 @@ export const getQueryRoutes = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/graph/routes/' + req.params.destPubkey + '/' + req.params.amount;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/graph/routes/' + req.params.destPubkey + '/' + req.params.amount;
if (req.query.outgoing_chan_id) {
options.url = options.url + '?outgoing_chan_id=' + req.query.outgoing_chan_id;
}
@ -116,7 +116,7 @@ export const getRemoteFeePolicy = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/graph/edge/' + req.params.chanid;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/graph/edge/' + req.params.chanid;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Graph', msg: 'Edge Info Received', data: body });
let remoteNodeFee = {};

@ -12,7 +12,7 @@ export const invoiceLookup = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/invoices/lookup';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/invoices/lookup';
if (req.query.payment_addr) {
options.url = options.url + '?payment_addr=' + req.query.payment_addr;
}
@ -36,7 +36,7 @@ export const listInvoices = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/invoices?num_max_invoices=' + req.query.num_max_invoices + '&index_offset=' + req.query.index_offset +
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/invoices?num_max_invoices=' + req.query.num_max_invoices + '&index_offset=' + req.query.index_offset +
'&reversed=' + req.query.reversed;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
@ -60,7 +60,7 @@ export const addInvoice = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/invoices';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/invoices';
options.form = JSON.stringify(req.body);
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Added', data: body });

@ -5,14 +5,15 @@ let options = null;
const logger = Logger;
const common = Common;
export const signMessage = (req, res, next) => {
const { message } = req.body;
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.ln_server_url + '/v1/signmessage';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/signmessage';
options.form = JSON.stringify({
msg: Buffer.from(req.body.message).toString('base64')
msg: Buffer.from(message).toString('base64')
});
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Signed', data: body });
@ -23,15 +24,16 @@ export const signMessage = (req, res, next) => {
});
};
export const verifyMessage = (req, res, next) => {
const { message, signature } = req.body;
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.ln_server_url + '/v1/verifymessage';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/verifymessage';
options.form = JSON.stringify({
msg: Buffer.from(req.body.message).toString('base64'),
signature: req.body.signature
msg: Buffer.from(message).toString('base64'),
signature: signature
});
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Verified', data: body });

@ -10,7 +10,7 @@ export const getNewAddress = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/newaddress?type=' + req.query.type;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/newaddress?type=' + req.query.type;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'NewAddress', msg: 'New Address Generated', data: body });
res.status(200).json(body);

@ -5,7 +5,7 @@ let options = null;
const logger = Logger;
const common = Common;
export const decodePaymentFromPaymentRequest = (selNode, payment) => {
options.url = selNode.ln_server_url + '/v1/payreq/' + payment;
options.url = selNode.settings.lnServerUrl + '/v1/payreq/' + payment;
return request(options).then((res) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'PayReq', msg: 'Description Received', data: res.description });
return res;
@ -17,7 +17,7 @@ export const decodePayment = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/payreq/' + req.params.payRequest;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/payreq/' + req.params.payRequest;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'PayRequest', msg: 'Payment Decoded', data: body });
res.status(200).json(body);
@ -27,13 +27,14 @@ export const decodePayment = (req, res, next) => {
});
};
export const decodePayments = (req, res, next) => {
const { payments } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'PayRequest', msg: 'Decoding Payments List..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
if (payments) {
const paymentsArr = payments.split(',');
return Promise.all(paymentsArr?.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'PayRequest', msg: 'Payment List Decoded', data: values });
@ -55,7 +56,7 @@ export const getPayments = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=' + req.query.max_payments + '&index_offset=' + req.query.index_offset + '&reversed=' + req.query.reversed;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/payments?max_payments=' + req.query.max_payments + '&index_offset=' + req.query.index_offset + '&reversed=' + req.query.reversed;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body });
res.status(200).json(body);
@ -68,8 +69,8 @@ export const getAllLightningTransactions = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Getting All Lightning Transactions..' });
const options1 = JSON.parse(JSON.stringify(common.getOptions(req)));
const options2 = JSON.parse(JSON.stringify(common.getOptions(req)));
// options1.url = req.session.selectedNode.ln_server_url + '/v1/payments?max_payments=100000&index_offset=0&reversed=true';
options2.url = req.session.selectedNode.ln_server_url + '/v1/invoices?num_max_invoices=100000&index_offset=0&reversed=true';
// options1.url = req.session.selectedNode.settings.lnServerUrl + '/v1/payments?max_payments=100000&index_offset=0&reversed=true';
options2.url = req.session.selectedNode.settings.lnServerUrl + '/v1/invoices?num_max_invoices=100000&index_offset=0&reversed=true';
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Payments Options', data: options1 });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'All Invoices Options', data: options2 });
// return Promise.all([request(options1), request(options2)]).then((values) => {
@ -87,7 +88,7 @@ export const paymentLookup = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/router/track/' + req.params.paymentHash;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/router/track/' + req.params.paymentHash;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Information Received for ' + req.params.paymentHash, data: body });
res.status(200).json(body.result || body);

@ -5,7 +5,7 @@ let options = null;
const logger = Logger;
const common = Common;
export const getAliasForPeers = (selNode, peer) => {
options.url = selNode.ln_server_url + '/v1/graph/node/' + peer.pub_key;
options.url = selNode.settings.lnServerUrl + '/v1/graph/node/' + peer.pub_key;
return request(options).then((aliasBody) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Peers', msg: 'Alias Received', data: aliasBody.node.alias });
peer.alias = aliasBody.node.alias;
@ -21,7 +21,7 @@ export const getPeers = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/peers';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/peers';
request(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;
@ -35,24 +35,25 @@ export const getPeers = (req, res, next) => {
});
};
export const postPeer = (req, res, next) => {
const { host, pubkey, perm } = req.body;
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.ln_server_url + '/v1/peers';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/peers';
options.form = JSON.stringify({
addr: { host: req.body.host, pubkey: req.body.pubkey },
perm: req.body.perm
addr: { host: host, pubkey: pubkey },
perm: perm
});
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: body });
options.url = req.session.selectedNode.ln_server_url + '/v1/peers';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/peers';
request(options).then((body) => {
const peers = (!body.peers) ? [] : body.peers;
return Promise.all(peers?.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
if (body.peers) {
body.peers = common.newestOnTop(body.peers, 'pub_key', req.body.pubkey);
body.peers = common.newestOnTop(body.peers, 'pub_key', pubkey);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: body });
}
res.status(201).json(body.peers);
@ -75,7 +76,7 @@ export const deletePeer = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/peers/' + req.params.peerPubKey;
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/peers/' + req.params.peerPubKey;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnect Pubkey', data: req.params.peerPubKey });
request.delete(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconneted', data: body });

@ -7,7 +7,8 @@ const common = Common;
const responseData = { switch: { forwarding_events: [], last_offset_index: 0 }, fees: { forwarding_events: [], last_offset_index: 0 } };
const num_max_events = 100;
export const forwardingHistory = (req, res, next) => {
getAllForwardingEvents(req, req.body.start_time, req.body.end_time, 0, 'switch', (eventsResponse) => {
const { start_time, end_time } = req.body;
getAllForwardingEvents(req, start_time, end_time, 0, 'switch', (eventsResponse) => {
if (eventsResponse.error) {
res.status(eventsResponse.error.statusCode).json(eventsResponse);
}
@ -26,7 +27,7 @@ export const getAllForwardingEvents = (req, start, end, offset, caller, callback
return callback({ message: err.message, error: err.error, statusCode: err.statusCode });
}
options = common.getOptions(req);
options.url = req.session.selectedNode.ln_server_url + '/v1/switch';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/switch';
options.form = {};
if (start) {
options.form.start_time = start;

@ -10,7 +10,7 @@ export const getTransactions = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/transactions';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/transactions';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Transactions', msg: 'Transactions List Received', data: body });
res.status(200).json(body.transactions);
@ -20,20 +20,21 @@ export const getTransactions = (req, res, next) => {
});
};
export const postTransactions = (req, res, next) => {
const { amount, address, fees, blocks, sendAll } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Transactions', msg: 'Sending Transaction..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/transactions';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/transactions';
options.form = {
amount: req.body.amount,
addr: req.body.address,
sat_per_byte: req.body.fees,
target_conf: req.body.blocks
amount: amount,
addr: address,
sat_per_byte: fees,
target_conf: blocks
};
if (req.body.sendAll) {
options.form.send_all = req.body.sendAll;
if (sendAll) {
options.form.send_all = sendAll;
}
options.form = JSON.stringify(options.form);
request.post(options).then((body) => {

@ -12,10 +12,10 @@ export const genSeed = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
if (req.params.passphrase) {
options.url = req.session.selectedNode.ln_server_url + '/v1/genseed?aezeed_passphrase=' + Buffer.from(atob(req.params.passphrase)).toString('base64');
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/genseed?aezeed_passphrase=' + Buffer.from(atob(req.params.passphrase)).toString('base64');
}
else {
options.url = req.session.selectedNode.ln_server_url + '/v1/genseed';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/genseed';
}
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Seed Generated', data: body });
@ -26,6 +26,7 @@ export const genSeed = (req, res, next) => {
});
};
export const operateWallet = (req, res, next) => {
const { wallet_password, aezeed_passphrase, cipher_seed_mnemonic } = req.body;
let err_message = '';
options = common.getOptions(req);
if (options.error) {
@ -34,26 +35,26 @@ export const operateWallet = (req, res, next) => {
options.method = 'POST';
if (!req.params.operation || req.params.operation === 'unlockwallet') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Unlocking Wallet..' });
options.url = req.session.selectedNode.ln_server_url + '/v1/unlockwallet';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/unlockwallet';
options.form = JSON.stringify({
wallet_password: Buffer.from(atob(req.body.wallet_password)).toString('base64')
wallet_password: Buffer.from(atob(wallet_password)).toString('base64')
});
err_message = 'Unlocking wallet failed! Verify that lnd is running and the wallet is locked!';
}
else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Initializing Wallet..' });
options.url = req.session.selectedNode.ln_server_url + '/v1/initwallet';
if (req.body.aezeed_passphrase && req.body.aezeed_passphrase !== '') {
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/initwallet';
if (aezeed_passphrase && aezeed_passphrase !== '') {
options.form = JSON.stringify({
wallet_password: Buffer.from(atob(req.body.wallet_password)).toString('base64'),
cipher_seed_mnemonic: req.body.cipher_seed_mnemonic,
aezeed_passphrase: Buffer.from(atob(req.body.aezeed_passphrase)).toString('base64')
wallet_password: Buffer.from(atob(wallet_password)).toString('base64'),
cipher_seed_mnemonic: cipher_seed_mnemonic,
aezeed_passphrase: Buffer.from(atob(aezeed_passphrase)).toString('base64')
});
}
else {
options.form = JSON.stringify({
wallet_password: Buffer.from(atob(req.body.wallet_password)).toString('base64'),
cipher_seed_mnemonic: req.body.cipher_seed_mnemonic
wallet_password: Buffer.from(atob(wallet_password)).toString('base64'),
cipher_seed_mnemonic: cipher_seed_mnemonic
});
}
err_message = 'Initializing wallet failed!';
@ -103,8 +104,8 @@ export const getUTXOs = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/wallet/utxos';
if (common.isVersionCompatible(req.session.selectedNode.ln_version, '0.14.0')) {
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/wallet/utxos';
if (common.isVersionCompatible(req.session.selectedNode.lnVersion, '0.14.0')) {
options.form = JSON.stringify({ max_confs: req.query.max_confs });
}
else {
@ -119,22 +120,23 @@ export const getUTXOs = (req, res, next) => {
});
};
export const bumpFee = (req, res, next) => {
const { txid, outputIndex, targetConf, satPerByte } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Bumping 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.ln_server_url + '/v2/wallet/bumpfee';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/wallet/bumpfee';
options.form = {};
options.form.outpoint = {
txid_str: req.body.txid,
output_index: req.body.outputIndex
txid_str: txid,
output_index: outputIndex
};
if (req.body.targetConf) {
options.form.target_conf = req.body.targetConf;
if (targetConf) {
options.form.target_conf = targetConf;
}
else if (req.body.satPerByte) {
options.form.sat_per_byte = req.body.satPerByte;
else if (satPerByte) {
options.form.sat_per_byte = satPerByte;
}
options.form = JSON.stringify(options.form);
request.post(options).then((body) => {
@ -151,12 +153,8 @@ export const labelTransaction = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/wallet/tx/label';
options.form = {};
options.form.txid = req.body.txid;
options.form.label = req.body.label;
options.form.overwrite = req.body.overwrite;
options.form = JSON.stringify(options.form);
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/wallet/tx/label';
options.form = JSON.stringify(req.body);
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Wallet', msg: 'Label Transaction Options', data: options.form });
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Transaction Labelled', data: body });
@ -167,17 +165,18 @@ export const labelTransaction = (req, res, next) => {
});
};
export const leaseUTXO = (req, res, next) => {
const { txid, outputIndex } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Leasing UTXO..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/wallet/utxos/lease';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/wallet/utxos/lease';
options.form = {};
options.form.id = req.body.txid;
options.form.id = txid;
options.form.outpoint = {
txid_bytes: req.body.txid,
output_index: req.body.outputIndex
txid_bytes: txid,
output_index: outputIndex
};
options.form = JSON.stringify(options.form);
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Wallet', msg: 'UTXO Lease Options', data: options.form });
@ -190,17 +189,18 @@ export const leaseUTXO = (req, res, next) => {
});
};
export const releaseUTXO = (req, res, next) => {
const { txid, outputIndex } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Wallet', msg: 'Releasing UTXO..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v2/wallet/utxos/release';
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/wallet/utxos/release';
options.form = {};
options.form.id = req.body.txid;
options.form.id = txid;
options.form.outpoint = {
txid_bytes: req.body.txid,
output_index: req.body.outputIndex
txid_bytes: txid,
output_index: outputIndex
};
options.form = JSON.stringify(options.form);
request.post(options).then((body) => {

@ -13,7 +13,7 @@ export class LNDWebSocketClient {
this.connect = (selectedNode) => {
try {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index);
if (!clientExists && selectedNode.ln_server_url) {
if (!clientExists && selectedNode.settings.lnServerUrl) {
const newWebSocketClient = { selectedNode: selectedNode };
this.webSocketClients.push(newWebSocketClient);
}
@ -25,7 +25,7 @@ export class LNDWebSocketClient {
this.fetchUnpaidInvoices = (selectedNode) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Getting Unpaid Invoices..' });
const options = this.setOptionsForSelNode(selectedNode);
options.url = selectedNode.ln_server_url + '/v1/invoices?pending_only=true';
options.url = selectedNode.settings.lnServerUrl + '/v1/invoices?pending_only=true';
return request(options).then((body) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Unpaid Invoices Received', data: body });
if (body.invoices && body.invoices.length > 0) {
@ -44,7 +44,7 @@ export class LNDWebSocketClient {
this.subscribeToInvoice = (options, selectedNode, rHash) => {
rHash = rHash?.replace(/\+/g, '-')?.replace(/[/]/g, '_');
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Invoice ' + rHash + ' ..' });
options.url = selectedNode.ln_server_url + '/v2/invoices/subscribe/' + rHash;
options.url = selectedNode.settings.lnServerUrl + '/v2/invoices/subscribe/' + rHash;
request(options).then((msg) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Invoice Information Received for ' + rHash });
if (typeof msg === 'string') {
@ -67,7 +67,7 @@ export class LNDWebSocketClient {
};
this.subscribeToPayment = (options, selectedNode, paymentHash) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Payment ' + paymentHash + ' ..' });
options.url = selectedNode.ln_server_url + '/v2/router/track/' + paymentHash;
options.url = selectedNode.settings.lnServerUrl + '/v2/router/track/' + paymentHash;
request(options).then((msg) => {
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Payment Information Received for ' + paymentHash });
msg['type'] = 'payment';
@ -84,7 +84,7 @@ export class LNDWebSocketClient {
this.setOptionsForSelNode = (selectedNode) => {
const options = { url: '', rejectUnauthorized: false, json: true, form: null };
try {
options['headers'] = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(selectedNode.macaroon_path, 'admin.macaroon')).toString('hex') };
options['headers'] = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(selectedNode.authentication.macaroonPath, 'admin.macaroon')).toString('hex') };
}
catch (err) {
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: 'WebSocketClient', msg: 'Set Options Error', error: JSON.stringify(err) });
@ -107,7 +107,7 @@ export class LNDWebSocketClient {
}
newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
this.webSocketClients[clientIdx] = newClient;
if (this.webSocketClients[clientIdx].selectedNode.ln_version === '' || !this.webSocketClients[clientIdx].selectedNode.ln_version || this.common.isVersionCompatible(this.webSocketClients[clientIdx].selectedNode.ln_version, '0.11.0')) {
if (this.webSocketClients[clientIdx].selectedNode.lnVersion === '' || !this.webSocketClients[clientIdx].selectedNode.lnVersion || this.common.isVersionCompatible(this.webSocketClients[clientIdx].selectedNode.lnVersion, '0.11.0')) {
this.fetchUnpaidInvoices(this.webSocketClients[clientIdx].selectedNode);
}
};

@ -1,3 +1,4 @@
import jwt from 'jsonwebtoken';
import * as fs from 'fs';
import { sep } from 'path';
import ini from 'ini';
@ -7,207 +8,148 @@ import { Database } from '../../utils/database.js';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
import { Authentication, SSO } from '../../models/config.model.js';
const options = { url: '' };
const logger = Logger;
const common = Common;
const wsServer = WSServer;
const databaseService = Database;
export const updateSelectedNode = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Selected Node..' });
const selNodeIndex = req.params.currNodeIndex ? +req.params.currNodeIndex : common.initSelectedNode ? +common.initSelectedNode.index : 1;
req.session.selectedNode = common.findNode(selNodeIndex);
if (req.headers && req.headers.authorization && req.headers.authorization !== '') {
wsServer.updateLNWSClientDetails(req.session.id, +req.session.selectedNode.index, +req.params.prevNodeIndex);
if (req.params.prevNodeIndex !== -1) {
databaseService.unloadDatabase(req.params.prevNodeIndex, req.session.id);
}
}
const responseVal = !req.session.selectedNode.ln_node ? '' : req.session.selectedNode.ln_node;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Selected Node Updated To ' + responseVal });
res.status(200).json({ status: 'Selected Node Updated To: ' + JSON.stringify(responseVal) + '!' });
// Set local block explorer URL after first API call
// if the selected node block explorer has working REST API suite
// otherwise set it to mempool.space
let blockExplorerUrl = '';
export const getExplorerFeesRecommended = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting Recommended Fee Rates..' });
options.url = (blockExplorerUrl === '') ?
req.session.selectedNode.settings.blockExplorerUrl + '/api/v1/fees/recommended' :
blockExplorerUrl + '/api/v1/fees/recommended';
request(options).then((body) => {
blockExplorerUrl = req.session.selectedNode.settings.blockExplorerUrl;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Recommended Fee Rates Received', data: body });
res.status(200).json(JSON.parse(body));
}).catch((errRes) => {
blockExplorerUrl = 'https://mempool.space';
options.url = blockExplorerUrl + '/api/v1/fees/recommended';
return request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Recommended Fee Rates Received', data: body });
res.status(200).json(JSON.parse(body));
}).catch((errRes) => {
const errMsg = 'Get Recommended Fee Rates Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
});
});
};
export const getRTLConfigInitial = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting Initial RTL Configuration..' });
const confFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
fs.readFile(confFile, 'utf8', (errRes, data) => {
export const getExplorerTransaction = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting Transaction From Block Explorer..' });
options.url = (blockExplorerUrl === '') ?
req.session.selectedNode.settings.blockExplorerUrl + '/api/tx/' + req.params.txid :
blockExplorerUrl + '/api/tx/' + req.params.txid;
request(options).then((body) => {
blockExplorerUrl = req.session.selectedNode.settings.blockExplorerUrl;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Transaction From Block Explorer Received', data: body });
res.status(200).json(JSON.parse(body));
}).catch((errRes) => {
blockExplorerUrl = 'https://mempool.space';
options.url = blockExplorerUrl + '/api/tx/' + req.params.txid;
return request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Transaction From Block Explorer Received', data: body });
res.status(200).json(JSON.parse(body));
}).catch((errRes) => {
const errMsg = 'Get Transaction From Block Explorer Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
});
});
};
export const getCurrencyRates = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting Currency Rates..' });
options.url = 'https://blockchain.info/ticker';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Currency Rates Received', data: body });
res.status(200).json(JSON.parse(body));
}).catch((errRes) => {
const errMsg = 'Get Rates Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
});
};
export const getFile = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting File..' });
const file = req.query.path ? req.query.path : (req.session.selectedNode.settings.channelBackupPath + sep + 'channel-' + req.query.channel?.replace(':', '-') + '.bak');
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'Channel Point', data: req.query.channel });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'File Path', data: file });
fs.readFile(file, 'utf8', (errRes, data) => {
if (errRes) {
if (errRes.code === 'ENOENT') {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'RTLConf', msg: 'Node config does not exist!', error: { error: 'Node config does not exist.' } });
res.status(200).json({ defaultNodeIndex: 0, selectedNodeIndex: 0, sso: {}, nodes: [] });
}
else {
const errMsg = 'Get Node Config Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
if (errRes.code && errRes.code === 'ENOENT') {
errRes.code = 'File Not Found!';
}
const errMsg = 'Reading File Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
else {
const nodeConfData = JSON.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
const enable2FA = !!common.rtl_secret2fa;
const allowPasswordUpdate = common.flg_allow_password_update;
const nodesArr = [];
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => {
const settings = { unannouncedChannels: false };
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.unannouncedChannels = !!node.unannounced_channels || false;
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
index: node.index,
lnNode: node.ln_node,
lnImplementation: node.ln_implementation,
settings: settings,
authentication: {}
});
});
}
const body = { defaultNodeIndex: nodeConfData.defaultNodeIndex, selectedNodeIndex: (req.session.selectedNode && req.session.selectedNode.index ? req.session.selectedNode.index : common.initSelectedNode.index), sso: sso, enable2FA: enable2FA, allowPasswordUpdate: allowPasswordUpdate, nodes: nodesArr };
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Initial RTL Configuration Received', data: body });
res.status(200).json(body);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'File Data Received', data: data });
res.status(200).json(data);
}
});
};
export const getRTLConfig = (req, res, next) => {
export const getApplicationSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting RTL Configuration..' });
const confFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const confFile = common.appConfig.rtlConfFilePath + sep + 'RTL-Config.json';
fs.readFile(confFile, 'utf8', (errRes, data) => {
if (errRes) {
if (errRes.code === 'ENOENT') {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'RTLConf', msg: 'Node config does not exist!', error: { error: 'Node config does not exist.' } });
res.status(200).json({ defaultNodeIndex: 0, selectedNodeIndex: 0, sso: {}, nodes: [] });
}
else {
const errMsg = 'Get Node Config Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
const errMsg = 'Get Node Config Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
else {
const nodeConfData = JSON.parse(data);
const sso = { rtlSSO: common.rtl_sso, logoutRedirectLink: common.logout_redirect_link };
const enable2FA = !!common.rtl_secret2fa;
const allowPasswordUpdate = common.flg_allow_password_update;
const nodesArr = [];
if (common.nodes && common.nodes.length > 0) {
common.nodes.forEach((node, i) => {
const authentication = {};
authentication.configPath = (node.config_path) ? node.config_path : '';
authentication.swapMacaroonPath = (node.swap_macaroon_path) ? node.swap_macaroon_path : '';
authentication.boltzMacaroonPath = (node.boltz_macaroon_path) ? node.boltz_macaroon_path : '';
const settings = { unannouncedChannels: false };
settings.userPersona = node.user_persona ? node.user_persona : 'MERCHANT';
settings.themeMode = (node.theme_mode) ? node.theme_mode : 'DAY';
settings.themeColor = (node.theme_color) ? node.theme_color : 'PURPLE';
settings.unannouncedChannels = !!node.unannounced_channels || false;
settings.fiatConversion = (node.fiat_conversion) ? !!node.fiat_conversion : false;
settings.bitcoindConfigPath = node.bitcoind_config_path;
settings.logLevel = node.log_level ? node.log_level : 'ERROR';
settings.lnServerUrl = node.ln_server_url;
settings.swapServerUrl = node.swap_server_url;
settings.boltzServerUrl = node.boltz_server_url;
settings.enableOffers = node.enable_offers;
settings.enablePeerswap = node.enable_peerswap;
settings.channelBackupPath = node.channel_backup_path;
settings.currencyUnit = node.currency_unit;
nodesArr.push({
index: node.index,
lnNode: node.ln_node,
lnImplementation: node.ln_implementation,
settings: settings,
authentication: authentication
});
});
}
const body = { defaultNodeIndex: nodeConfData.defaultNodeIndex, selectedNodeIndex: (req.session.selectedNode && req.session.selectedNode.index ? req.session.selectedNode.index : common.initSelectedNode.index), sso: sso, enable2FA: enable2FA, allowPasswordUpdate: allowPasswordUpdate, nodes: nodesArr };
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'RTL Configuration Received', data: body });
res.status(200).json(body);
const appConfData = common.removeSecureData(JSON.parse(data));
appConfData.allowPasswordUpdate = common.appConfig.allowPasswordUpdate;
appConfData.enable2FA = common.appConfig.enable2FA;
appConfData.selectedNodeIndex = (req.session.selectedNode && req.session.selectedNode.index ? req.session.selectedNode.index : common.selectedNode.index);
common.appConfig.selectedNodeIndex = appConfData.selectedNodeIndex;
const token = req.headers.authorization ? req.headers.authorization.split(' ')[1] : '';
jwt.verify(token, common.secret_key, (err, user) => {
if (err) {
// Delete unnecessary data for initial response (without security token)
const selNodeIdx = appConfData.nodes.findIndex((node) => node.index === appConfData.selectedNodeIndex) || 0;
appConfData.SSO = new SSO();
appConfData.secret2FA = '';
appConfData.dbDirectoryPath = '';
appConfData.nodes[selNodeIdx].authentication = new Authentication();
delete appConfData.nodes[selNodeIdx].settings.bitcoindConfigPath;
delete appConfData.nodes[selNodeIdx].settings.lnServerUrl;
delete appConfData.nodes[selNodeIdx].settings.swapServerUrl;
delete appConfData.nodes[selNodeIdx].settings.boltzServerUrl;
delete appConfData.nodes[selNodeIdx].settings.enableOffers;
delete appConfData.nodes[selNodeIdx].settings.enablePeerswap;
delete appConfData.nodes[selNodeIdx].settings.channelBackupPath;
appConfData.nodes = [appConfData.nodes[selNodeIdx]];
}
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'RTL Configuration Received', data: appConfData });
res.status(200).json(appConfData);
});
}
});
};
export const updateUISettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating UI Settings..' });
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const node = config.nodes.find((node) => (node.index === req.session.selectedNode.index));
if (node && node.Settings) {
node.Settings.userPersona = req.body.updatedSettings.userPersona;
node.Settings.themeMode = req.body.updatedSettings.themeMode;
node.Settings.themeColor = req.body.updatedSettings.themeColor;
node.Settings.unannouncedChannels = req.body.updatedSettings.unannouncedChannels;
node.Settings.fiatConversion = req.body.updatedSettings.fiatConversion;
if (req.body.updatedSettings.fiatConversion) {
node.Settings.currencyUnit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
}
else {
delete node.Settings.currencyUnit;
}
const selectedNode = common.findNode(req.session.selectedNode.index);
selectedNode.user_persona = req.body.updatedSettings.userPersona;
selectedNode.theme_mode = req.body.updatedSettings.themeMode;
selectedNode.theme_color = req.body.updatedSettings.themeColor;
selectedNode.unannounced_channels = req.body.updatedSettings.unannouncedChannels;
selectedNode.fiat_conversion = req.body.updatedSettings.fiatConversion;
if (req.body.updatedSettings.fiatConversion) {
selectedNode.currency_unit = req.body.updatedSettings.currencyUnit ? req.body.updatedSettings.currencyUnit : 'USD';
export const updateSelectedNode = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Selected Node..' });
const selNodeIndex = req.params.currNodeIndex ? +req.params.currNodeIndex : common.selectedNode ? +common.selectedNode.index : 1;
req.session.selectedNode = common.findNode(selNodeIndex);
common.selectedNode = req.session.selectedNode;
if (req.headers && req.headers.authorization && req.headers.authorization !== '') {
wsServer.updateLNWSClientDetails(req.session.id, +req.session.selectedNode.index, +req.params.prevNodeIndex);
if (req.params.prevNodeIndex !== '-1') {
databaseService.unloadDatabase(req.params.prevNodeIndex, req.session.id);
}
else {
delete selectedNode.currency_unit;
if (req.params.currNodeIndex !== '-1') {
databaseService.loadDatabase(req.session);
}
common.replaceNode(req, selectedNode);
}
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'UI Settings Updated', data: maskPasswords(config) });
res.status(201).json({ message: 'Node Settings Updated Successfully' });
}
catch (errRes) {
const errMsg = 'Update Node Settings Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
};
export const update2FASettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating 2FA Settings..' });
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
if (req.body.secret2fa && req.body.secret2fa.trim() !== '') {
config.secret2fa = req.body.secret2fa;
}
else {
delete config.secret2fa;
}
const message = req.body.secret2fa.trim() === '' ? 'Two factor authentication disabled successfully.' : 'Two factor authentication enabled successfully.';
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
common.rtl_secret2fa = config.secret2fa;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: message });
res.status(201).json({ message: message });
}
catch (errRes) {
const errMsg = 'Update 2FA Settings Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
};
export const updateDefaultNode = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Default Node..' });
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.defaultNodeIndex = req.body.defaultNodeIndex;
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Default Node Updated', data: maskPasswords(config) });
res.status(201).json({ message: 'Default Node Updated Successfully' });
}
catch (errRes) {
const errMsg = 'Update Default Node Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
blockExplorerUrl = '';
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Selected Node Updated To ' + req.session.selectedNode.lnNode || '' });
res.status(200).json(common.removeAuthSecureData(JSON.parse(JSON.stringify(req.session.selectedNode))));
};
export const getConfig = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Reading Configuration File..' });
@ -215,14 +157,14 @@ export const getConfig = (req, res, next) => {
let fileFormat = 'INI';
switch (req.params.nodeType) {
case 'ln':
confFile = req.session.selectedNode.config_path;
confFile = req.session.selectedNode.authentication.configPath;
break;
case 'bitcoind':
confFile = req.session.selectedNode.bitcoind_config_path;
confFile = req.session.selectedNode.settings.bitcoindConfigPath;
break;
case 'rtl':
fileFormat = 'JSON';
confFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
confFile = common.appConfig.rtlConfFilePath + sep + 'RTL-Config.json';
break;
default:
confFile = '';
@ -248,144 +190,77 @@ export const getConfig = (req, res, next) => {
if (jsonConfig['Application Options'] && jsonConfig['Application Options'].color) {
jsonConfig['Application Options'].color = '#' + jsonConfig['Application Options'].color;
}
if (req.params.nodeType === 'ln' && req.session.selectedNode.ln_implementation === 'ECL' && !jsonConfig['eclair.api.password']) {
if (req.params.nodeType === 'ln' && req.session.selectedNode.lnImplementation === 'ECL' && !jsonConfig['eclair.api.password']) {
fileFormat = 'HOCON';
jsonConfig = parseHocon(data);
}
}
jsonConfig = maskPasswords(jsonConfig);
jsonConfig = common.maskPasswords(jsonConfig);
const responseJSON = (fileFormat === 'JSON') ? jsonConfig : ini.stringify(jsonConfig)?.replace('color=\\#', 'color=#');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Configuration File Data Received', data: responseJSON });
res.status(200).json({ format: fileFormat, data: responseJSON });
}
});
};
export const getFile = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting File..' });
const file = req.query.path ? req.query.path : (req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.query.channel?.replace(':', '-') + '.bak');
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'Channel Point', data: req.query.channel });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'File Path', data: file });
fs.readFile(file, 'utf8', (errRes, data) => {
if (errRes) {
if (errRes.code && errRes.code === 'ENOENT') {
errRes.code = 'File Not Found!';
}
const errMsg = 'Reading File Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
export const updateNodeSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Node Settings..' });
const RTLConfFile = common.appConfig.rtlConfFilePath + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const node = config.nodes.find((node) => (node.index === req.session.selectedNode.index));
if (node && node.settings) {
node.settings = req.body.settings;
if (req.body.authentication.boltzMacaroonPath) {
node.authentication.boltzMacaroonPath = req.body.authentication.boltzMacaroonPath;
}
else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'File Data Received', data: data });
res.status(200).json(data);
delete node.authentication.boltzMacaroonPath;
}
});
};
export const getCurrencyRates = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting Currency Rates..' });
options.url = 'https://blockchain.info/ticker';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Currency Rates Received', data: body });
res.status(200).json(JSON.parse(body));
}).catch((errRes) => {
const errMsg = 'Get Rates Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
});
};
export const updateSSO = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating SSO Settings..' });
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
delete config.SSO;
config.SSO = req.body.SSO;
if (req.body.authentication.swapMacaroonPath) {
node.authentication.swapMacaroonPath = req.body.authentication.swapMacaroonPath;
}
else {
delete node.authentication.swapMacaroonPath;
}
}
try {
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'SSO Setting Updated', data: maskPasswords(config) });
res.status(201).json({ message: 'SSO Updated Successfully' });
const selectedNode = common.findNode(req.session.selectedNode.index);
if (selectedNode && selectedNode.settings) {
selectedNode.settings = req.body.settings;
selectedNode.authentication.boltzMacaroonPath = req.body.authentication.boltzMacaroonPath;
selectedNode.authentication.swapMacaroonPath = req.body.authentication.swapMacaroonPath;
common.replaceNode(req, selectedNode);
}
let responseNode = JSON.parse(JSON.stringify(common.selectedNode));
responseNode = common.removeAuthSecureData(responseNode);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Node Settings Updated', data: responseNode });
res.status(201).json(responseNode);
}
catch (errRes) {
const errMsg = 'Update SSO Error';
const errMsg = 'Update Node Settings Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
};
export const updateServiceSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Service Settings..' });
const RTLConfFile = common.rtl_conf_file_path + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
const selectedNode = common.findNode(req.session.selectedNode.index);
config.nodes.forEach((node) => {
if (node.index === req.session.selectedNode.index) {
switch (req.body.service) {
case 'LOOP':
if (req.body.settings.enable) {
node.Settings.swapServerUrl = req.body.settings.serverUrl;
node.Authentication.swapMacaroonPath = req.body.settings.macaroonPath;
selectedNode.swap_server_url = req.body.settings.serverUrl;
selectedNode.swap_macaroon_path = req.body.settings.macaroonPath;
}
else {
delete node.Settings.swapServerUrl;
delete node.Authentication.swapMacaroonPath;
delete selectedNode.swap_server_url;
delete selectedNode.swap_macaroon_path;
}
break;
case 'BOLTZ':
if (req.body.settings.enable) {
node.Settings.boltzServerUrl = req.body.settings.serverUrl;
node.Authentication.boltzMacaroonPath = req.body.settings.macaroonPath;
selectedNode.boltz_server_url = req.body.settings.serverUrl;
selectedNode.boltz_macaroon_path = req.body.settings.macaroonPath;
}
else {
delete node.Settings.boltzServerUrl;
delete node.Authentication.boltzMacaroonPath;
delete selectedNode.boltz_server_url;
delete selectedNode.boltz_macaroon_path;
}
break;
case 'OFFERS':
node.Settings.enableOffers = req.body.settings.enableOffers;
selectedNode.enable_offers = req.body.settings.enableOffers;
break;
case 'PEERSWAP':
node.Settings.enablePeerswap = req.body.settings.enablePeerswap;
selectedNode.enable_peerswap = req.body.settings.enablePeerswap;
break;
default:
break;
}
common.replaceNode(req, selectedNode);
}
return node;
});
export const updateApplicationSettings = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Updating Application Settings..' });
const RTLConfFile = common.appConfig.rtlConfFilePath + sep + 'RTL-Config.json';
try {
const config = common.addSecureData(req.body);
common.appConfig = JSON.parse(JSON.stringify(config));
delete config.selectedNodeIndex;
delete config.enable2FA;
delete config.allowPasswordUpdate;
delete config.rtlConfFilePath;
delete config.rtlPass;
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Service Settings Updated', data: maskPasswords(config) });
res.status(201).json({ message: 'Service Settings Updated Successfully' });
const newConfig = JSON.parse(JSON.stringify(common.appConfig));
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Application Settings Updated', data: common.maskPasswords(newConfig) });
res.status(201).json(common.removeSecureData(newConfig));
}
catch (errRes) {
const errMsg = 'Update Service Settings Error';
const errMsg = 'Update Default Node Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.error, error: err.error });
}
};
export const maskPasswords = (obj) => {
const keys = Object.keys(obj);
const length = keys.length;
if (length !== 0) {
for (let i = 0; i < length; i++) {
if (typeof obj[keys[i]] === 'object') {
keys[keys[i]] = maskPasswords(obj[keys[i]]);
}
if (typeof keys[i] === 'string' &&
(keys[i].toLowerCase().includes('password') || keys[i].toLowerCase().includes('multipass') ||
keys[i].toLowerCase().includes('rpcpass') || keys[i].toLowerCase().includes('rpcpassword') ||
keys[i].toLowerCase().includes('rpcuser'))) {
obj[keys[i]] = '********************';
}
}
}
return obj;
};

@ -44,19 +44,20 @@ const handleMultipleFailedAttemptsError = (failed, currentTime, errMsg) => {
};
}
};
export const verifyToken = (twoFAToken) => !!(common.rtl_secret2fa && common.rtl_secret2fa !== '' && otplib.authenticator.check(twoFAToken, common.rtl_secret2fa));
export const verifyToken = (twoFAToken) => !!(common.appConfig.secret2FA && common.appConfig.secret2FA !== '' && otplib.authenticator.check(twoFAToken, common.appConfig.secret2FA));
export const authenticateUser = (req, res, next) => {
const { authenticateWith, authenticationValue, twoFAToken } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Authenticating User..' });
if (+common.rtl_sso) {
if (req.body.authenticateWith === 'JWT' && jwt.verify(req.body.authenticationValue, common.secret_key)) {
if (+common.appConfig.SSO.rtlSso) {
if (authenticateWith === 'JWT' && jwt.verify(authenticationValue, common.secret_key)) {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated' });
res.status(406).json({ message: 'SSO Authentication Error', error: 'Login with Password is not allowed with SSO.' });
}
else if (req.body.authenticateWith === 'PASSWORD') {
if (common.cookie_value.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(common.cookie_value).digest('hex'), 'utf-8'), Buffer.from(req.body.authenticationValue, 'utf-8'))) {
else if (authenticateWith === 'PASSWORD') {
if (common.appConfig.SSO.cookieValue.trim().length >= 32 && crypto.timingSafeEqual(Buffer.from(crypto.createHash('sha256').update(common.appConfig.SSO.cookieValue).digest('hex'), 'utf-8'), Buffer.from(authenticationValue, 'utf-8'))) {
common.refreshCookie();
if (!req.session.selectedNode) {
req.session.selectedNode = common.initSelectedNode;
req.session.selectedNode = common.selectedNode;
}
const token = jwt.sign({ user: 'SSO_USER' }, common.secret_key);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'User Authenticated' });
@ -73,10 +74,10 @@ export const authenticateUser = (req, res, next) => {
const currentTime = new Date().getTime();
const reqIP = common.getRequestIP(req);
const failed = getFailedInfo(reqIP, currentTime);
const password = req.body.authenticationValue;
if (common.rtl_pass === password && failed.count < ALLOWED_LOGIN_ATTEMPTS) {
if (req.body.twoFAToken && req.body.twoFAToken !== '') {
if (!verifyToken(req.body.twoFAToken)) {
const password = authenticationValue;
if (common.appConfig.rtlPass === password && failed.count < ALLOWED_LOGIN_ATTEMPTS) {
if (twoFAToken && twoFAToken !== '') {
if (!verifyToken(twoFAToken)) {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Authenticate', msg: 'Invalid Token! Failed IP ' + reqIP, error: { error: 'Invalid token.' } });
failed.count = failed.count + 1;
failed.lastTried = currentTime;
@ -84,7 +85,7 @@ export const authenticateUser = (req, res, next) => {
}
}
if (!req.session.selectedNode) {
req.session.selectedNode = common.initSelectedNode;
req.session.selectedNode = common.selectedNode;
}
delete failedLoginAttempts[reqIP];
const token = jwt.sign({ user: 'NODE_USER' }, common.secret_key);
@ -93,23 +94,23 @@ export const authenticateUser = (req, res, next) => {
}
else {
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Authenticate', msg: 'Invalid Password! Failed IP ' + reqIP, error: { error: 'Invalid password.' } });
failed.count = common.rtl_pass !== password ? (failed.count + 1) : failed.count;
failed.lastTried = common.rtl_pass !== password ? currentTime : failed.lastTried;
failed.count = common.appConfig.rtlPass !== password ? (failed.count + 1) : failed.count;
failed.lastTried = common.appConfig.rtlPass !== password ? currentTime : failed.lastTried;
return res.status(401).json(handleMultipleFailedAttemptsError(failed, currentTime, 'Invalid Password!'));
}
}
};
export const resetPassword = (req, res, next) => {
const { currPassword, newPassword } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Resetting Password..' });
if (+common.rtl_sso) {
if (+common.appConfig.SSO.rtlSso) {
const errMsg = 'Password cannot be reset for SSO authentication';
const err = common.handleError({ statusCode: 401, message: 'Password Reset Error', error: errMsg }, 'Authenticate', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
else {
const currPassword = req.body.currPassword;
if (common.rtl_pass === currPassword) {
common.rtl_pass = common.replacePasswordWithHash(req.body.newPassword);
if (common.appConfig.rtlPass === currPassword) {
common.appConfig.rtlPass = common.replacePasswordWithHash(newPassword);
const token = jwt.sign({ user: 'NODE_USER' }, common.secret_key);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Authenticate', msg: 'Password Reset Successful' });
res.status(200).json({ token: token });

@ -79,6 +79,7 @@ export const getSwapInfo = (req, res, next) => {
});
};
export const createSwap = (req, res, next) => {
const { amount, sendFromInternal, address } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Swap..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -87,9 +88,12 @@ export const createSwap = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createswap';
options.body = { amount: req.body.amount };
if (req.body.address !== '') {
options.body.address = req.body.address;
options.body = { amount: amount };
if (sendFromInternal) {
options.body.send_from_internal = sendFromInternal;
}
if (address && address !== '') {
options.body.address = address;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Boltz', msg: 'Create Swap Options Body', data: options.body });
request.post(options).then((createSwapRes) => {
@ -101,6 +105,7 @@ export const createSwap = (req, res, next) => {
});
};
export const createReverseSwap = (req, res, next) => {
const { amount, acceptZeroConf, address } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Reverse Swap..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -109,9 +114,9 @@ export const createReverseSwap = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createreverseswap';
options.body = { amount: req.body.amount };
if (req.body.address !== '') {
options.body.address = req.body.address;
options.body = { amount: amount, accept_zero_conf: acceptZeroConf || false };
if (address && address !== '') {
options.body.address = address;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Boltz', msg: 'Create Reverse Swap Body', data: options.body });
request.post(options).then((createReverseSwapRes) => {
@ -123,6 +128,7 @@ export const createReverseSwap = (req, res, next) => {
});
};
export const createChannel = (req, res, next) => {
const { amount, address } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Boltz Channel..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -131,9 +137,9 @@ export const createChannel = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createchannel';
options.body = { amount: req.body.amount };
if (req.body.address !== '') {
options.body.address = req.body.address;
options.body = { amount: amount };
if (address && address !== '') {
options.body.address = address;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Boltz', msg: 'Create Channel Options Body', data: options.body });
request.post(options).then((createChannelRes) => {

@ -5,30 +5,25 @@ let options = null;
const logger = Logger;
const common = Common;
export const loopOut = (req, res, next) => {
const { amount, targetConf, swapRoutingFee, minerFee, prepayRoutingFee, prepayAmt, swapFee, swapPublicationDeadline, chanId, destAddress } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Looping Out..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop Out Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/out';
options.uri = '/v1/loop/out';
options.body = {
amt: req.body.amount,
sweep_conf_target: req.body.targetConf,
max_swap_routing_fee: req.body.swapRoutingFee,
max_miner_fee: req.body.minerFee,
max_prepay_routing_fee: req.body.prepayRoutingFee,
max_prepay_amt: req.body.prepayAmt,
max_swap_fee: req.body.swapFee,
swap_publication_deadline: req.body.swapPublicationDeadline,
amt: amount,
sweep_conf_target: targetConf,
max_swap_routing_fee: swapRoutingFee,
max_miner_fee: minerFee,
max_prepay_routing_fee: prepayRoutingFee,
max_prepay_amt: prepayAmt,
max_swap_fee: swapFee,
swap_publication_deadline: swapPublicationDeadline,
initiator: 'RTL'
};
if (req.body.chanId !== '') {
options.body['loop_out_channel'] = req.body.chanId;
if (chanId !== '') {
options.body['loop_out_channel'] = chanId;
}
if (req.body.destAddress !== '') {
options.body['dest'] = req.body.destAddress;
if (destAddress !== '') {
options.body['dest'] = destAddress;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Out Body', data: options.body });
request.post(options).then((loopOutRes) => {
@ -41,13 +36,7 @@ export const loopOut = (req, res, next) => {
};
export const loopOutTerms = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop Out Terms..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop Out Terms Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/out/terms';
options.uri = '/v1/loop/out/terms';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Loop Out Terms Received', data: body });
res.status(200).json(body);
@ -58,13 +47,7 @@ export const loopOutTerms = (req, res, next) => {
};
export const loopOutQuote = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop Out Quotes..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop Out Quotes Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/out/quote/' + req.params.amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
options.uri = '/v1/loop/out/quote/' + req.params.amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Out Quote URL', data: options.url });
request(options).then((quoteRes) => {
quoteRes.amount = +req.params.amount;
@ -78,19 +61,13 @@ export const loopOutQuote = (req, res, next) => {
};
export const loopOutTermsAndQuotes = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop Out Terms & Quotes..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop Out Terms & Quotes Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/out/terms';
options.uri = '/v1/loop/out/terms';
request(options).then((terms) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Out Terms Received', data: terms });
const options1 = common.getSwapServerOptions(req);
const options2 = common.getSwapServerOptions(req);
options1.url = options1.url + '/v1/loop/out/quote/' + terms.min_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
options2.url = options2.url + '/v1/loop/out/quote/' + terms.max_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
const options1 = options;
const options2 = options;
options1.uri = '/v1/loop/out/quote/' + terms.min_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
options2.uri = '/v1/loop/out/quote/' + terms.max_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Out Min Quote Options', data: options1 });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Out Max Quote Options', data: options2 });
return Promise.all([request(options1), request(options2)]).then((values) => {
@ -111,18 +88,13 @@ export const loopOutTermsAndQuotes = (req, res, next) => {
});
};
export const loopIn = (req, res, next) => {
const { amount, swapFee, minerFee } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Looping In..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop In Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/in';
options.uri = '/v1/loop/in';
options.body = {
amt: req.body.amount,
max_swap_fee: req.body.swapFee,
max_miner_fee: req.body.minerFee,
amt: amount,
max_swap_fee: swapFee,
max_miner_fee: minerFee,
initiator: 'RTL'
};
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Loop In Body', data: options.body });
@ -136,13 +108,7 @@ export const loopIn = (req, res, next) => {
};
export const loopInTerms = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop In Terms..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop In Terms Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/in/terms';
options.uri = '/v1/loop/in/terms';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Loop In Terms Received', data: body });
res.status(200).json(body);
@ -153,13 +119,7 @@ export const loopInTerms = (req, res, next) => {
};
export const loopInQuote = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop In Quotes..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop In Quotes Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/in/quote/' + req.params.amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
options.uri = '/v1/loop/in/quote/' + req.params.amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop In Quote Options', data: options.url });
request(options).then((body) => {
body.amount = +req.params.amount;
@ -173,19 +133,13 @@ export const loopInQuote = (req, res, next) => {
};
export const loopInTermsAndQuotes = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop In Terms & Quotes..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Loop In Terms & Quotes Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/in/terms';
options.uri = '/v1/loop/in/terms';
request(options).then((terms) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop In Terms Received', data: terms });
const options1 = common.getSwapServerOptions(req);
const options2 = common.getSwapServerOptions(req);
options1.url = options1.url + '/v1/loop/in/quote/' + terms.min_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
options2.url = options2.url + '/v1/loop/in/quote/' + terms.max_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
const options1 = options;
const options2 = options;
options1.uri = '/v1/loop/in/quote/' + terms.min_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
options2.uri = '/v1/loop/in/quote/' + terms.max_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop In Min Quote Options', data: options1 });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop In Max Quote Options', data: options2 });
return Promise.all([request(options1), request(options2)]).then((values) => {
@ -207,13 +161,12 @@ export const loopInTermsAndQuotes = (req, res, next) => {
};
export const swaps = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting List Swaps..' });
options = common.getSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'List Swaps Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/swaps';
options.uri = '/v1/loop/swaps';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Loop', msg: 'Loop Swaps Received', data: body });
res.status(200).json(body.swaps);
@ -224,18 +177,29 @@ export const swaps = (req, res, next) => {
};
export const swap = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Swap Information..' });
options = common.getSwapServerOptions(req);
options.uri = '/v1/loop/swap/' + req.params.id;
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Loop Swap Information Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Loop', 'Get Swap Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const loopInfo = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Getting Loop Information..' });
options = common.setSwapServerOptions(req);
if (options.url === '') {
const errMsg = 'Loop Server URL is missing in the configuration.';
const err = common.handleError({ statusCode: 500, message: 'Get Swap Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
const err = common.handleError({ statusCode: 500, message: 'Get Loop Info Error', error: errMsg }, 'Loop', errMsg, req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/loop/swap/' + req.params.id;
options.uri = '/v1/loop/info';
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Loop Swap Information Received', data: body });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Loop', msg: 'Loop Information Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Loop', 'Get Swap Error', req.session.selectedNode);
const err = common.handleError(errRes, 'Loop', 'Get Loop Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

@ -1,59 +1,72 @@
export class CommonSelectedNode {
constructor(options, ln_server_url, macaroon_path, ln_api_password, swap_server_url, boltz_server_url, config_path, rtl_conf_file_path, swap_macaroon_path, boltz_macaroon_path, bitcoind_config_path, channel_backup_path, log_level, log_file, index, ln_node, ln_implementation, user_persona, theme_mode, theme_color, unannounced_channels, fiat_conversion, currency_unit, ln_version, api_version, enable_offers, enable_peerswap) {
this.options = options;
this.ln_server_url = ln_server_url;
this.macaroon_path = macaroon_path;
this.ln_api_password = ln_api_password;
this.swap_server_url = swap_server_url;
this.boltz_server_url = boltz_server_url;
this.config_path = config_path;
this.rtl_conf_file_path = rtl_conf_file_path;
this.swap_macaroon_path = swap_macaroon_path;
this.boltz_macaroon_path = boltz_macaroon_path;
this.bitcoind_config_path = bitcoind_config_path;
this.channel_backup_path = channel_backup_path;
this.log_level = log_level;
this.log_file = log_file;
this.index = index;
this.ln_node = ln_node;
this.ln_implementation = ln_implementation;
this.user_persona = user_persona;
this.theme_mode = theme_mode;
this.theme_color = theme_color;
this.unannounced_channels = unannounced_channels;
this.fiat_conversion = fiat_conversion;
this.currency_unit = currency_unit;
this.ln_version = ln_version;
this.api_version = api_version;
this.enable_offers = enable_offers;
this.enable_peerswap = enable_peerswap;
}
}
export class AuthenticationConfiguration {
constructor(configPath, swapMacaroonPath, boltzMacaroonPath) {
this.configPath = configPath;
this.swapMacaroonPath = swapMacaroonPath;
this.boltzMacaroonPath = boltzMacaroonPath;
export class SSO {
constructor(rtlSso, rtlCookiePath, logoutRedirectLink, cookieValue) {
this.rtlSso = rtlSso;
this.rtlCookiePath = rtlCookiePath;
this.logoutRedirectLink = logoutRedirectLink;
this.cookieValue = cookieValue;
}
}
export class NodeSettingsConfiguration {
constructor(userPersona, themeMode, themeColor, unannouncedChannels, fiatConversion, currencyUnit, bitcoindConfigPath, logLevel, lnServerUrl, swapServerUrl, boltzServerUrl, channelBackupPath, enableOffers, enablePeerswap) {
export class Settings {
constructor(blockExplorerUrl, lnServerUrl, swapServerUrl, boltzServerUrl, bitcoindConfigPath, channelBackupPath, logLevel, logFile, userPersona, themeMode, themeColor, unannouncedChannels, fiatConversion, currencyUnit, enableOffers, enablePeerswap) {
this.blockExplorerUrl = blockExplorerUrl;
this.lnServerUrl = lnServerUrl;
this.swapServerUrl = swapServerUrl;
this.boltzServerUrl = boltzServerUrl;
this.bitcoindConfigPath = bitcoindConfigPath;
this.channelBackupPath = channelBackupPath;
this.logLevel = logLevel;
this.logFile = logFile;
this.userPersona = userPersona;
this.themeMode = themeMode;
this.themeColor = themeColor;
this.unannouncedChannels = unannouncedChannels;
this.fiatConversion = fiatConversion;
this.currencyUnit = currencyUnit;
this.bitcoindConfigPath = bitcoindConfigPath;
this.logLevel = logLevel;
this.lnServerUrl = lnServerUrl;
this.swapServerUrl = swapServerUrl;
this.boltzServerUrl = boltzServerUrl;
this.channelBackupPath = channelBackupPath;
this.enableOffers = enableOffers;
this.enablePeerswap = enablePeerswap;
}
}
export class Authentication {
constructor(options, configPath, macaroonPath, macaroonValue, runePath, runeValue, lnApiPassword, swapMacaroonPath, boltzMacaroonPath) {
this.options = options;
this.configPath = configPath;
this.macaroonPath = macaroonPath;
this.macaroonValue = macaroonValue;
this.runePath = runePath;
this.runeValue = runeValue;
this.lnApiPassword = lnApiPassword;
this.swapMacaroonPath = swapMacaroonPath;
this.boltzMacaroonPath = boltzMacaroonPath;
}
}
export class ApplicationConfig {
constructor(defaultNodeIndex, selectedNodeIndex, dbDirectoryPath, rtlConfFilePath, rtlPass, multiPass, multiPassHashed, allowPasswordUpdate, enable2FA, secret2FA, SSO, nodes) {
this.defaultNodeIndex = defaultNodeIndex;
this.selectedNodeIndex = selectedNodeIndex;
this.dbDirectoryPath = dbDirectoryPath;
this.rtlConfFilePath = rtlConfFilePath;
this.rtlPass = rtlPass;
this.multiPass = multiPass;
this.multiPassHashed = multiPassHashed;
this.allowPasswordUpdate = allowPasswordUpdate;
this.enable2FA = enable2FA;
this.secret2FA = secret2FA;
this.SSO = SSO;
this.nodes = nodes;
}
}
export class SelectedNode {
constructor(logLevel, logFile, index, lnNode, lnImplementation, lnVersion, settings, authentication) {
this.logLevel = logLevel;
this.logFile = logFile;
this.index = index;
this.lnNode = lnNode;
this.lnImplementation = lnImplementation;
this.lnVersion = lnVersion;
this.settings = settings;
this.authentication = authentication;
}
}
export class LogJSONObj {
constructor(level, msg, data, error, fileName, selectedNode) {
this.level = level;

@ -3,15 +3,15 @@ export var OfferFieldsEnum;
OfferFieldsEnum["BOLT12"] = "bolt12";
OfferFieldsEnum["AMOUNTMSAT"] = "amountMSat";
OfferFieldsEnum["TITLE"] = "title";
OfferFieldsEnum["VENDOR"] = "vendor";
OfferFieldsEnum["ISSUER"] = "issuer";
OfferFieldsEnum["DESCRIPTION"] = "description";
})(OfferFieldsEnum || (OfferFieldsEnum = {}));
export class Offer {
constructor(bolt12, amountMSat, title, vendor, description, lastUpdatedAt) {
constructor(bolt12, amountMSat, title, issuer, description, lastUpdatedAt) {
this.bolt12 = bolt12;
this.amountMSat = amountMSat;
this.title = title;
this.vendor = vendor;
this.issuer = issuer;
this.description = description;
this.lastUpdatedAt = lastUpdatedAt;
}
@ -137,3 +137,25 @@ export const CollectionFieldsEnum = { ...OfferFieldsEnum, ...PageSettingsFieldsE
export const LNDCollection = [CollectionsEnum.PAGE_SETTINGS];
export const ECLCollection = [CollectionsEnum.PAGE_SETTINGS];
export const CLNCollection = [CollectionsEnum.PAGE_SETTINGS, CollectionsEnum.OFFERS];
export const ECL_UPDATED_DB = [
{
pageId: 'peers_channels',
tables: [
{
tableId: 'open_channels',
removed: ['buried', 'feeRatePerKw'],
renamed: ['isFunder:isInitiator']
},
{
tableId: 'pending_channels',
removed: ['buried', 'feeRatePerKw'],
renamed: ['isFunder:isInitiator']
},
{
tableId: 'inactive_channels',
removed: ['buried', 'feeRatePerKw'],
renamed: ['isFunder:isInitiator']
}
]
}
];

@ -1,7 +0,0 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getBalance } from '../../controllers/cln/balance.js';
const router = Router();
router.get('/', isAuthenticated, getBalance);
export default router;

@ -1,14 +1,12 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
import { listPeerChannels, openChannel, setChannelFee, closeChannel, listForwards, funderUpdatePolicy } from '../../controllers/cln/channels.js';
const router = Router();
router.get('/listChannels', isAuthenticated, listChannels);
router.get('/listPeerChannels', isAuthenticated, listPeerChannels);
router.post('/', isAuthenticated, openChannel);
router.post('/setChannelFee', isAuthenticated, setChannelFee);
router.delete('/:channelId', isAuthenticated, closeChannel);
router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance);
router.get('/listForwards', isAuthenticated, listForwards);
router.get('/listForwardsPaginated', isAuthenticated, listForwardsPaginated);
router.post('/close/', isAuthenticated, closeChannel);
router.post('/listForwards', isAuthenticated, listForwards);
router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy);
export default router;

@ -1,7 +0,0 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getFees } from '../../controllers/cln/fees.js';
const router = Router();
router.get('/', isAuthenticated, getFees);
export default router;

@ -1,8 +1,6 @@
import exprs from 'express';
const { Router } = exprs;
import infoCLRoutes from './getInfo.js';
import feesCLRoutes from './fees.js';
import balanceCLRoutes from './balance.js';
import channelsCLRoutes from './channels.js';
import invoicesCLRoutes from './invoices.js';
import onChainCLRoutes from './onchain.js';
@ -14,8 +12,6 @@ import utilityCLRoutes from './utility.js';
const router = Router();
const clRoutes = [
{ path: '/getinfo', route: infoCLRoutes },
{ path: '/fees', route: feesCLRoutes },
{ path: '/balance', route: balanceCLRoutes },
{ path: '/channels', route: channelsCLRoutes },
{ path: '/invoices', route: invoicesCLRoutes },
{ path: '/onchain', route: onChainCLRoutes },

@ -3,7 +3,7 @@ const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { listInvoices, addInvoice, deleteExpiredInvoice } from '../../controllers/cln/invoices.js';
const router = Router();
router.get('/', isAuthenticated, listInvoices);
router.post('/lookup/', isAuthenticated, listInvoices);
router.post('/', isAuthenticated, addInvoice);
router.delete('/', isAuthenticated, deleteExpiredInvoice);
router.post('/delete/', isAuthenticated, deleteExpiredInvoice);
export default router;

@ -1,11 +1,10 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getRoute, listNode, listChannel, feeRates, listNodes } from '../../controllers/cln/network.js';
import { getRoute, listChannels, feeRates, listNodes } from '../../controllers/cln/network.js';
const router = Router();
router.get('/getRoute/:destPubkey/:amount', isAuthenticated, getRoute);
router.get('/listNode/:id', isAuthenticated, listNode);
router.get('/listChannel/:channelShortId', isAuthenticated, listChannel);
router.get('/feeRates/:feeRateStyle', isAuthenticated, feeRates);
router.get('/listNodes', isAuthenticated, listNodes);
router.post('/listNodes', isAuthenticated, listNodes);
router.post('/getRoute', isAuthenticated, getRoute);
router.post('/feeRates', isAuthenticated, feeRates);
router.post('/listChannels', isAuthenticated, listChannels);
export default router;

@ -4,9 +4,9 @@ import { isAuthenticated } from '../../utils/authCheck.js';
import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/cln/offers.js';
const router = Router();
router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks);
router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark);
router.post('/offerbookmark/delete', isAuthenticated, deleteOfferBookmark);
router.get('/', isAuthenticated, listOffers);
router.post('/', isAuthenticated, createOffer);
router.post('/fetchOfferInvoice', isAuthenticated, fetchOfferInvoice);
router.delete('/:offerID', isAuthenticated, disableOffer);
router.post('/disableOffer', isAuthenticated, disableOffer);
export default router;

@ -3,7 +3,7 @@ const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getNewAddress, onChainWithdraw, getUTXOs } from '../../controllers/cln/onchain.js';
const router = Router();
router.get('/', isAuthenticated, getNewAddress);
router.post('/', isAuthenticated, onChainWithdraw);
router.post('/newaddr', isAuthenticated, getNewAddress);
router.get('/utxos/', isAuthenticated, getUTXOs);
export default router;

@ -5,5 +5,5 @@ import { getPeers, postPeer, deletePeer } from '../../controllers/cln/peers.js';
const router = Router();
router.get('/', isAuthenticated, getPeers);
router.post('/', isAuthenticated, postPeer);
router.delete('/:peerId', isAuthenticated, deletePeer);
router.post('/disconnect/', isAuthenticated, deletePeer);
export default router;

@ -1,10 +1,9 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { decodePayments, decodePayment, signMessage, verifyMessage, listConfigs } from '../../controllers/cln/utility.js';
import { decodePayment, signMessage, verifyMessage, listConfigs } from '../../controllers/cln/utility.js';
const router = Router();
router.get('/', isAuthenticated, decodePayments);
router.get('/decode/:payReq', isAuthenticated, decodePayment);
router.post('/decode', isAuthenticated, decodePayment);
router.post('/sign', isAuthenticated, signMessage);
router.post('/verify', isAuthenticated, verifyMessage);
router.get('/listConfigs', isAuthenticated, listConfigs);

@ -1,11 +1,12 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel } from '../../controllers/eclair/channels.js';
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel, circularRebalance } from '../../controllers/eclair/channels.js';
const router = Router();
router.get('/', isAuthenticated, getChannels);
router.get('/stats', isAuthenticated, getChannelStats);
router.post('/', isAuthenticated, openChannel);
router.post('/updateRelayFee', isAuthenticated, updateChannelRelayFee);
router.post('/circularRebalance', circularRebalance);
router.delete('/', isAuthenticated, closeChannel);
export default router;

@ -1,7 +1,8 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getNodes } from '../../controllers/eclair/network.js';
import { getNodes, findRouteBetweenNodes } from '../../controllers/eclair/network.js';
const router = Router();
router.get('/nodes/:id', isAuthenticated, getNodes);
router.get('/routebetweennodes', isAuthenticated, findRouteBetweenNodes);
export default router;

@ -1,10 +1,11 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment } from '../../controllers/eclair/payments.js';
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment, sendPaymentToRoute } from '../../controllers/eclair/payments.js';
const router = Router();
router.get('/route/', isAuthenticated, queryPaymentRoute);
router.get('/decode/:invoice', isAuthenticated, decodePayment);
router.post('/getsentinfos', isAuthenticated, getSentPaymentsInformation);
router.post('/sendtoroute', isAuthenticated, sendPaymentToRoute);
router.post('/', isAuthenticated, postPayment);
export default router;

@ -1,17 +1,15 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { getRTLConfigInitial, getRTLConfig, updateUISettings, update2FASettings, getConfig, getFile, updateSelectedNode, updateDefaultNode, updateServiceSettings, updateSSO, getCurrencyRates } from '../../controllers/shared/RTLConf.js';
import { updateNodeSettings, getConfig, getFile, updateSelectedNode, updateApplicationSettings, getCurrencyRates, getApplicationSettings, getExplorerFeesRecommended, getExplorerTransaction } from '../../controllers/shared/RTLConf.js';
const router = Router();
router.get('/rtlconfinit', getRTLConfigInitial);
router.get('/rtlconf', isAuthenticated, getRTLConfig);
router.post('/', isAuthenticated, updateUISettings);
router.post('/update2FA', isAuthenticated, update2FASettings);
router.get('/config/:nodeType', isAuthenticated, getConfig);
router.get('/', getApplicationSettings);
router.get('/rates', getCurrencyRates);
router.get('/file', isAuthenticated, getFile);
router.get('/updateSelNode/:currNodeIndex/:prevNodeIndex', updateSelectedNode);
router.post('/updateDefaultNode', updateDefaultNode);
router.post('/updateServiceSettings', updateServiceSettings);
router.post('/updateSSO', updateSSO);
router.get('/rates', getCurrencyRates);
router.get('/config/:nodeType', isAuthenticated, getConfig);
router.post('/node', isAuthenticated, updateNodeSettings);
router.post('/application', isAuthenticated, updateApplicationSettings);
router.get('/explorerFeesRecommended', getExplorerFeesRecommended);
router.get('/explorerTransaction/:txid', getExplorerTransaction);
export default router;

@ -1,8 +1,9 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { loopInTerms, loopInQuote, loopInTermsAndQuotes, loopIn, loopOutTerms, loopOutQuote, loopOutTermsAndQuotes, loopOut, swaps, swap } from '../../controllers/shared/loop.js';
import { loopInfo, loopInTerms, loopInQuote, loopInTermsAndQuotes, loopIn, loopOutTerms, loopOutQuote, loopOutTermsAndQuotes, loopOut, swaps, swap } from '../../controllers/shared/loop.js';
const router = Router();
router.get('/info', isAuthenticated, loopInfo);
router.get('/in/terms', isAuthenticated, loopInTerms);
router.get('/in/quote/:amount', isAuthenticated, loopInQuote);
router.get('/in/termsAndQuotes', isAuthenticated, loopInTermsAndQuotes);

@ -10,10 +10,9 @@ import sharedRoutes from '../routes/shared/index.js';
import lndRoutes from '../routes/lnd/index.js';
import clnRoutes from '../routes/cln/index.js';
import eclRoutes from '../routes/eclair/index.js';
import { Database } from './database.js';
import { Common } from './common.js';
import { Logger } from './logger.js';
import { Config } from './config.js';
import { CLWSClient } from '../controllers/cln/webSocketClient.js';
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
const ONE_DAY = 1000 * 60 * 60 * 24;
@ -22,19 +21,16 @@ export class ExpressApplication {
this.app = express();
this.logger = Logger;
this.common = Common;
this.config = Config;
this.eclWsClient = ECLWSClient;
this.clWsClient = CLWSClient;
// public clWsClient: CLWebSocketClient = CLWSClient;
this.lndWsClient = LNDWSClient;
this.databaseService = Database;
this.directoryName = dirname(fileURLToPath(import.meta.url));
this.getApp = () => this.app;
this.loadConfiguration = () => {
this.config.setServerConfiguration();
};
this.setCORS = () => { CORS.mount(this.app); };
this.setCSRF = () => { CSRF.mount(this.app); };
this.setApplicationRoutes = () => {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Setting up Application Routes..' });
this.app.use(this.common.baseHref + '/api', sharedRoutes);
this.app.use(this.common.baseHref + '/api/lnd', lndRoutes);
this.app.use(this.common.baseHref + '/api/cln', clnRoutes);
@ -49,42 +45,42 @@ export class ExpressApplication {
this.handleApplicationErrors(err, res);
next();
});
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Application Routes Set' });
};
this.handleApplicationErrors = (err, res) => {
switch (err.code) {
case 'EACCES':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server requires elevated privileges' });
res.status(406).send('Server requires elevated privileges.');
break;
case 'EADDRINUSE':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is already in use' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is already in use' });
res.status(409).send('Server is already in use.');
break;
case 'ECONNREFUSED':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is down/locked' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Server is down/locked' });
res.status(401).send('Server is down/locked.');
break;
case 'EBADCSRFTOKEN':
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'Invalid CSRF token. Form tempered.' });
res.status(403).send('Invalid CSRF token, form tempered.');
break;
default:
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'App', msg: 'DEFUALT ERROR', error: err });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'App', msg: 'DEFUALT ERROR', error: err });
res.status(400).send(JSON.stringify(err));
break;
}
};
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'App', msg: 'Starting Express Application..' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'App', msg: 'Starting Express Application..' });
this.app.set('trust proxy', true);
this.app.use(sessions({ secret: this.common.secret_key, saveUninitialized: true, cookie: { secure: false, maxAge: ONE_DAY }, resave: false }));
this.app.use(cookieParser(this.common.secret_key));
this.app.use(bodyParser.json({ limit: '25mb' }));
this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' }));
this.loadConfiguration();
this.setCORS();
this.setCSRF();
this.setApplicationRoutes();
this.databaseService.migrateDatabase();
}
}
export default ExpressApplication;

@ -46,7 +46,7 @@ export const verifyWSUser = (info, next) => {
catch (err) {
cookies = {};
updatedReq['cookies'] = JSON.parse(cookies);
logger.log({ selectedNode: common.initSelectedNode, level: 'WARN', fileName: 'AuthCheck', msg: '403 Unable to read CSRF token cookie', data: err });
logger.log({ selectedNode: common.selectedNode, level: 'WARN', fileName: 'AuthCheck', msg: '403 Unable to read CSRF token cookie', data: err });
}
csurfProtection(updatedReq, null, (err) => {
if (err) {
@ -58,7 +58,7 @@ export const verifyWSUser = (info, next) => {
});
}
catch (err) {
logger.log({ selectedNode: common.initSelectedNode, level: 'WARN', fileName: 'AuthCheck', msg: '403 Unable to verify CSRF token', data: err });
logger.log({ selectedNode: common.selectedNode, level: 'WARN', fileName: 'AuthCheck', msg: '403 Unable to verify CSRF token', data: err });
next(true);
}
}

@ -8,18 +8,11 @@ export class CommonService {
constructor() {
this.logger = Logger;
this.nodes = [];
this.initSelectedNode = null;
this.rtl_conf_file_path = '';
this.selectedNode = null;
this.ssoInit = { rtlSso: 0, rtlCookiePath: '', logoutRedirectLink: '', cookieValue: '' };
this.appConfig = { defaultNodeIndex: 0, selectedNodeIndex: 0, rtlConfFilePath: '', dbDirectoryPath: join(dirname(fileURLToPath(import.meta.url)), '..', '..'), rtlPass: '', allowPasswordUpdate: true, enable2FA: false, secret2FA: '', SSO: this.ssoInit, nodes: [] };
this.port = 3000;
this.host = null;
this.rtl_pass = '';
this.flg_allow_password_update = true;
this.rtl_secret2fa = '';
this.rtl_sso = 0;
this.rtl_cookie_path = '';
this.logout_redirect_link = '';
this.cookie_value = '';
this.api_version = '';
this.host = '';
this.secret_key = crypto.randomBytes(64).toString('hex');
this.read_dummy_data = false;
this.baseHref = '/rtl';
@ -28,128 +21,219 @@ export class CommonService {
{ name: 'JAN', days: 31 }, { name: 'FEB', days: 28 }, { name: 'MAR', days: 31 }, { name: 'APR', days: 30 }, { name: 'MAY', days: 31 }, { name: 'JUN', days: 30 },
{ name: 'JUL', days: 31 }, { name: 'AUG', days: 31 }, { name: 'SEP', days: 30 }, { name: 'OCT', days: 31 }, { name: 'NOV', days: 30 }, { name: 'DEC', days: 31 }
];
this.getSwapServerOptions = (req) => {
this.maskPasswords = (obj) => {
const keys = Object.keys(obj);
const length = keys.length;
if (length !== 0) {
for (let i = 0; i < length; i++) {
if (typeof obj[keys[i]] === 'object') {
keys[keys[i]] = this.maskPasswords(obj[keys[i]]);
}
if (typeof keys[i] === 'string' &&
((keys[i].toLowerCase().includes('password') && keys[i] !== 'allowPasswordUpdate') || keys[i].toLowerCase().includes('multipass') ||
keys[i].toLowerCase().includes('rpcpass') || keys[i].toLowerCase().includes('rpcpassword') ||
keys[i].toLowerCase().includes('rpcuser'))) {
obj[keys[i]] = '*'.repeat(20);
}
}
}
return obj;
};
this.removeAuthSecureData = (node) => {
delete node.authentication.macaroonPath;
delete node.authentication.runePath;
delete node.authentication.runeValue;
delete node.authentication.lnApiPassword;
delete node.authentication.options;
return node;
};
this.removeSecureData = (config) => {
delete config.rtlConfFilePath;
delete config.rtlPass;
delete config.multiPass;
delete config.multiPassHashed;
delete config.secret2FA;
config.nodes?.map((node) => this.removeAuthSecureData(node));
return config;
};
this.addSecureData = (config) => {
config.rtlConfFilePath = this.appConfig.rtlConfFilePath;
config.rtlPass = this.appConfig.rtlPass;
config.multiPassHashed = this.appConfig.multiPassHashed;
config.SSO.rtlCookiePath = this.appConfig.SSO.rtlCookiePath;
if (this.appConfig.multiPass) {
config.multiPass = this.appConfig.multiPass;
}
if (config.secret2FA === this.appConfig.secret2FA) {
config.secret2FA = this.appConfig.secret2FA;
}
config.nodes.map((node, i) => {
if (this.appConfig && this.appConfig.nodes && this.appConfig.nodes.length > i && this.appConfig.nodes[i].authentication) {
if (this.appConfig.nodes[i].authentication.macaroonPath) {
node.authentication.macaroonPath = this.appConfig.nodes[i].authentication.macaroonPath;
}
if (this.appConfig.nodes[i].authentication.runePath) {
node.authentication.runePath = this.appConfig.nodes[i].authentication.runePath;
}
if (this.appConfig.nodes[i].authentication.lnApiPassword) {
node.authentication.lnApiPassword = this.appConfig.nodes[i].authentication.lnApiPassword;
}
}
return node;
});
return config;
};
this.setSwapServerOptions = (req) => {
const swapOptions = {
url: req.session.selectedNode.swap_server_url,
baseUrl: req.session.selectedNode.settings.swapServerUrl,
uri: '',
rejectUnauthorized: false,
json: true,
headers: { 'Grpc-Metadata-macaroon': '' }
};
if (req.session.selectedNode.swap_macaroon_path) {
if (req.session.selectedNode.authentication.swapMacaroonPath) {
try {
swapOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.swap_macaroon_path, 'loop.macaroon')).toString('hex') };
swapOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.authentication.swapMacaroonPath, 'loop.macaroon')).toString('hex') };
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Loop macaroon Error', error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Loop macaroon Error', error: err });
}
}
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Swap Options', data: swapOptions });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Swap Options', data: swapOptions });
return swapOptions;
};
this.getBoltzServerOptions = (req) => {
const boltzOptions = {
url: req.session.selectedNode.boltz_server_url,
url: req.session.selectedNode.settings.boltzServerUrl,
rejectUnauthorized: false,
json: true,
headers: { 'Grpc-Metadata-macaroon': '' }
};
if (req.session.selectedNode.boltz_macaroon_path) {
if (req.session.selectedNode.authentication.boltzMacaroonPath) {
try {
boltzOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.boltz_macaroon_path, 'admin.macaroon')).toString('hex') };
boltzOptions.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.authentication.boltzMacaroonPath, 'admin.macaroon')).toString('hex') };
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Boltz macaroon Error', error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Boltz macaroon Error', error: err });
}
}
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Boltz Options', data: boltzOptions });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Boltz Options', data: boltzOptions });
return boltzOptions;
};
this.getOptions = (req) => {
if (req.session.selectedNode && req.session.selectedNode.options) {
req.session.selectedNode.options.method = (req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() !== 'ECL') ? 'GET' : 'POST';
delete req.session.selectedNode.options.form;
req.session.selectedNode.options.qs = {};
return req.session.selectedNode.options;
if (req.session.selectedNode && req.session.selectedNode.authentication.options) {
req.session.selectedNode.authentication.options.method = (req.session.selectedNode.lnImplementation && req.session.selectedNode.lnImplementation.toUpperCase() === 'LND') ? 'GET' : 'POST';
delete req.session.selectedNode.authentication.options.form;
delete req.session.selectedNode.authentication.options.body;
req.session.selectedNode.authentication.options.qs = {};
return req.session.selectedNode.authentication.options;
}
return this.handleError({ statusCode: 401, message: 'Session expired after a day\'s inactivity' }, 'Session Expired', 'Session Expiry Error', this.initSelectedNode);
return this.handleError({ statusCode: 401, message: 'Session expired after a day\'s inactivity' }, 'Session Expired', 'Session Expiry Error', this.selectedNode);
};
this.updateSelectedNodeOptions = (req) => {
if (!req.session.selectedNode) {
req.session.selectedNode = {};
}
req.session.selectedNode.options = {
req.session.selectedNode.authentication.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: null
};
try {
if (req.session.selectedNode && req.session.selectedNode.ln_implementation) {
switch (req.session.selectedNode.ln_implementation.toUpperCase()) {
if (req.session.selectedNode && req.session.selectedNode.lnImplementation) {
switch (req.session.selectedNode.lnImplementation.toUpperCase()) {
case 'CLN':
req.session.selectedNode.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'access.macaroon'))).toString('base64') };
try {
if (!req.session.selectedNode.authentication.runeValue) {
req.session.selectedNode.authentication.runeValue = this.getRuneValue(req.session.selectedNode.authentication.runePath);
}
req.session.selectedNode.authentication.options.headers = { rune: req.session.selectedNode.authentication.runeValue };
}
catch (err) {
throw new Error(err);
}
break;
case 'ECL':
req.session.selectedNode.options.headers = { authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') };
req.session.selectedNode.authentication.options.headers = { authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.authentication.lnApiPassword).toString('base64') };
break;
default:
req.session.selectedNode.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.macaroon_path, 'admin.macaroon')).toString('hex') };
req.session.selectedNode.authentication.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(req.session.selectedNode.authentication.macaroonPath, 'admin.macaroon')).toString('hex') };
break;
}
}
if (req.session.selectedNode) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Updated Node Options for ' + req.session.selectedNode.ln_node, data: req.session.selectedNode.options });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Updated Node Options for ' + req.session.selectedNode.lnNode, data: req.session.selectedNode.authentication.options });
}
return { status: 200, message: 'Updated Successfully' };
}
catch (err) {
req.session.selectedNode.options = {
req.session.selectedNode.authentication.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: null
};
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Update Selected Node Options Error', error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Update Selected Node Options Error', error: err });
return { status: 502, message: err };
}
};
this.getRuneValue = (rune_path) => {
const data = fs.readFileSync(rune_path, 'utf8');
const pattern = /LIGHTNING_RUNE="(?<runeValue>[^"]+)"/;
const match = data.match(pattern);
if (match.groups.runeValue) {
return match.groups.runeValue;
}
else {
throw new Error('Rune not found in the file.');
}
};
this.setOptions = (req) => {
if (this.nodes[0].options && this.nodes[0].options.headers) {
if (this.nodes[0].authentication.options && this.nodes[0].authentication.options.headers) {
return;
}
if (this.nodes && this.nodes.length > 0) {
this.nodes.forEach((node) => {
node.options = {
node.authentication.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: null
};
try {
if (node.ln_implementation) {
switch (node.ln_implementation.toUpperCase()) {
if (node.lnImplementation) {
switch (node.lnImplementation.toUpperCase()) {
case 'CLN':
node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') };
try {
if (!node.authentication.runeValue) {
node.authentication.runeValue = this.getRuneValue(node.authentication.runePath);
}
node.authentication.options.headers = { rune: node.authentication.runeValue };
}
catch (err) {
throw new Error(err);
}
break;
case 'ECL':
node.options.headers = { authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') };
node.authentication.options.headers = { authorization: 'Basic ' + Buffer.from(':' + node.authentication.lnApiPassword).toString('base64') };
break;
default:
node.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(node.macaroon_path, 'admin.macaroon')).toString('hex') };
node.authentication.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(join(node.authentication.macaroonPath, 'admin.macaroon')).toString('hex') };
break;
}
}
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Common Set Options Error', error: err });
node.options = {
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Common Set Options Error', error: err });
node.authentication.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
}
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Set Node Options for ' + node.ln_node, data: node.options });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Set Node Options for ' + node.lnNode, data: node.authentication.options });
});
this.updateSelectedNodeOptions(req);
}
@ -221,9 +305,9 @@ export class CommonService {
this.handleError = (errRes, fileName, errMsg, selectedNode) => {
const err = JSON.parse(JSON.stringify(errRes));
if (!selectedNode) {
selectedNode = this.initSelectedNode;
selectedNode = this.selectedNode;
}
switch (selectedNode.ln_implementation) {
switch (selectedNode.lnImplementation) {
case 'LND':
if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
delete err.options.headers['Grpc-Metadata-macaroon'];
@ -233,11 +317,11 @@ export class CommonService {
}
break;
case 'CLN':
if (err.options && err.options.headers && err.options.headers.macaroon) {
delete err.options.headers.macaroon;
if (err.options && err.options.headers && err.options.headers.rune) {
delete err.options.headers.rune;
}
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers.macaroon) {
delete err.response.request.headers.macaroon;
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers.rune) {
delete err.response.request.headers.rune;
}
break;
case 'ECL':
@ -254,7 +338,7 @@ export class CommonService {
}
break;
}
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') });
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : err) });
let newErrorObj = { statusCode: 500, message: '', error: '' };
if (err.code && err.code === 'ENOENT') {
newErrorObj = {
@ -275,7 +359,7 @@ export class CommonService {
(err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error')
};
}
if (selectedNode.ln_implementation === 'ECL' && err.message && err.message.indexOf('Authentication Error') < 0 && err.name && err.name === 'StatusCodeError') {
if (selectedNode.lnImplementation === 'ECL' && err.message && err.message.indexOf('Authentication Error') < 0 && err.name && err.name === 'StatusCodeError') {
newErrorObj.statusCode = 500;
}
return newErrorObj;
@ -286,16 +370,16 @@ export class CommonService {
req.socket.remoteAddress ||
(req.connection.socket ? req.connection.socket.remoteAddress : null));
this.getDummyData = (dataKey, lnImplementation) => {
const dummyDataFile = this.rtl_conf_file_path + sep + 'ECLDummyData.log';
const dummyDataFile = this.appConfig.rtlConfFilePath + sep + 'ECLDummyData.log';
return new Promise((resolve, reject) => {
if (this.dummy_data_array_from_file.length === 0) {
fs.readFile(dummyDataFile, 'utf8', (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Dummy data file does not exist' });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Dummy data file does not exist' });
}
else {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Getting dummy data failed' });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Getting dummy data failed' });
}
}
else {
@ -310,36 +394,36 @@ export class CommonService {
});
};
this.readCookie = () => {
const exists = fs.existsSync(this.rtl_cookie_path);
const exists = fs.existsSync(this.appConfig.SSO.rtlCookiePath);
if (exists) {
try {
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
this.appConfig.SSO.cookieValue = fs.readFileSync(this.appConfig.SSO.rtlCookiePath, 'utf-8');
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading cookie: \n' + err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading cookie: \n' + err });
throw new Error(err);
}
}
else {
try {
const directoryName = dirname(this.rtl_cookie_path);
const directoryName = dirname(this.appConfig.SSO.rtlCookiePath);
this.createDirectory(directoryName);
fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex'));
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
fs.writeFileSync(this.appConfig.SSO.rtlCookiePath, crypto.randomBytes(64).toString('hex'));
this.appConfig.SSO.cookieValue = fs.readFileSync(this.appConfig.SSO.rtlCookiePath, 'utf-8');
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading the cookie: \n' + err });
throw new Error(err);
}
}
};
this.refreshCookie = () => {
try {
fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex'));
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
fs.writeFileSync(this.appConfig.SSO.rtlCookiePath, crypto.randomBytes(64).toString('hex'));
this.appConfig.SSO.cookieValue = fs.readFileSync(this.appConfig.SSO.rtlCookiePath, 'utf-8');
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while refreshing cookie', error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while refreshing cookie', error: err });
throw new Error(err);
}
};
@ -366,60 +450,73 @@ export class CommonService {
}, initDir);
};
this.replacePasswordWithHash = (multiPassHashed) => {
this.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(dirname(fileURLToPath(import.meta.url)), '../..');
this.appConfig.rtlConfFilePath = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(dirname(fileURLToPath(import.meta.url)), '../..');
try {
const RTLConfFile = this.rtl_conf_file_path + sep + 'RTL-Config.json';
const RTLConfFile = this.appConfig.rtlConfFilePath + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.multiPassHashed = multiPassHashed;
delete config.multiPass;
fs.writeFileSync(RTLConfFile, JSON.stringify(config, null, 2), 'utf-8');
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Please note that, RTL has encrypted the plaintext password into its corresponding hash' });
return config.multiPassHashed;
}
catch (err) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Password hashing failed', error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Password hashing failed', error: err });
}
};
this.getAllNodeAllChannelBackup = (node) => {
const channel_backup_file = node.channel_backup_path + sep + 'channel-all.bak';
const channel_backup_file = node.settings.channelBackupPath + sep + 'channel-all.bak';
const options = {
url: node.ln_server_url + '/v1/channels/backup',
url: node.settings.lnServerUrl + '/v1/channels/backup',
rejectUnauthorized: false,
json: true,
headers: { 'Grpc-Metadata-macaroon': fs.readFileSync(node.macaroon_path + '/admin.macaroon').toString('hex') }
headers: { 'Grpc-Metadata-macaroon': fs.readFileSync(node.authentication.macaroonPath + '/admin.macaroon').toString('hex') }
};
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Getting Channel Backup for Node ' + node.ln_node + '..' });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Getting Channel Backup for Node ' + node.lnNode + '..' });
request(options).then((body) => {
fs.writeFile(channel_backup_file, JSON.stringify(body), (err) => {
if (err) {
if (node.ln_node) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.ln_node, error: err });
if (node.lnNode) {
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.lnNode, error: err });
}
else {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for File ' + channel_backup_file, error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for File ' + channel_backup_file, error: err });
}
}
else {
if (node.ln_node) {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for Node ' + node.ln_node, data: body });
if (node.lnNode) {
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for Node ' + node.lnNode, data: body });
}
else {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for File ' + channel_backup_file, data: body });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Successful in Channel Backup for File ' + channel_backup_file, data: body });
}
}
});
}, (err) => {
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.ln_node, error: err });
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Error in Channel Backup for Node ' + node.lnNode, error: err });
fs.writeFile(channel_backup_file, '', () => { });
});
};
this.isVersionCompatible = (currentVersion, checkVersion) => {
if (currentVersion) {
const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || [];
const checkVersionsArr = checkVersion.split('.');
return (+versionsArr[0] > +checkVersionsArr[0]) ||
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) ||
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]);
if (currentVersion && currentVersion !== '') {
// eslint-disable-next-line prefer-named-capture-group
const pattern = /v?(\d+(\.\d+)*)/;
const match = currentVersion.match(pattern);
if (match && match.length && match.length > 1) {
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Global Version ' + match[1] });
this.logger.log({ selectedNode: this.selectedNode, level: 'INFO', fileName: 'Common', msg: 'Checking Compatiblility with Version ' + checkVersion });
const currentVersionArr = match[1].split('.') || [];
currentVersionArr[1] = currentVersionArr[1].substring(0, 2);
const checkVersionsArr = checkVersion.split('.');
checkVersionsArr[1] = checkVersionsArr[1].substring(0, 2);
return (+currentVersionArr[0] > +checkVersionsArr[0]) ||
(+currentVersionArr[0] === +checkVersionsArr[0] && +currentVersionArr[1] > +checkVersionsArr[1]) ||
(+currentVersionArr[0] === +checkVersionsArr[0] && +currentVersionArr[1] === +checkVersionsArr[1] && +currentVersionArr[2] >= +checkVersionsArr[2]);
}
else {
this.logger.log({ selectedNode: this.selectedNode, level: 'ERROR', fileName: 'Common', msg: 'Invalid Version String ' + currentVersion });
return false;
}
}
return false;
};
@ -427,17 +524,8 @@ export class CommonService {
this.logEnvVariables = (req) => {
const selNode = req.session.selectedNode;
if (selNode && selNode.index) {
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'PORT: ' + this.port });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'HOST: ' + this.host });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.rtl_sso });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + selNode.index });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'INDEX: ' + selNode.index });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN NODE: ' + selNode.ln_node });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN IMPLEMENTATION: ' + selNode.ln_implementation });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'FIAT CONVERSION: ' + selNode.fiat_conversion });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'CURRENCY UNIT: ' + selNode.currency_unit });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LN SERVER URL: ' + selNode.ln_server_url });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'LOGOUT REDIRECT LINK: ' + this.logout_redirect_link + '\r\n' });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup:', msg: JSON.stringify(this.removeSecureData(JSON.parse(JSON.stringify(this.appConfig)))) });
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.appConfig.SSO.rtlSso });
}
};
this.filterData = (dataKey, lnImplementation) => {

@ -20,31 +20,37 @@ export class ConfigService {
let macaroonPath = '';
let configPath = '';
let channelBackupPath = '';
let dbPath = '';
switch (this.platform) {
case 'win32':
macaroonPath = homeDir + '\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet';
configPath = homeDir + '\\AppData\\Local\\Lnd\\lnd.conf';
channelBackupPath = homeDir + '\\backup\\node-1';
dbPath = homeDir + '\\database\\node-1';
break;
case 'darwin':
macaroonPath = homeDir + '/Library/Application Support/Lnd/data/chain/bitcoin/mainnet';
configPath = homeDir + '/Library/Application Support/Lnd/lnd.conf';
channelBackupPath = homeDir + '/backup/node-1';
dbPath = homeDir + '/database/node-1';
break;
case 'linux':
macaroonPath = homeDir + '/.lnd/data/chain/bitcoin/mainnet';
configPath = homeDir + '/.lnd/lnd.conf';
channelBackupPath = homeDir + '/backup/node-1';
dbPath = homeDir + '/database/node-1';
break;
default:
macaroonPath = '';
configPath = '';
channelBackupPath = '';
dbPath = '';
break;
}
const configData = {
port: '3000',
defaultNodeIndex: 1,
dbDirectoryPath: dbPath,
SSO: {
rtlSSO: 0,
rtlCookiePath: '',
@ -55,11 +61,11 @@ export class ConfigService {
index: 1,
lnNode: 'Node 1',
lnImplementation: 'LND',
Authentication: {
authentication: {
macaroonPath: macaroonPath,
configPath: configPath
},
Settings: {
settings: {
userPersona: 'MERCHANT',
themeMode: 'DAY',
themeColor: 'PURPLE',
@ -67,7 +73,8 @@ export class ConfigService {
logLevel: 'ERROR',
lnServerUrl: 'https://127.0.0.1:8080',
fiatConversion: false,
unannouncedChannels: false
unannouncedChannels: false,
blockExplorerUrl: 'https://mempool.space'
}
}
]
@ -89,15 +96,15 @@ export class ConfigService {
};
this.updateLogByLevel = () => {
let updateLogFlag = false;
this.common.rtl_conf_file_path = process?.env?.RTL_CONFIG_PATH ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
this.common.appConfig.rtlConfFilePath = process?.env?.RTL_CONFIG_PATH ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
try {
const RTLConfFile = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
const RTLConfFile = this.common.appConfig.rtlConfFilePath + sep + 'RTL-Config.json';
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
config.nodes.forEach((node) => {
if (node.Settings.hasOwnProperty('enableLogging')) {
if (node.settings.hasOwnProperty('enableLogging')) {
updateLogFlag = true;
node.Settings.logLevel = node.Settings.enableLogging ? 'INFO' : 'ERROR';
delete node.Settings.enableLogging;
node.settings.logLevel = node.settings.enableLogging ? 'INFO' : 'ERROR';
delete node.settings.enableLogging;
}
});
if (updateLogFlag) {
@ -109,21 +116,22 @@ export class ConfigService {
}
};
this.validateNodeConfig = (config) => {
config.allowPasswordUpdate = true;
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || (typeof process?.env?.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
this.common.rtl_pass = this.hash.update(process?.env?.APP_PASSWORD).digest('hex');
this.common.flg_allow_password_update = false;
config.rtlPass = this.hash.update(process?.env?.APP_PASSWORD).digest('hex');
config.allowPasswordUpdate = false;
}
else if (config.multiPassHashed && config.multiPassHashed !== '') {
this.common.rtl_pass = config.multiPassHashed;
config.rtlPass = config.multiPassHashed;
}
else if (config.multiPass && config.multiPass !== '') {
this.common.rtl_pass = this.common.replacePasswordWithHash(this.hash.update(config.multiPass).digest('hex'));
config.rtlPass = this.common.replacePasswordWithHash(this.hash.update(config.multiPass).digest('hex'));
}
else {
this.errMsg = this.errMsg + '\nNode Authentication can be set with multiPass only. Please set multiPass in RTL-Config.json';
}
this.common.rtl_secret2fa = config.secret2fa;
config.enable2FA = !!config.secret2FA;
}
else {
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
@ -132,144 +140,161 @@ export class ConfigService {
}
this.common.port = (process?.env?.PORT) ? this.normalizePort(process?.env?.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
this.common.host = (process?.env?.HOST) ? process?.env?.HOST : (config.host) ? config.host : null;
config.dbDirectoryPath = (process?.env?.DB_DIRECTORY_PATH) ? process?.env?.DB_DIRECTORY_PATH : (config.dbDirectoryPath) ? config.dbDirectoryPath : join(dirname(fileURLToPath(import.meta.url)), '..', '..');
if (config.nodes && config.nodes.length > 0) {
config.nodes.forEach((node, idx) => {
this.common.nodes[idx] = {};
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
this.common.nodes[idx].index = node.index;
this.common.nodes[idx].ln_node = node.lnNode;
this.common.nodes[idx].ln_implementation = (process?.env?.LN_IMPLEMENTATION) ? process?.env?.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
if (this.common.nodes[idx].ln_implementation === 'CLT') {
this.common.nodes[idx].ln_implementation = 'CLN';
this.common.nodes[idx].lnNode = node.lnNode;
this.common.nodes[idx].lnImplementation = (process?.env?.lnImplementation) ? process?.env?.lnImplementation : node.lnImplementation ? node.lnImplementation : 'LND';
if (this.common.nodes[idx].lnImplementation === 'CLT') {
this.common.nodes[idx].lnImplementation = 'CLN';
}
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH;
}
else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath;
}
else if (this.common.nodes[idx].ln_implementation !== 'ECL') {
this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!';
}
if (this.common.nodes[idx].ln_implementation === 'ECL') {
if (process?.env?.LN_API_PASSWORD) {
this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD;
}
else if (node.Authentication && node.Authentication.lnApiPassword) {
this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword;
}
else {
this.common.nodes[idx].ln_api_password = '';
}
switch (this.common.nodes[idx].lnImplementation) {
case 'CLN':
if (process?.env?.RUNE_PATH && process?.env?.RUNE_PATH.trim() !== '') {
this.common.nodes[idx].authentication.runePath = process?.env?.RUNE_PATH;
}
else if (node.authentication && node.authentication.runePath && node.authentication.runePath.trim() !== '') {
this.common.nodes[idx].authentication.runePath = node.authentication.runePath;
}
else {
this.errMsg = 'Please set rune path for node index ' + node.index + ' in RTL-Config.json!';
}
break;
case 'ECL':
if (process?.env?.LN_API_PASSWORD) {
this.common.nodes[idx].authentication.lnApiPassword = process?.env?.LN_API_PASSWORD;
}
else if (node.authentication && node.authentication.lnApiPassword) {
this.common.nodes[idx].authentication.lnApiPassword = node.authentication.lnApiPassword;
}
else {
this.common.nodes[idx].authentication.lnApiPassword = '';
}
break;
default:
if (process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
this.common.nodes[idx].authentication.macaroonPath = process?.env?.MACAROON_PATH;
}
else if (node.authentication && node.authentication.macaroonPath && node.authentication.macaroonPath.trim() !== '') {
this.common.nodes[idx].authentication.macaroonPath = node.authentication.macaroonPath;
}
else {
this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!';
}
break;
}
if (process?.env?.CONFIG_PATH) {
this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH;
this.common.nodes[idx].authentication.configPath = process?.env?.CONFIG_PATH;
}
else if (node.Authentication && node.Authentication.configPath) {
this.common.nodes[idx].config_path = node.Authentication.configPath;
else if (node.authentication && node.authentication.configPath) {
this.common.nodes[idx].authentication.configPath = node.authentication.configPath;
}
else {
this.common.nodes[idx].config_path = '';
this.common.nodes[idx].authentication.configPath = '';
}
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '' && this.common.nodes[idx].config_path !== '') {
if (this.common.nodes[idx].lnImplementation === 'ECL' && this.common.nodes[idx].authentication.lnApiPassword === '' && this.common.nodes[idx].authentication.configPath !== '') {
try {
const exists = fs.existsSync(this.common.nodes[idx].config_path || '');
const exists = fs.existsSync(this.common.nodes[idx].authentication.configPath || '');
if (exists) {
try {
const configFile = fs.readFileSync((this.common.nodes[idx].config_path || ''), 'utf-8');
const configFile = fs.readFileSync((this.common.nodes[idx].authentication.configPath || ''), 'utf-8');
const iniParsed = ini.parse(configFile);
this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password;
this.common.nodes[idx].authentication.lnApiPassword = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password;
}
catch (err) {
this.errMsg = this.errMsg + '\nSomething went wrong while reading config file: \n' + err;
}
}
else {
this.errMsg = this.errMsg + '\nInvalid config path: ' + this.common.nodes[idx].config_path;
this.errMsg = this.errMsg + '\nInvalid config path: ' + this.common.nodes[idx].authentication.configPath;
}
}
catch (err) {
this.errMsg = this.errMsg + '\nUnable to read config file: \n' + err;
}
}
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '') {
if (this.common.nodes[idx].lnImplementation === 'ECL' && this.common.nodes[idx].authentication.lnApiPassword === '') {
this.errMsg = this.errMsg + '\nPlease set config path Or api password for node index ' + node.index + ' in RTL-Config.json! It is mandatory for Eclair authentication!';
}
if (process?.env?.LN_SERVER_URL && process?.env?.LN_SERVER_URL.trim() !== '') {
this.common.nodes[idx].ln_server_url = process?.env?.LN_SERVER_URL.endsWith('/v1') ? process?.env?.LN_SERVER_URL.slice(0, -3) : process?.env?.LN_SERVER_URL;
this.common.nodes[idx].settings.lnServerUrl = process?.env?.LN_SERVER_URL.endsWith('/v1') ? process?.env?.LN_SERVER_URL.slice(0, -3) : process?.env?.LN_SERVER_URL;
}
else if (process?.env?.LND_SERVER_URL && process?.env?.LND_SERVER_URL.trim() !== '') {
this.common.nodes[idx].ln_server_url = process?.env?.LND_SERVER_URL.endsWith('/v1') ? process?.env?.LND_SERVER_URL.slice(0, -3) : process?.env?.LND_SERVER_URL;
this.common.nodes[idx].settings.lnServerUrl = process?.env?.LND_SERVER_URL.endsWith('/v1') ? process?.env?.LND_SERVER_URL.slice(0, -3) : process?.env?.LND_SERVER_URL;
}
else if (node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') {
this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') ? node.Settings.lnServerUrl.slice(0, -3) : node.Settings.lnServerUrl;
else if (node.settings.lnServerUrl && node.settings.lnServerUrl.trim() !== '') {
this.common.nodes[idx].settings.lnServerUrl = node.settings.lnServerUrl.endsWith('/v1') ? node.settings.lnServerUrl.slice(0, -3) : node.settings.lnServerUrl;
}
else if (node.Settings.lndServerUrl && node.Settings.lndServerUrl.trim() !== '') {
this.common.nodes[idx].ln_server_url = node.Settings.lndServerUrl.endsWith('/v1') ? node.Settings.lndServerUrl.slice(0, -3) : node.Settings.lndServerUrl;
else if (node.settings.lndServerUrl && node.settings.lndServerUrl.trim() !== '') {
this.common.nodes[idx].settings.lnServerUrl = node.settings.lndServerUrl.endsWith('/v1') ? node.settings.lndServerUrl.slice(0, -3) : node.settings.lndServerUrl;
}
else {
this.errMsg = this.errMsg + '\nPlease set LN Server URL for node index ' + node.index + ' in RTL-Config.json!';
}
this.common.nodes[idx].user_persona = node.Settings.userPersona ? node.Settings.userPersona : 'MERCHANT';
this.common.nodes[idx].theme_mode = node.Settings.themeMode ? node.Settings.themeMode : 'DAY';
this.common.nodes[idx].theme_color = node.Settings.themeColor ? node.Settings.themeColor : 'PURPLE';
this.common.nodes[idx].unannounced_channels = node.Settings.unannouncedChannels ? !!node.Settings.unannouncedChannels : false;
this.common.nodes[idx].log_level = node.Settings.logLevel ? node.Settings.logLevel : 'ERROR';
this.common.nodes[idx].fiat_conversion = node.Settings.fiatConversion ? !!node.Settings.fiatConversion : false;
if (this.common.nodes[idx].fiat_conversion) {
this.common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD';
this.common.nodes[idx].settings.userPersona = node.settings.userPersona ? node.settings.userPersona : 'MERCHANT';
this.common.nodes[idx].settings.themeMode = node.settings.themeMode ? node.settings.themeMode : 'DAY';
this.common.nodes[idx].settings.themeColor = node.settings.themeColor ? node.settings.themeColor : 'PURPLE';
this.common.nodes[idx].settings.unannouncedChannels = node.settings.unannouncedChannels ? !!node.settings.unannouncedChannels : false;
this.common.nodes[idx].settings.logLevel = node.settings.logLevel ? node.settings.logLevel : 'ERROR';
this.common.nodes[idx].settings.fiatConversion = node.settings.fiatConversion ? !!node.settings.fiatConversion : false;
if (this.common.nodes[idx].settings.fiatConversion) {
this.common.nodes[idx].settings.currencyUnit = node.settings.currencyUnit ? node.settings.currencyUnit : 'USD';
}
if (process?.env?.SWAP_SERVER_URL && process?.env?.SWAP_SERVER_URL.trim() !== '') {
this.common.nodes[idx].swap_server_url = process?.env?.SWAP_SERVER_URL.endsWith('/v1') ? process?.env?.SWAP_SERVER_URL.slice(0, -3) : process?.env?.SWAP_SERVER_URL;
this.common.nodes[idx].swap_macaroon_path = process?.env?.SWAP_MACAROON_PATH;
this.common.nodes[idx].settings.swapServerUrl = process?.env?.SWAP_SERVER_URL.endsWith('/v1') ? process?.env?.SWAP_SERVER_URL.slice(0, -3) : process?.env?.SWAP_SERVER_URL;
this.common.nodes[idx].authentication.swapMacaroonPath = process?.env?.SWAP_MACAROON_PATH;
}
else if (node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') {
this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl;
this.common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath ? node.Authentication.swapMacaroonPath : '';
else if (node.settings.swapServerUrl && node.settings.swapServerUrl.trim() !== '') {
this.common.nodes[idx].settings.swapServerUrl = node.settings.swapServerUrl.endsWith('/v1') ? node.settings.swapServerUrl.slice(0, -3) : node.settings.swapServerUrl;
this.common.nodes[idx].authentication.swapMacaroonPath = node.authentication.swapMacaroonPath ? node.authentication.swapMacaroonPath : '';
}
else {
this.common.nodes[idx].swap_server_url = '';
this.common.nodes[idx].swap_macaroon_path = '';
this.common.nodes[idx].settings.swapServerUrl = '';
this.common.nodes[idx].authentication.swapMacaroonPath = '';
}
if (process?.env?.BOLTZ_SERVER_URL && process?.env?.BOLTZ_SERVER_URL.trim() !== '') {
this.common.nodes[idx].boltz_server_url = process?.env?.BOLTZ_SERVER_URL.endsWith('/v1') ? process?.env?.BOLTZ_SERVER_URL.slice(0, -3) : process?.env?.BOLTZ_SERVER_URL;
this.common.nodes[idx].boltz_macaroon_path = process?.env?.BOLTZ_MACAROON_PATH;
this.common.nodes[idx].settings.boltzServerUrl = process?.env?.BOLTZ_SERVER_URL.endsWith('/v1') ? process?.env?.BOLTZ_SERVER_URL.slice(0, -3) : process?.env?.BOLTZ_SERVER_URL;
this.common.nodes[idx].authentication.boltzMacaroonPath = process?.env?.BOLTZ_MACAROON_PATH;
}
else if (node.Settings.boltzServerUrl && node.Settings.boltzServerUrl.trim() !== '') {
this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') ? node.Settings.boltzServerUrl.slice(0, -3) : node.Settings.boltzServerUrl;
this.common.nodes[idx].boltz_macaroon_path = node.Authentication.boltzMacaroonPath ? node.Authentication.boltzMacaroonPath : '';
else if (node.settings.boltzServerUrl && node.settings.boltzServerUrl.trim() !== '') {
this.common.nodes[idx].settings.boltzServerUrl = node.settings.boltzServerUrl.endsWith('/v1') ? node.settings.boltzServerUrl.slice(0, -3) : node.settings.boltzServerUrl;
this.common.nodes[idx].authentication.boltzMacaroonPath = node.authentication.boltzMacaroonPath ? node.authentication.boltzMacaroonPath : '';
}
else {
this.common.nodes[idx].boltz_server_url = '';
this.common.nodes[idx].boltz_macaroon_path = '';
this.common.nodes[idx].settings.boltzServerUrl = '';
this.common.nodes[idx].authentication.boltzMacaroonPath = '';
}
this.common.nodes[idx].enable_offers = process?.env?.ENABLE_OFFERS ? process?.env?.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
this.common.nodes[idx].enable_peerswap = process?.env?.ENABLE_PEERSWAP ? process?.env?.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
this.common.nodes[idx].bitcoind_config_path = process?.env?.BITCOIND_CONFIG_PATH ? process?.env?.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
this.common.nodes[idx].channel_backup_path = process?.env?.CHANNEL_BACKUP_PATH ? process?.env?.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
this.common.nodes[idx].settings.enableOffers = process?.env?.ENABLE_OFFERS ? process?.env?.ENABLE_OFFERS : (node.settings.enableOffers) ? node.settings.enableOffers : false;
this.common.nodes[idx].settings.enablePeerswap = process?.env?.ENABLE_PEERSWAP ? process?.env?.ENABLE_PEERSWAP : (node.settings.enablePeerswap) ? node.settings.enablePeerswap : false;
this.common.nodes[idx].settings.bitcoindConfigPath = process?.env?.BITCOIND_CONFIG_PATH ? process?.env?.BITCOIND_CONFIG_PATH : (node.settings.bitcoindConfigPath) ? node.settings.bitcoindConfigPath : '';
this.common.nodes[idx].settings.channelBackupPath = process?.env?.CHANNEL_BACKUP_PATH ? process?.env?.CHANNEL_BACKUP_PATH : (node.settings.channelBackupPath) ? node.settings.channelBackupPath : this.common.appConfig.rtlConfFilePath + sep + 'channels-backup' + sep + 'node-' + node.index;
this.common.nodes[idx].settings.blockExplorerUrl = process?.env?.BLOCK_EXPLORER_URL ? process.env.BLOCK_EXPLORER_URL : (node.settings.blockExplorerUrl) ? node.settings.blockExplorerUrl : 'https://mempool.space';
try {
this.common.createDirectory(this.common.nodes[idx].channel_backup_path);
const exists = fs.existsSync(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak');
this.common.createDirectory(this.common.nodes[idx].settings.channelBackupPath);
const exists = fs.existsSync(this.common.nodes[idx].settings.channelBackupPath + sep + 'channel-all.bak');
if (!exists) {
try {
if (this.common.nodes[idx].ln_implementation === 'LND') {
if (this.common.nodes[idx].lnImplementation === 'LND') {
this.common.getAllNodeAllChannelBackup(this.common.nodes[idx]);
}
else {
const createStream = fs.createWriteStream(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak');
const createStream = fs.createWriteStream(this.common.nodes[idx].settings.channelBackupPath + sep + 'channel-all.bak');
createStream.end();
}
}
catch (err) {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating backup file: \n' + err });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating backup file: \n' + err });
}
}
}
catch (err) {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating the backup directory: \n' + err });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating the backup directory: \n' + err });
}
this.common.nodes[idx].log_file = this.common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log';
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) });
const log_file = this.common.nodes[idx].log_file;
this.common.nodes[idx].settings.logFile = config.rtlConfFilePath + '/logs/RTL-Node-' + node.index + '.log';
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) });
const log_file = this.common.nodes[idx].settings.logFile;
if (fs.existsSync(log_file || '')) {
fs.writeFile((log_file || ''), '', () => { });
}
@ -281,7 +306,7 @@ export class ConfigService {
createStream.end();
}
catch (err) {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err });
}
}
});
@ -293,28 +318,28 @@ export class ConfigService {
};
this.setSSOParams = (config) => {
if (process?.env?.RTL_SSO) {
this.common.rtl_sso = +process?.env?.RTL_SSO;
config.SSO.rtlSso = +process?.env?.RTL_SSO;
}
else if (config.SSO && config.SSO.rtlSSO) {
this.common.rtl_sso = config.SSO.rtlSSO;
config.SSO.rtlSso = config.SSO.rtlSSO;
}
if (process?.env?.RTL_COOKIE_PATH) {
this.common.rtl_cookie_path = process?.env?.RTL_COOKIE_PATH;
config.SSO.rtlCookiePath = process?.env?.RTL_COOKIE_PATH;
}
else if (config.SSO && config.SSO.rtlCookiePath) {
this.common.rtl_cookie_path = config.SSO.rtlCookiePath;
config.SSO.rtlCookiePath = config.SSO.rtlCookiePath;
}
else {
this.common.rtl_cookie_path = '';
config.SSO.rtlCookiePath = '';
}
if (process?.env?.LOGOUT_REDIRECT_LINK) {
this.common.logout_redirect_link = process?.env?.LOGOUT_REDIRECT_LINK;
config.SSO.logoutRedirectLink = process?.env?.LOGOUT_REDIRECT_LINK;
}
else if (config.SSO && config.SSO.logoutRedirectLink) {
this.common.logout_redirect_link = config.SSO.logoutRedirectLink;
config.SSO.logoutRedirectLink = config.SSO.logoutRedirectLink;
}
if (+this.common.rtl_sso) {
if (!this.common.rtl_cookie_path || this.common.rtl_cookie_path.trim() === '') {
if (+config.SSO.rtlSso) {
if (!config.SSO.rtlCookiePath || config.SSO.rtlCookiePath.trim() === '') {
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
}
else {
@ -324,29 +349,53 @@ export class ConfigService {
};
this.setSelectedNode = (config) => {
if (config.defaultNodeIndex) {
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex) || {};
this.common.selectedNode = this.common.findNode(config.defaultNodeIndex) || {};
}
else {
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index) || {};
this.common.selectedNode = this.common.findNode(this.common.nodes[0].index) || {};
}
};
this.updateConfig = (confFileFullPath, config) => {
// Update Config file to change Settings to settings and Authentication to authentication
// Added in v0.15.1, remove in a year?
if (!config.nodes) {
return;
}
config.nodes.map((node) => {
if (node.Authentication) {
node.authentication = JSON.parse(JSON.stringify(node.Authentication));
delete node.Authentication;
}
if (node.Settings) {
node.settings = JSON.parse(JSON.stringify(node.Settings));
delete node.Settings;
}
return node;
});
fs.writeFileSync(confFileFullPath, JSON.stringify(config, null, 2), 'utf-8');
};
this.setServerConfiguration = () => {
const rtlConfFilePath = (process?.env?.RTL_CONFIG_PATH) ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
const confFileFullPath = rtlConfFilePath + sep + 'RTL-Config.json';
try {
this.common.rtl_conf_file_path = (process?.env?.RTL_CONFIG_PATH) ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
const confFileFullPath = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
if (!fs.existsSync(confFileFullPath)) {
fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig()));
fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig(), null, 2), 'utf-8');
}
const config = JSON.parse(fs.readFileSync(confFileFullPath, 'utf-8'));
this.updateConfig(confFileFullPath, config);
config.rtlConfFilePath = rtlConfFilePath;
this.updateLogByLevel();
this.validateNodeConfig(config);
this.setSelectedNode(config);
this.common.appConfig = config;
}
catch (err) {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while configuring the node server: \n' + err });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'Config', msg: 'Config file path: ' + confFileFullPath });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while configuring the node server: \n' + err });
throw new Error(err);
}
};
this.setServerConfiguration();
}
}
export const Config = new ConfigService();

@ -6,7 +6,7 @@ class CORS {
this.common = Common;
}
mount(app) {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CORS', msg: 'Setting up CORS..' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CORS', msg: 'Setting up CORS..' });
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization, filePath');
@ -17,7 +17,7 @@ class CORS {
}
next();
});
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CORS', msg: 'CORS Set' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CORS', msg: 'CORS Set' });
return app;
}
;

@ -8,11 +8,11 @@ class CSRF {
this.common = Common;
}
mount(app) {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CSRF', msg: 'Setting up CSRF..' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CSRF', msg: 'Setting up CSRF..' });
if (process.env.NODE_ENV !== 'development') {
app.use((req, res, next) => this.csrfProtection(req, res, next));
}
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'CSRF', msg: 'CSRF Set' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'CSRF', msg: 'CSRF Set' });
return app;
}
;

@ -1,16 +1,67 @@
import * as fs from 'fs';
import { join, dirname, sep } from 'path';
import { fileURLToPath } from 'url';
import { join, sep } from 'path';
import { Common } from '../utils/common.js';
import { Logger } from '../utils/logger.js';
import { validateDocument, LNDCollection, ECLCollection, CLNCollection } from '../models/database.model.js';
import { CollectionsEnum, validateDocument, LNDCollection, ECLCollection, CLNCollection, ECL_UPDATED_DB } from '../models/database.model.js';
export class DatabaseService {
constructor() {
this.common = Common;
this.logger = Logger;
this.dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
this.dbDirectory = join(this.common.appConfig.dbDirectoryPath, 'database');
this.nodeDatabase = {};
}
migrateDatabase() {
this.common.nodes?.map((node) => {
if (node.lnImplementation === 'ECL') {
this.nodeDatabase[node.index] = { adapter: null, data: {} };
this.nodeDatabase[node.index].adapter = new DatabaseAdapter(this.dbDirectory, node);
this.fetchNodeData(node);
if (this.nodeDatabase[node.index].data.PageSettings) {
try {
const currPageSettings = JSON.parse(JSON.stringify(this.nodeDatabase[node.index].data.PageSettings));
ECL_UPDATED_DB.forEach((updatePage) => {
const foundPageDB = this.nodeDatabase[node.index].data.PageSettings.find((currPage) => currPage.pageId === updatePage.pageId);
if (foundPageDB) {
updatePage.tables.forEach((updateTable) => {
const foundTableDB = foundPageDB.tables.find((currTable) => currTable.tableId === updateTable.tableId);
if (foundTableDB) {
updateTable.removed.forEach((colToBeRemoved) => {
const foundIndex = foundTableDB.columnSelection.findIndex((col) => col === colToBeRemoved);
const foundIndexSM = foundTableDB.columnSelectionSM.findIndex((col) => col === colToBeRemoved);
if (foundIndex >= 0) {
foundTableDB.columnSelection?.splice(foundIndex, 1);
}
if (foundIndexSM >= 0) {
foundTableDB.columnSelectionSM?.splice(foundIndexSM, 1);
}
});
updateTable.renamed.forEach((colToBeRenamed) => {
const [oldName, newName] = colToBeRenamed.split(':');
const foundIndex = foundTableDB.columnSelection.findIndex((col) => col === oldName);
const foundIndexSM = foundTableDB.columnSelectionSM.findIndex((col) => col === oldName);
if (foundIndex >= 0) {
foundTableDB.columnSelection?.splice(foundIndex, 1, newName);
}
if (foundIndexSM >= 0) {
foundTableDB.columnSelectionSM?.splice(foundIndexSM, 1, newName);
}
});
}
});
}
});
if (currPageSettings !== this.nodeDatabase[node.index].data.PageSettings) {
this.saveDatabase(node, CollectionsEnum.PAGE_SETTINGS);
}
}
catch (err) {
this.logger.log({ selectedNode: node, level: 'ERROR', fileName: 'Database', msg: 'Database Migration Error', error: err });
}
}
}
return true;
});
}
loadDatabase(session) {
const { id, selectedNode } = session;
try {
@ -29,7 +80,7 @@ export class DatabaseService {
}
}
fetchNodeData(selectedNode) {
switch (selectedNode.ln_implementation) {
switch (selectedNode.lnImplementation) {
case 'CLN':
for (const collectionName in CLNCollection) {
if (CLNCollection.hasOwnProperty(collectionName)) {
@ -199,14 +250,14 @@ export class DatabaseAdapter {
this.dbFilePath = dbDirectoryPath + sep + 'node-' + selNode.index;
// For backward compatibility Start
const oldFilePath = dbDirectoryPath + sep + 'rtldb-node-' + selNode.index + '.json';
if (selNode.ln_implementation === 'CLN' && fs.existsSync(oldFilePath)) {
if (selNode.lnImplementation === 'CLN' && fs.existsSync(oldFilePath)) {
this.renameOldDB(oldFilePath, selNode);
}
// For backward compatibility End
this.insertSession(id);
}
renameOldDB(oldFilePath, selNode = null) {
const newFilePath = this.dbFilePath + sep + 'rtldb-' + selNode.ln_implementation + '-Offers.json';
const newFilePath = this.dbFilePath + sep + 'rtldb-' + selNode.lnImplementation + '-Offers.json';
try {
this.common.createDirectory(this.dbFilePath);
const oldOffers = JSON.parse(fs.readFileSync(oldFilePath, 'utf-8'));
@ -226,7 +277,7 @@ export class DatabaseAdapter {
catch (err) {
throw new Error(JSON.stringify(err));
}
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.ln_implementation + '-' + collectionName + '.json';
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.lnImplementation + '-' + collectionName + '.json';
try {
if (!fs.existsSync(collectionFilePath)) {
fs.writeFileSync(collectionFilePath, '[]');
@ -239,15 +290,15 @@ export class DatabaseAdapter {
const otherFiles = fs.readdirSync(this.dbFilePath);
otherFiles.forEach((oFileName) => {
let collectionValid = false;
switch (this.selNode.ln_implementation) {
switch (this.selNode.lnImplementation) {
case 'CLN':
collectionValid = CLNCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
collectionValid = CLNCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.lnImplementation + '-' + collection + '.json'), false);
break;
case 'ECL':
collectionValid = ECLCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
collectionValid = ECLCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.lnImplementation + '-' + collection + '.json'), false);
break;
default:
collectionValid = LNDCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.ln_implementation + '-' + collection + '.json'), false);
collectionValid = LNDCollection.reduce((acc, collection) => acc || oFileName === ('rtldb-' + this.selNode.lnImplementation + '-' + collection + '.json'), false);
break;
}
if (oFileName.endsWith('.json') && !collectionValid) {
@ -273,7 +324,7 @@ export class DatabaseAdapter {
saveData(collectionName, collectionData) {
try {
if (collectionData) {
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.ln_implementation + '-' + collectionName + '.json';
const collectionFilePath = this.dbFilePath + sep + 'rtldb-' + this.selNode.lnImplementation + '-' + collectionName + '.json';
const tempFile = collectionFilePath + '.tmp';
fs.writeFileSync(tempFile, JSON.stringify(collectionData, null, 2));
fs.renameSync(tempFile, collectionFilePath);

@ -16,15 +16,15 @@ export class LoggerService {
msgStr = msgStr + '.\r\n';
}
console.error(msgStr);
if (msgJSON.selectedNode && msgJSON.selectedNode.log_file) {
fs.appendFile(msgJSON.selectedNode.log_file, msgStr, () => { });
if (msgJSON.selectedNode && msgJSON.selectedNode.settings.logFile) {
fs.appendFile(msgJSON.selectedNode.settings.logFile, msgStr, () => { });
}
break;
case 'WARN':
msgStr = prepMsgData(msgJSON, msgStr);
if (!msgJSON.selectedNode || msgJSON.selectedNode.log_level === 'WARN' || msgJSON.selectedNode.log_level === 'INFO' || msgJSON.selectedNode.log_level === 'DEBUG') {
if (msgJSON.selectedNode && msgJSON.selectedNode.log_file) {
fs.appendFile(msgJSON.selectedNode.log_file, msgStr, () => { });
if (!msgJSON.selectedNode || msgJSON.selectedNode.settings.logLevel === 'WARN' || msgJSON.selectedNode.settings.logLevel === 'INFO' || msgJSON.selectedNode.settings.logLevel === 'DEBUG') {
if (msgJSON.selectedNode && msgJSON.selectedNode.settings.logFile) {
fs.appendFile(msgJSON.selectedNode.settings.logFile, msgStr, () => { });
}
}
break;
@ -32,18 +32,18 @@ export class LoggerService {
if (!msgJSON.selectedNode && msgJSON.fileName === 'RTL') {
console.log(msgStr + '.\r\n');
}
else if (msgJSON.selectedNode && msgJSON.selectedNode.log_level === 'INFO') {
else if (msgJSON.selectedNode && msgJSON.selectedNode.settings.logLevel === 'INFO') {
msgStr = msgStr + '.\r\n';
console.log(msgStr);
if (msgJSON.selectedNode.log_file) {
fs.appendFile(msgJSON.selectedNode.log_file, msgStr, () => { });
if (msgJSON.selectedNode.settings.logFile) {
fs.appendFile(msgJSON.selectedNode.settings.logFile, msgStr, () => { });
}
}
else if (msgJSON.selectedNode && msgJSON.selectedNode.log_level === 'DEBUG') {
else if (msgJSON.selectedNode && msgJSON.selectedNode.settings.logLevel === 'DEBUG') {
msgStr = prepMsgData(msgJSON, msgStr);
console.log(msgStr);
if (msgJSON.selectedNode.log_file) {
fs.appendFile(msgJSON.selectedNode.log_file, msgStr, () => { });
if (msgJSON.selectedNode.settings.logFile) {
fs.appendFile(msgJSON.selectedNode.settings.logFile, msgStr, () => { });
}
}
break;
@ -51,11 +51,11 @@ export class LoggerService {
if (!msgJSON.selectedNode) {
console.log(msgStr + '.\r\n');
}
else if (msgJSON.selectedNode && msgJSON.selectedNode.log_level === 'DEBUG') {
else if (msgJSON.selectedNode && msgJSON.selectedNode.settings.logLevel === 'DEBUG') {
msgStr = prepMsgData(msgJSON, msgStr);
console.log(msgStr);
if (msgJSON.selectedNode.log_file) {
fs.appendFile(msgJSON.selectedNode.log_file, msgStr, () => { });
if (msgJSON.selectedNode.settings.logFile) {
fs.appendFile(msgJSON.selectedNode.settings.logFile, msgStr, () => { });
}
}
break;

@ -28,7 +28,7 @@ export class RTLWebSocketServer {
}
}, 1000 * 60 * 60); // Terminate broken connections every hour
this.mount = (httpServer) => {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connecting Websocket Server..' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connecting Websocket Server..' });
this.webSocketServer = new WebSocketServer({ noServer: true, path: this.common.baseHref + '/api/ws', verifyClient: (process.env.NODE_ENV === 'development') ? null : verifyWSUser });
httpServer.on('upgrade', (request, socket, head) => {
if (request.headers['upgrade'] !== 'websocket') {
@ -46,7 +46,7 @@ export class RTLWebSocketServer {
});
this.webSocketServer.on('connection', this.mountEventsOnConnection);
this.webSocketServer.on('close', () => clearInterval(this.pingInterval));
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Websocket Server Connected' });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Websocket Server Connected' });
};
this.upgradeCallback = (websocket, request) => {
this.webSocketServer.emit('connection', websocket, request);
@ -58,13 +58,13 @@ export class RTLWebSocketServer {
websocket.isAlive = true;
websocket.sessionId = cookies && cookies['connect.sid'] ? cookieParser.signedCookie(cookies['connect.sid'], this.common.secret_key) : null;
websocket.clientNodeIndex = +protocols[1];
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connected: ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
websocket.on('error', this.sendErrorToAllLNClients);
websocket.on('message', this.sendEventsToAllLNClients);
websocket.on('pong', () => { websocket.isAlive = true; });
websocket.on('close', (code, reason) => {
this.updateLNWSClientDetails(websocket.sessionId, -1, websocket.clientNodeIndex);
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Disconnected due to ' + code + ' : ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
this.logger.log({ selectedNode: this.common.selectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Disconnected due to ' + code + ' : ' + websocket.clientId + ', Total WS clients: ' + this.webSocketServer.clients.size });
});
};
this.updateLNWSClientDetails = (sessionId, currNodeIndex, prevNodeIndex) => {
@ -85,7 +85,7 @@ export class RTLWebSocketServer {
}
else {
const selectedNode = this.common.findNode(currNodeIndex);
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Invalid Node Selection. Previous and current node indices can not be less than zero.' });
this.logger.log({ selectedNode: !selectedNode ? this.common.selectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Invalid Node Selection. Previous and current node indices can not be less than zero.' });
}
};
this.disconnectFromNodeClient = (sessionId, prevNodeIndex) => {
@ -99,8 +99,8 @@ export class RTLWebSocketServer {
const foundClientIdx = this.clientDetails.findIndex((clientDetail) => clientDetail.index === +prevNodeIndex);
this.clientDetails.splice(foundClientIdx, 1);
const prevSelectedNode = this.common.findNode(prevNodeIndex);
if (prevSelectedNode && prevSelectedNode.ln_implementation) {
switch (prevSelectedNode.ln_implementation) {
if (prevSelectedNode && prevSelectedNode.lnImplementation) {
switch (prevSelectedNode.lnImplementation) {
case 'LND':
this.eventEmitterLND.emit('DISCONNECT', prevNodeIndex);
break;
@ -129,8 +129,8 @@ export class RTLWebSocketServer {
const currSelectedNode = this.common.findNode(currNodeIndex);
foundClient = { index: currNodeIndex, sessionIds: [sessionId] };
this.clientDetails.push(foundClient);
if (currSelectedNode && currSelectedNode.ln_implementation) {
switch (currSelectedNode.ln_implementation) {
if (currSelectedNode && currSelectedNode.lnImplementation) {
switch (currSelectedNode.lnImplementation) {
case 'LND':
this.eventEmitterLND.emit('CONNECT', currNodeIndex);
break;
@ -149,27 +149,27 @@ export class RTLWebSocketServer {
this.sendErrorToAllLNClients = (serverError, selectedNode) => {
try {
this.webSocketServer.clients.forEach((client) => {
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Broadcasting error to clients...: ' + serverError });
this.logger.log({ selectedNode: !selectedNode ? this.common.selectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Broadcasting error to clients...: ' + serverError });
if (+client.clientNodeIndex === +selectedNode.index) {
client.send(serverError);
}
});
}
catch (err) {
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
this.logger.log({ selectedNode: !selectedNode ? this.common.selectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
}
};
this.sendEventsToAllLNClients = (newMessage, selectedNode) => {
try {
this.webSocketServer.clients.forEach((client) => {
if (+client.clientNodeIndex === +selectedNode.index) {
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId });
this.logger.log({ selectedNode: !selectedNode ? this.common.selectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId + ', Message: ' + newMessage });
client.send(newMessage);
}
});
}
catch (err) {
this.logger.log({ selectedNode: !selectedNode ? this.common.initSelectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
this.logger.log({ selectedNode: !selectedNode ? this.common.selectedNode : selectedNode, level: 'ERROR', fileName: 'WebSocketServer', msg: 'Error while broadcasting message: ' + JSON.stringify(err) });
}
};
this.generateAcceptValue = (acceptKey) => crypto.createHash('sha1').update(acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary').digest('base64');

@ -1,7 +1,7 @@
# ---------------
# Install Dependencies
# ---------------
FROM node:16-alpine as builder
FROM node:18-alpine as builder
WORKDIR /RTL
@ -22,12 +22,12 @@ RUN npm run buildfrontend
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --production --legacy-peer-deps
RUN npm prune --omit=dev --legacy-peer-deps
# ---------------
# Release App
# ---------------
FROM node:16-alpine as runner
FROM node:18-alpine as runner
WORKDIR /RTL

@ -1,7 +1,7 @@
# ---------------
# Install Dependencies
# ---------------
FROM node:16-stretch-slim as builder
FROM node:18-alpine as builder
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-armel /tini
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-armel.asc /tini.asc
@ -26,12 +26,12 @@ RUN npm run buildfrontend
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --production --legacy-peer-deps
RUN npm prune --omit=dev --legacy-peer-deps
# ---------------
# Release App
# ---------------
FROM arm32v7/node:16-stretch-slim as runner
FROM arm32v7/node:18-alpine as runner
WORKDIR /RTL

@ -1,7 +1,7 @@
# ---------------
# Install Dependencies
# ---------------
FROM node:16-stretch-slim as builder
FROM node:18-alpine as builder
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini-static-arm64 /tini
RUN chmod +x /tini
@ -25,12 +25,12 @@ RUN npm run buildfrontend
RUN npm run buildbackend
# Remove non production necessary modules
RUN npm prune --production --legacy-peer-deps
RUN npm prune --omit=dev --legacy-peer-deps
# ---------------
# Release App
# ---------------
FROM arm64v8/node:16-stretch-slim as runner
FROM arm64v8/node:18-alpine as runner
WORKDIR /RTL

@ -0,0 +1,11 @@
import { FlatCompat } from '@eslint/eslintrc';
import { dirname } from 'path';
import { fileURLToPath } from 'url';
const compat = new FlatCompat({
baseDirectory: dirname(fileURLToPath(import.meta.url))
});
export default [
...compat.extends('./.eslintrc.json')
];

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

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 it is too large Load Diff

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

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

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

Loading…
Cancel
Save