Release 0.15.0 (#1334)

c-lightning-REST to clnrest migration.
master
ShahanaFarooqui 5 months ago committed by GitHub
parent d083be1196
commit 475b47b7ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

5
.github/README.md vendored

@ -96,11 +96,12 @@ Example RTL-Config.json:
"lnNode": "LND Testnet", "lnNode": "LND Testnet",
"lnImplementation": "LND", "lnImplementation": "LND",
"Authentication": { "Authentication": {
"macaroonPath": "<Complete path of the folder containing LND's admin.macaroon for the node # 1>", "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>", "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>", "boltzMacaroonPath": "<Complete path of the folder containing Boltz admin.macaroon for the node>",
"configPath": "<Optional:Path of the .conf if present locally or empty>", "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", "userPersona": "OPERATOR",

@ -21,11 +21,12 @@ parameters have `default` values for initial setup and can be updated after RTL
"lnNode": "<Node name to uniquely identify the node in the UI, Required>", "lnNode": "<Node name to uniquely identify the node in the UI, Required>",
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL, Required>", "lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL, Required>",
"Authentication": { "Authentication": {
"macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN>", "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>", "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>", "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>", "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": { "Settings": {
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT/OPERATOR. Default MERCHANT, Optional>", "userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT/OPERATOR. Default MERCHANT, Optional>",
@ -59,7 +60,8 @@ LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080)
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://127.0.0.1:8081) (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 /> 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 /> 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 /> 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 /> BOLTZ_MACAROON_PATH (Path for the folder containing Boltz's 'admin.macaroon', optional)<br />
RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication, Required)<br /> RTL_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

@ -17,8 +17,9 @@ Follow the below steps to install and setup RTL to run on Core Lightning.
### <a name="prereq"></a>Pre-requisites: ### <a name="prereq"></a>Pre-requisites:
1. Functioning Core Lightning node. Follow install instructions on their [github](https://github.com/ElementsProject/lightning) 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) 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) 3. clnrest - Ensure that core lightning's `clnrest` API server is configured. Configuration instructions [here](https://docs.corelightning.org/docs/rest#configuration)
4. Copy the `access.macaroon` file from `cl-rest` to the device, on which RTL will be installed 4. Create/reuse a rune created by core-lightning. Check [`createrune`](https://docs.corelightning.org/reference/lightning-createrune) and [`showrunes`](https://docs.corelightning.org/reference/lightning-showrunes) commands documentation for more details.
4. Copy the `rune` and save it in a file named `.commando`, on which RTL will be installed.
### <a name="arch"></a>Architecture ### <a name="arch"></a>Architecture
![](../screenshots/RTL-CLN-Arch-2.png) ![](../screenshots/RTL-CLN-Arch-2.png)
@ -50,12 +51,12 @@ If there is an error with `upstream dependency conflict` message then replace `n
### <a name="prep"></a>Prep for Execution ### <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. 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`.. * 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. * Locate the complete path of the readable `.commando` file on your node.
* Modify the RTL conf file per the example file below * Modify the RTL conf file per the example file below
Ensure that the follow values are correct per your config: 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. * `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. * `runePath` - Path of the folder including filename which contains `rune` for the node. This rune in the file should be saved in `LIGHTNING_RUNE="your-rune"` format.
* `lnServerUrl` - complete url with ip address and port of the 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. * `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. * `configPath` (optional) - File path of the core lightning config file, if RTL server is local to the core lightning server.
@ -77,7 +78,7 @@ Ensure that the follow values are correct per your config:
"lnNode": "Core Lightning Testnet # 1", "lnNode": "Core Lightning Testnet # 1",
"lnImplementation": "CLN", "lnImplementation": "CLN",
"Authentication": { "Authentication": {
"macaroonPath": "<Modify to include the path of the folder with access.macaroon>", "runePath": "<Modify to include the path of the folder including filename which contains `rune`>",
"configPath": "<Optional - Config file path for core lightning>" "configPath": "<Optional - Config file path for core lightning>"
}, },
"Settings": { "Settings": {

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

@ -30,7 +30,8 @@ This step is only required to configure the nodes, which will be remotely connec
12. `swapServerUrl` must be set to the swap service url. e.g. https://127.0.0.1:8081. 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. 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. 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 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. 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 #### 3. Restart RTL

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

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

@ -77,10 +77,10 @@
"builder": "@angular-devkit/build-angular:dev-server", "builder": "@angular-devkit/build-angular:dev-server",
"configurations": { "configurations": {
"production": { "production": {
"browserTarget": "RTLApp:build:production" "buildTarget": "RTLApp:build:production"
}, },
"development": { "development": {
"browserTarget": "RTLApp:build:development" "buildTarget": "RTLApp:build:development"
} }
}, },
"defaultConfiguration": "development" "defaultConfiguration": "development"
@ -88,7 +88,7 @@
"extract-i18n": { "extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n", "builder": "@angular-devkit/build-angular:extract-i18n",
"options": { "options": {
"browserTarget": "RTLApp:build" "buildTarget": "RTLApp:build"
} }
}, },
"test": { "test": {

@ -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,6 +1,7 @@
import request from 'request-promise'; import request from 'request-promise';
import { Logger } from '../../utils/logger.js'; import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js'; import { Common } from '../../utils/common.js';
import { getAlias } from './network.js';
let options = null; let options = null;
const logger = Logger; const logger = Logger;
const common = Common; const common = Common;
@ -10,59 +11,29 @@ export const listPeerChannels = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error }); return res.status(options.statusCode).json({ message: options.message, error: options.error });
} }
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels'; options.url = req.session.selectedNode.ln_server_url + '/v1/listpeerchannels';
request(options).then((body) => { request.post(options).then((body) => {
body?.map((channel) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels });
if (!channel.alias || channel.alias === '') { return Promise.all(body.channels?.map((channel) => {
channel.alias = channel.peer_id.substring(0, 20); 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);
const local = channel.to_us_msat || 0; return getAlias(req.session.selectedNode, channel, 'peer_id');
const remote = (channel.total_msat - local) || 0; })).then((values) => {
const total = channel.total_msat || 0; logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels });
channel.to_them_msat = remote; return res.status(200).json(body.channels || []);
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
}); });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'List Peer 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 }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const listChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
request(options).then((body) => {
body?.map((channel) => {
if (!channel.alias || channel.alias === '') {
channel.alias = channel.channel_id.substring(0, 20);
}
const local = channel.to_us_msat || 0;
const remote = (channel.total_msat - local) || 0;
const total = channel.total_msat || 0;
channel.to_them_msat = remote;
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
});
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);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const openChannel = (req, res, next) => { export const openChannel = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.ln_server_url + '/v1/fundchannel';
options.body = req.body; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
@ -79,7 +50,7 @@ export const setChannelFee = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.ln_server_url + '/v1/setchannel';
options.body = req.body; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
@ -97,10 +68,10 @@ export const closeChannel = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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/close';
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/closeChannel/' + req.params.channelId + unilateralTimeoutQuery; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel', data: options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body });
res.status(204).json(body); res.status(204).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -108,37 +79,18 @@ export const closeChannel = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error }); 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) => { export const listForwards = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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'); options.url = req.session.selectedNode.ln_server_url + '/v1/listforwards';
request.get(options).then((body) => { options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.query.status, data: body }); request.post(options).then((body) => {
res.status(200).json(body); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.body.status, data: body });
body.forwards = !body.forwards ? [] : (req.body.status === 'failed' || req.body.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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode); const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -150,38 +102,14 @@ export const funderUpdatePolicy = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/funderupdate';
if (req.body && req.body.policy) { options.body = req.body;
options.body = req.body;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: 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); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode); const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); 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 });
});
};

@ -10,13 +10,12 @@ export const getFees = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error }); return res.status(options.statusCode).json({ message: options.message, error: options.error });
} }
options.url = req.session.selectedNode.ln_server_url + '/v1/getFees'; options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo';
request(options).then((body) => { request.post(options).then((body) => {
if (!body.feeCollected) { const versionCompatible = common.isVersionCompatible(req.session.selectedNode.ln_version, '23.02');
body.feeCollected = 0; const feeData = { feeCollected: ((versionCompatible ? body.fees_collected_msat : body.msatoshi_fees_collected) || 0) };
} logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: feeData });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Fees', msg: 'Fee Received', data: body }); res.status(200).json(feeData);
res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Fees', 'Get Fees Error', req.session.selectedNode); const err = common.handleError(errRes, 'Fees', 'Get Fees Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });

@ -19,52 +19,41 @@ export const getInfo = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo'; 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 }); 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: 'Calling Info from Core Lightning server url ' + options.url }); 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) { if (!options.headers || !options.headers.rune) {
const errMsg = 'Core lightning get info failed due to bad or missing macaroon!'; const errMsg = 'Core lightning get info failed due to missing rune!';
const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode); 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 }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
} }
else { else {
return request(options).then((body) => { return request.post(options).then((body) => {
const body_str = (!body) ? '' : JSON.stringify(body); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'GetInfo', msg: 'Node Information Before Update', data: body });
const search_idx = (!body) ? -1 : body_str.search('Not Found'); body.lnImplementation = 'Core Lightning';
if (!body || search_idx > -1 || body.error) { const chainObj = { chain: '', network: '' };
if (body && !body.error) { if (body.network.includes('litecoin') || body.network.includes('feathercoin')) {
body.error = 'Error From Server!'; chainObj.chain = '';
} chainObj.network = '';
const err = common.handleError(body, 'GetInfo', 'Get Info Error', req.session.selectedNode); }
return res.status(err.statusCode).json({ message: err.message, error: err.error }); else if (body.network.includes('liquid')) {
chainObj.chain = 'Liquid';
chainObj.network = common.titleCase(body.network);
} }
else { else {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'GetInfo', msg: 'Node Information Before Update', data: body }); chainObj.chain = 'Bitcoin';
body.lnImplementation = 'Core Lightning'; chainObj.network = common.titleCase(body.network);
const chainObj = { chain: '', network: '' }; }
if (body.network.includes('litecoin') || body.network.includes('feathercoin')) { body.chains = [chainObj];
chainObj.chain = ''; body.uris = [];
chainObj.network = ''; if (body.address && body.address.length > 0) {
} body.address.forEach((addr) => {
else if (body.network.includes('liquid')) { body.uris.push(body.id + '@' + addr.address + ':' + addr.port);
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);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
} }
req.session.selectedNode.ln_version = 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode); const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); 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) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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/delexpiredinvoice';
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/delExpiredInvoice' + queryStr; options.body = req.body;
request.delete(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body });
res.status(204).json({ status: 'Invoice Deleted Successfully' }); res.status(204).json({ status: 'Invoice Deleted Successfully' });
}).catch((errRes) => { }).catch((errRes) => {
@ -26,10 +26,10 @@ export const listInvoices = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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/listinvoices';
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/listInvoices' + labelQuery; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List URL', data: options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -43,7 +43,7 @@ export const addInvoice = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.ln_server_url + '/v1/invoice';
options.body = req.body; options.body = req.body;
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: 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) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/getroute';
request(options).then((body) => { 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 }); 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Network', 'Query Routes Error', req.session.selectedNode); const err = common.handleError(errRes, 'Network', 'Query Routes Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const listNode = (req, res, next) => { export const listChannels = (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) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/listchannels';
request(options).then((body) => { 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup Finished', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -55,9 +45,10 @@ export const feeRates = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/feerates';
request(options).then((body) => { options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Fee Rates Received for ' + req.params.feeRateStyle, data: body }); request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Fee Rates Received for ' + req.body.style, data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Network', 'Fee Rates Error', req.session.selectedNode); const err = common.handleError(errRes, 'Network', 'Fee Rates Error', req.session.selectedNode);
@ -70,23 +61,38 @@ export const listNodes = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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/listnodes';
options.url = req.session.selectedNode.ln_server_url + '/v1/network/listNodes' + queryStr; const filter_liquidity_ads = !!req.body.liquidity_ads;
delete req.body.liquidity_ads;
options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Network', msg: 'List Nodes URL' + options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
body.forEach((node) => { let response = body.nodes;
if (node.option_will_fund) { if (filter_liquidity_ads) {
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' && response = body.nodes.filter((node) => ((node.option_will_fund) ? node : null));
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' && res.status(200).json(response);
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);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Network', 'Node Lookup Error', req.session.selectedNode); const err = common.handleError(errRes, 'Network', 'Node Lookup Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const getAlias = (selNode, peer, id) => {
options.url = selNode.ln_server_url + '/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;
});
};

@ -19,9 +19,9 @@ export const listOfferBookmarks = (req, res, next) => {
}; };
export const deleteOfferBookmark = (req, res, next) => { export const deleteOfferBookmark = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' }); 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, req.body.offer_str).then((deleteRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: 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(req.body.offer_str);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Offers', 'Offer Bookmark Delete Error', req.session.selectedNode); 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 }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -33,15 +33,9 @@ export const listOffers = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/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;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offers List URL', data: options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offers List Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -55,7 +49,7 @@ export const createOffer = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.ln_server_url + '/v1/offer';
options.body = req.body; options.body = req.body;
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Created', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Created', data: body });
@ -71,7 +65,7 @@ export const fetchOfferInvoice = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.ln_server_url + '/v1/fetchinvoice';
options.body = req.body; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Invoice Body', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Invoice Body', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
@ -88,8 +82,9 @@ export const disableOffer = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/disableOffer';
request.delete(options).then((body) => { options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
res.status(202).json(body); res.status(202).json(body);
}).catch((errRes) => { }).catch((errRes) => {

@ -10,8 +10,9 @@ export const getNewAddress = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/newaddr';
request(options).then((body) => { 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'New Address Generated', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -42,10 +43,47 @@ export const getUTXOs = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error }); return res.status(options.statusCode).json({ message: options.message, error: options.error });
} }
options.url = req.session.selectedNode.ln_server_url + '/v1/listFunds'; options.url = req.session.selectedNode.ln_server_url + '/v1/listfunds';
request(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Funds List Received', data: 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'OnChain', 'List Funds Error', req.session.selectedNode); const err = common.handleError(errRes, 'OnChain', 'List Funds Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });

@ -7,6 +7,18 @@ let options = null;
const logger = Logger; const logger = Logger;
const common = Common; const common = Common;
const databaseService = Database; const databaseService = Database;
export const getMemo = (selNode, payment) => {
options.url = selNode.ln_server_url + '/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) { function paymentReducer(accumulator, currentPayment) {
const currPayHash = currentPayment.payment_hash; const currPayHash = currentPayment.payment_hash;
if (!currentPayment.partid) { if (!currentPayment.partid) {
@ -69,10 +81,14 @@ export const listPayments = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listsendpays';
request(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body.payments }); 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode); const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -83,27 +99,57 @@ export const postPayment = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error }); return res.status(options.statusCode).json({ message: options.message, error: options.error });
} }
const options_body = JSON.parse(JSON.stringify(req.body));
if (req.body.paymentType === 'KEYSEND') { if (req.body.paymentType === 'KEYSEND') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Keysend Payment..' }); 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.url = req.session.selectedNode.ln_server_url + '/v1/keysend';
options.body = req.body; 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 { else {
if (req.body.paymentType === 'OFFER') { if (req.body.paymentType === 'OFFER') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' });
options.body = { invoice: req.body.invoice };
} }
else { else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' });
options.body = req.body;
} }
if (req.body.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.ln_server_url + '/v1/pay'; options.url = req.session.selectedNode.ln_server_url + '/v1/pay';
} }
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body });
if (req.body.paymentType === 'OFFER') { if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB && req.body.bolt12) { 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() }; const offerToUpdate = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount_msat), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.issuer) { if (req.body.issuer) {
offerToUpdate['issuer'] = req.body.issuer; offerToUpdate['issuer'] = req.body.issuer;
} }

@ -1,6 +1,7 @@
import request from 'request-promise'; import request from 'request-promise';
import { Logger } from '../../utils/logger.js'; import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js'; import { Common } from '../../utils/common.js';
import { getAlias } from './network.js';
let options = null; let options = null;
const logger = Logger; const logger = Logger;
const common = Common; const common = Common;
@ -10,15 +11,14 @@ export const getPeers = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listpeers';
request(options).then((body) => { request.post(options).then((body) => {
body.forEach((peer) => { logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
if (!peer.alias || peer.alias === '') { const peers = !body.peers ? [] : body.peers;
peer.alias = peer.id.substring(0, 20); 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode); const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); 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) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.ln_server_url + '/v1/connect';
options.body = req.body; options.body = req.body;
request.post(options).then((connectRes) => { request.post(options).then((connectRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: 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'; const listOptions = common.getOptions(req);
request(options).then((listPeersRes) => { listOptions.url = req.session.selectedNode.ln_server_url + '/v1/listpeers';
const peers = listPeersRes ? common.newestOnTop(listPeersRes, 'id', req.body.id) : []; 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
res.status(201).json(peers); res.status(201).json(peers);
}).catch((errRes) => { }).catch((errRes) => {
@ -54,8 +55,9 @@ export const deletePeer = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/disconnect';
request.delete(options).then((body) => { options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body });
res.status(204).json({}); res.status(204).json({});
}).catch((errRes) => { }).catch((errRes) => {

@ -4,44 +4,15 @@ import { Common } from '../../utils/common.js';
let options = null; let options = null;
const logger = Logger; const logger = Logger;
const common = Common; 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) => { export const decodePayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payment..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payment..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/decode';
request(options).then((body) => { options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Decoded', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Decoded', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -55,8 +26,8 @@ export const signMessage = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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.url = req.session.selectedNode.ln_server_url + '/v1/signmessage';
options.form = { message: req.body.message }; options.body = req.body;
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Signed', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Signed', data: body });
res.status(201).json(body); res.status(201).json(body);
@ -71,8 +42,9 @@ export const verifyMessage = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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; options.url = req.session.selectedNode.ln_server_url + '/v1/checkmessage';
request.get(options, (error, response, body) => { 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Verified', data: body });
res.status(201).json(body); res.status(201).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -86,8 +58,8 @@ export const listConfigs = (req, res, next) => {
if (options.error) { if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listconfigs';
request(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {

@ -1,6 +1,4 @@
import * as fs from 'fs'; import socketIOClient from 'socket.io-client';
import { join } from 'path';
import WebSocket from 'ws';
import { Logger } from '../../utils/logger.js'; import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js'; import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js'; import { WSServer } from '../../utils/webSocketServer.js';
@ -12,7 +10,7 @@ export class CLWebSocketClient {
this.webSocketClients = []; this.webSocketClients = [];
this.reconnectTimeOut = null; this.reconnectTimeOut = null;
this.waitTime = 0.5; this.waitTime = 0.5;
this.reconnet = (clWsClt) => { this.reconnect = (clWsClt) => {
if (this.reconnectTimeOut) { if (this.reconnectTimeOut) {
return; return;
} }
@ -36,7 +34,7 @@ export class CLWebSocketClient {
} }
} }
else { else {
if ((!clientExists.webSocketClient || clientExists.webSocketClient.readyState !== WebSocket.OPEN) && selectedNode.ln_server_url) { if ((!clientExists.webSocketClient || !clientExists.webSocketClient.connected) && selectedNode.ln_server_url) {
clientExists.reConnect = true; clientExists.reConnect = true;
this.connectWithClient(clientExists); this.connectWithClient(clientExists);
} }
@ -48,42 +46,50 @@ export class CLWebSocketClient {
}; };
this.connectWithClient = (clWsClt) => { this.connectWithClient = (clWsClt) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' }); 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'; try {
const mcrnHexEncoded = Buffer.from(fs.readFileSync(join(clWsClt.selectedNode.macaroon_path, 'access.macaroon'))).toString('hex'); if (!clWsClt.selectedNode.rune_value) {
clWsClt.webSocketClient = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false }); clWsClt.selectedNode.rune_value = this.common.getRuneValue(clWsClt.selectedNode.rune_path);
clWsClt.webSocketClient.onopen = () => { }
clWsClt.webSocketClient = socketIOClient(clWsClt.selectedNode.ln_server_url, {
extraHeaders: { rune: clWsClt.selectedNode.rune_value },
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.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' });
this.waitTime = 0.5; this.waitTime = 0.5;
}; });
clWsClt.webSocketClient.onclose = (e) => { clWsClt.webSocketClient.on('disconnect', (reason) => {
if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLN') { 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...' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...', data: reason });
clWsClt.webSocketClient.close(); clWsClt.webSocketClient.close();
if (clWsClt.reConnect) { 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 }); 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; this.wsServer.sendEventsToAllLNClients(JSON.stringify({ source: 'CLN', data: msg }), clWsClt.selectedNode);
msg['source'] = 'CLN'; });
const msgStr = JSON.stringify(msg); clWsClt.webSocketClient.on('error', (err) => {
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
};
clWsClt.webSocketClient.onerror = (err) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', 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 + ' }')); 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); this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
clWsClt.webSocketClient.close(); clWsClt.webSocketClient.close();
if (clWsClt.reConnect) { if (clWsClt.reConnect) {
this.reconnet(clWsClt); this.reconnect(clWsClt);
} }
}; });
}; };
this.disconnect = (selectedNode) => { this.disconnect = (selectedNode) => {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index); 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..' }); this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the Core Lightning\'s Websocket Server..' });
clientExists.reConnect = false; clientExists.reConnect = false;
clientExists.webSocketClient.close(); clientExists.webSocketClient.close();

@ -1,8 +1,11 @@
export class CommonSelectedNode { 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) { constructor(options, ln_server_url, macaroon_path, macaroon_value, rune_path, rune_value, 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.options = options;
this.ln_server_url = ln_server_url; this.ln_server_url = ln_server_url;
this.macaroon_path = macaroon_path; this.macaroon_path = macaroon_path;
this.macaroon_value = macaroon_value;
this.rune_path = rune_path;
this.rune_value = rune_value;
this.ln_api_password = ln_api_password; this.ln_api_password = ln_api_password;
this.swap_server_url = swap_server_url; this.swap_server_url = swap_server_url;
this.boltz_server_url = boltz_server_url; this.boltz_server_url = boltz_server_url;

@ -1,15 +1,12 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, listPeerChannels, 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(); const router = Router();
router.get('/listChannels', isAuthenticated, listChannels);
router.get('/listPeerChannels', isAuthenticated, listPeerChannels); router.get('/listPeerChannels', isAuthenticated, listPeerChannels);
router.post('/', isAuthenticated, openChannel); router.post('/', isAuthenticated, openChannel);
router.post('/setChannelFee', isAuthenticated, setChannelFee); router.post('/setChannelFee', isAuthenticated, setChannelFee);
router.delete('/:channelId', isAuthenticated, closeChannel); router.post('/close/', isAuthenticated, closeChannel);
router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance); router.post('/listForwards', isAuthenticated, listForwards);
router.get('/listForwards', isAuthenticated, listForwards);
router.get('/listForwardsPaginated', isAuthenticated, listForwardsPaginated);
router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy); router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy);
export default router; export default router;

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

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

@ -1,11 +1,10 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; 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(); const router = Router();
router.get('/getRoute/:destPubkey/:amount', isAuthenticated, getRoute); router.post('/listNodes', isAuthenticated, listNodes);
router.get('/listNode/:id', isAuthenticated, listNode); router.post('/getRoute', isAuthenticated, getRoute);
router.get('/listChannel/:channelShortId', isAuthenticated, listChannel); router.post('/feeRates', isAuthenticated, feeRates);
router.get('/feeRates/:feeRateStyle', isAuthenticated, feeRates); router.post('/listChannels', isAuthenticated, listChannels);
router.get('/listNodes', isAuthenticated, listNodes);
export default router; 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'; import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, createOffer, fetchOfferInvoice } from '../../controllers/cln/offers.js';
const router = Router(); const router = Router();
router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks); router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks);
router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark); router.post('/offerbookmark/delete', isAuthenticated, deleteOfferBookmark);
router.get('/', isAuthenticated, listOffers); router.get('/', isAuthenticated, listOffers);
router.post('/', isAuthenticated, createOffer); router.post('/', isAuthenticated, createOffer);
router.post('/fetchOfferInvoice', isAuthenticated, fetchOfferInvoice); router.post('/fetchOfferInvoice', isAuthenticated, fetchOfferInvoice);
router.delete('/:offerID', isAuthenticated, disableOffer); router.post('/disableOffer', isAuthenticated, disableOffer);
export default router; export default router;

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

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

@ -1,10 +1,9 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; 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(); const router = Router();
router.get('/', isAuthenticated, decodePayments); router.post('/decode', isAuthenticated, decodePayment);
router.get('/decode/:payReq', isAuthenticated, decodePayment);
router.post('/sign', isAuthenticated, signMessage); router.post('/sign', isAuthenticated, signMessage);
router.post('/verify', isAuthenticated, verifyMessage); router.post('/verify', isAuthenticated, verifyMessage);
router.get('/listConfigs', isAuthenticated, listConfigs); router.get('/listConfigs', isAuthenticated, listConfigs);

@ -13,7 +13,6 @@ import eclRoutes from '../routes/eclair/index.js';
import { Database } from './database.js'; import { Database } from './database.js';
import { Common } from './common.js'; import { Common } from './common.js';
import { Logger } from './logger.js'; import { Logger } from './logger.js';
import { CLWSClient } from '../controllers/cln/webSocketClient.js';
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js'; import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js'; import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
const ONE_DAY = 1000 * 60 * 60 * 24; const ONE_DAY = 1000 * 60 * 60 * 24;
@ -23,7 +22,7 @@ export class ExpressApplication {
this.logger = Logger; this.logger = Logger;
this.common = Common; this.common = Common;
this.eclWsClient = ECLWSClient; this.eclWsClient = ECLWSClient;
this.clWsClient = CLWSClient; // public clWsClient: CLWebSocketClient = CLWSClient;
this.lndWsClient = LNDWSClient; this.lndWsClient = LNDWSClient;
this.databaseService = Database; this.databaseService = Database;
this.directoryName = dirname(fileURLToPath(import.meta.url)); this.directoryName = dirname(fileURLToPath(import.meta.url));

@ -20,6 +20,7 @@ export class CommonService {
this.rtl_cookie_path = ''; this.rtl_cookie_path = '';
this.logout_redirect_link = ''; this.logout_redirect_link = '';
this.cookie_value = ''; this.cookie_value = '';
this.ln_version = '';
this.api_version = ''; this.api_version = '';
this.secret_key = crypto.randomBytes(64).toString('hex'); this.secret_key = crypto.randomBytes(64).toString('hex');
this.read_dummy_data = false; this.read_dummy_data = false;
@ -68,8 +69,9 @@ export class CommonService {
}; };
this.getOptions = (req) => { this.getOptions = (req) => {
if (req.session.selectedNode && req.session.selectedNode.options) { 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'; req.session.selectedNode.options.method = (req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() === 'LND') ? 'GET' : 'POST';
delete req.session.selectedNode.options.form; delete req.session.selectedNode.options.form;
delete req.session.selectedNode.options.body;
req.session.selectedNode.options.qs = {}; req.session.selectedNode.options.qs = {};
return req.session.selectedNode.options; return req.session.selectedNode.options;
} }
@ -89,7 +91,15 @@ export class CommonService {
if (req.session.selectedNode && req.session.selectedNode.ln_implementation) { if (req.session.selectedNode && req.session.selectedNode.ln_implementation) {
switch (req.session.selectedNode.ln_implementation.toUpperCase()) { switch (req.session.selectedNode.ln_implementation.toUpperCase()) {
case 'CLN': 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.rune_value) {
req.session.selectedNode.rune_value = this.getRuneValue(req.session.selectedNode.rune_path);
}
req.session.selectedNode.options.headers = { rune: req.session.selectedNode.rune_value };
}
catch (err) {
throw new Error(err);
}
break; break;
case 'ECL': case 'ECL':
req.session.selectedNode.options.headers = { authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') }; req.session.selectedNode.options.headers = { authorization: 'Basic ' + Buffer.from(':' + req.session.selectedNode.ln_api_password).toString('base64') };
@ -115,6 +125,17 @@ export class CommonService {
return { status: 502, message: 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) => { this.setOptions = (req) => {
if (this.nodes[0].options && this.nodes[0].options.headers) { if (this.nodes[0].options && this.nodes[0].options.headers) {
return; return;
@ -131,7 +152,15 @@ export class CommonService {
if (node.ln_implementation) { if (node.ln_implementation) {
switch (node.ln_implementation.toUpperCase()) { switch (node.ln_implementation.toUpperCase()) {
case 'CLN': case 'CLN':
node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') }; try {
if (!node.rune_value) {
node.rune_value = this.getRuneValue(node.rune_path);
}
node.options.headers = { rune: node.rune_value };
}
catch (err) {
throw new Error(err);
}
break; break;
case 'ECL': case 'ECL':
node.options.headers = { authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') }; node.options.headers = { authorization: 'Basic ' + Buffer.from(':' + node.ln_api_password).toString('base64') };
@ -424,12 +453,25 @@ export class CommonService {
}); });
}; };
this.isVersionCompatible = (currentVersion, checkVersion) => { this.isVersionCompatible = (currentVersion, checkVersion) => {
if (currentVersion) { if (currentVersion && currentVersion !== '') {
const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || []; // eslint-disable-next-line prefer-named-capture-group
const checkVersionsArr = checkVersion.split('.'); const pattern = /v?(\d+(\.\d+)*)/;
return (+versionsArr[0] > +checkVersionsArr[0]) || const match = currentVersion.match(pattern);
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) || if (match && match.length && match.length > 1) {
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]); this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Global Version ' + match[1] });
this.logger.log({ selectedNode: this.initSelectedNode, 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.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Invalid Version String ' + currentVersion });
return false;
}
} }
return false; return false;
}; };

@ -148,25 +148,40 @@ export class ConfigService {
if (this.common.nodes[idx].ln_implementation === 'CLT') { if (this.common.nodes[idx].ln_implementation === 'CLT') {
this.common.nodes[idx].ln_implementation = 'CLN'; this.common.nodes[idx].ln_implementation = 'CLN';
} }
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') { switch (this.common.nodes[idx].ln_implementation) {
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH; case 'CLN':
} if (process?.env?.RUNE_PATH && process?.env?.RUNE_PATH.trim() !== '') {
else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') { this.common.nodes[idx].rune_path = process?.env?.RUNE_PATH;
this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath; }
} else if (node.Authentication && node.Authentication.runePath && node.Authentication.runePath.trim() !== '') {
else if (this.common.nodes[idx].ln_implementation !== 'ECL') { this.common.nodes[idx].rune_path = node.Authentication.runePath;
this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!'; }
} else {
if (this.common.nodes[idx].ln_implementation === 'ECL') { this.errMsg = 'Please set rune path for node index ' + node.index + ' in RTL-Config.json!';
if (process?.env?.LN_API_PASSWORD) { }
this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD; break;
} case 'ECL':
else if (node.Authentication && node.Authentication.lnApiPassword) { if (process?.env?.LN_API_PASSWORD) {
this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword; this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD;
} }
else { else if (node.Authentication && node.Authentication.lnApiPassword) {
this.common.nodes[idx].ln_api_password = ''; this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword;
} }
else {
this.common.nodes[idx].ln_api_password = '';
}
break;
default:
if (process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH;
}
else if (node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
this.common.nodes[idx].macaroon_path = 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) { if (process?.env?.CONFIG_PATH) {
this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH; this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH;

@ -163,7 +163,7 @@ export class RTLWebSocketServer {
try { try {
this.webSocketServer.clients.forEach((client) => { this.webSocketServer.clients.forEach((client) => {
if (+client.clientNodeIndex === +selectedNode.index) { 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.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId + ', Message: ' + newMessage });
client.send(newMessage); client.send(newMessage);
} }
}); });

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -69,6 +69,32 @@ MIT
@angular/router @angular/router
MIT MIT
@babel/runtime
MIT
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@fortawesome/angular-fontawesome @fortawesome/angular-fontawesome
MIT MIT
MIT License MIT License

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

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

@ -0,0 +1 @@
(()=>{"use strict";var e,v={},m={};function r(e){var o=m[e];if(void 0!==o)return o.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(o,t,i,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,f]=e[n],s=!0,d=0;d<t.length;d++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[d]))?t.splice(d--,1):(s=!1,f<a&&(a=f));if(s){e.splice(n--,1);var u=i();void 0!==u&&(o=u)}}return o}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,i,f]},r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((o,t)=>(r.f[t](e,o),o),[])),r.u=e=>e+"."+{125:"708b87b4f08ae30f",456:"21f2112ce22ca275",570:"1e18345ecab5f7fe",758:"8736aa34f95f1235"}[e]+".js",r.miniCssF=e=>{},r.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={},o="RTLApp:";r.l=(t,i,f,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==f)for(var d=document.getElementsByTagName("script"),u=0;u<d.length;u++){var l=d[u];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==o+f){a=l;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",o+f),a.src=r.tu(t)),e[t]=[i];var c=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:o=>o},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,f)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=i){var a=new Promise((l,c)=>n=e[i]=[l,c]);f.push(n[2]=a);var s=r.p+r.u(i),d=new Error;r.l(s,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;d.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",d.name="ChunkLoadError",d.type=c,d.request=p,n[1](d)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var o=(i,f)=>{var d,u,[n,a,s]=f,l=0;if(n.some(p=>0!==e[p])){for(d in a)r.o(a,d)&&(r.m[d]=a[d]);if(s)var c=s(r)}for(i&&i(f);l<n.length;l++)r.o(e,u=n[l])&&e[u]&&e[u][0](),e[u]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();

@ -1 +0,0 @@
(()=>{"use strict";var e,v={},m={};function r(e){var f=m[e];if(void 0!==f)return f.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(f,t,i,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,o]=e[n],s=!0,d=0;d<t.length;d++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[d]))?t.splice(d--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var u=i();void 0!==u&&(f=u)}}return f}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,i,o]},r.d=(e,f)=>{for(var t in f)r.o(f,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:f[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((f,t)=>(r.f[t](e,f),f),[])),r.u=e=>e+"."+{125:"020bb8ec6698fd7e",456:"a54c45d211d6d10c",570:"28ed94d292ccd982",758:"2801e2da6f8bba94"}[e]+".js",r.miniCssF=e=>{},r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),(()=>{var e={},f="RTLApp:";r.l=(t,i,o,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==o)for(var d=document.getElementsByTagName("script"),u=0;u<d.length;u++){var l=d[u];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==f+o){a=l;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",f+o),a.src=r.tu(t)),e[t]=[i];var c=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:f=>f},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,o)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=i){var a=new Promise((l,c)=>n=e[i]=[l,c]);o.push(n[2]=a);var s=r.p+r.u(i),d=new Error;r.l(s,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;d.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",d.name="ChunkLoadError",d.type=c,d.request=p,n[1](d)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var f=(i,o)=>{var d,u,[n,a,s]=o,l=0;if(n.some(p=>0!==e[p])){for(d in a)r.o(a,d)&&(r.m[d]=a[d]);if(s)var c=s(r)}for(i&&i(o);l<n.length;l++)r.o(e,u=n[l])&&e[u]&&e[u][0](),e[u]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(f.bind(null,0)),t.push=f.bind(null,t.push.bind(t))})()})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8183
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{ {
"name": "rtl", "name": "rtl",
"version": "0.14.1-beta", "version": "0.15.0-beta",
"license": "MIT", "license": "MIT",
"type": "module", "type": "module",
"scripts": { "scripts": {
@ -21,9 +21,9 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@ngrx/effects": "^16.3.0", "@ngrx/effects": "^17.0.0",
"@ngrx/store": "^16.3.0", "@ngrx/store": "^17.0.0",
"@swimlane/ngx-charts": "^20.4.1", "@swimlane/ngx-charts": "^20.5.0",
"angular-user-idle": "^4.0.0", "angular-user-idle": "^4.0.0",
"atob": "^2.1.2", "atob": "^2.1.2",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
@ -34,63 +34,64 @@
"hocon-parser": "^1.0.1", "hocon-parser": "^1.0.1",
"ini": "^4.1.1", "ini": "^4.1.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"ng-qrcode": "^16.0.0", "ng-qrcode": "^17.0.0",
"ngx-perfect-scrollbar-next": "^10.1.1", "ngx-perfect-scrollbar-next": "^10.1.1",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"pdfmake": "^0.2.7", "pdfmake": "^0.2.8",
"request": "^2.88.2", "request": "^2.88.2",
"request-promise": "^4.2.6", "request-promise": "^4.2.6",
"rxjs": "~7.8.0", "rxjs": "^7.8.1",
"sha256": "^0.2.0", "sha256": "^0.2.0",
"socket.io-client": "^4.7.2",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"tslib": "^2.3.0", "tslib": "^2.6.2",
"ws": "^8.14.2", "ws": "^8.14.2",
"zone.js": "~0.13.0" "zone.js": "^0.14.2"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "^16.2.5", "@angular-devkit/build-angular": "^17.0.3",
"@angular-eslint/builder": "^16.2.0", "@angular-eslint/builder": "^17.1.0",
"@angular-eslint/eslint-plugin": "^16.2.0", "@angular-eslint/eslint-plugin": "^17.1.0",
"@angular-eslint/eslint-plugin-template": "^16.2.0", "@angular-eslint/eslint-plugin-template": "^17.1.0",
"@angular-eslint/schematics": "^16.2.0", "@angular-eslint/schematics": "^17.1.0",
"@angular-eslint/template-parser": "^16.2.0", "@angular-eslint/template-parser": "^17.1.0",
"@angular/animations": "^16.2.0", "@angular/animations": "^17.0.4",
"@angular/cdk": "^16.2.7", "@angular/cdk": "^17.0.1",
"@angular/cli": "^16.2.5", "@angular/cli": "^17.0.3",
"@angular/compiler-cli": "^16.2.0", "@angular/common": "^17.0.4",
"@angular/common": "^16.2.0", "@angular/compiler": "^17.0.4",
"@angular/compiler": "^16.2.0", "@angular/compiler-cli": "^17.0.4",
"@angular/core": "^16.2.0", "@angular/core": "^17.0.4",
"@angular/flex-layout": "^15.0.0-beta.42", "@angular/flex-layout": "^15.0.0-beta.42",
"@angular/forms": "^16.2.0", "@angular/forms": "^17.0.4",
"@angular/material": "^16.2.7", "@angular/material": "^17.0.1",
"@angular/platform-browser": "^16.2.0", "@angular/platform-browser": "^17.0.4",
"@angular/platform-browser-dynamic": "^16.2.0", "@angular/platform-browser-dynamic": "^17.0.4",
"@angular/router": "^16.2.0", "@angular/router": "^17.0.4",
"@fortawesome/angular-fontawesome": "^0.13.0", "@fortawesome/angular-fontawesome": "^0.14.0",
"@fortawesome/fontawesome-svg-core": "^6.4.2", "@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2", "@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2", "@fortawesome/free-solid-svg-icons": "^6.4.2",
"@ngrx/store-devtools": "^16.3.0", "@ngrx/store-devtools": "^17.0.0",
"@types/jasmine": "~4.3.0", "@types/jasmine": "^5.1.4",
"@types/node": "^20.8.2", "@types/node": "^20.9.4",
"@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.7.4", "@typescript-eslint/parser": "^6.10.0",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.50.0", "eslint": "^8.53.0",
"eslint-plugin-deprecation": "^2.0.0", "eslint-plugin-deprecation": "^2.0.0",
"jasmine-core": "~4.6.0", "jasmine-core": "^5.1.1",
"jasmine-spec-reporter": "^7.0.0", "jasmine-spec-reporter": "^7.0.0",
"karma": "~6.4.0", "karma": "^6.4.2",
"karma-chrome-launcher": "~3.2.0", "karma-chrome-launcher": "^3.2.0",
"karma-coverage": "~2.2.0", "karma-coverage": "^2.2.1",
"karma-jasmine": "~5.1.0", "karma-jasmine": "^5.1.0",
"karma-jasmine-html-reporter": "~2.1.0", "karma-jasmine-html-reporter": "^2.1.0",
"material-icons": "^1.13.12", "material-icons": "^1.13.12",
"nodemon": "^3.0.1", "nodemon": "^3.0.1",
"protractor": "^7.0.0", "protractor": "^7.0.0",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"ts-node": "^10.9.1", "ts-node": "^10.9.1",
"typescript": "~5.1.3" "typescript": "~5.2.2"
} }
} }

@ -1,23 +0,0 @@
import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
let options = null;
const logger: LoggerService = Logger;
const common: CommonService = 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,6 +1,8 @@
import request from 'request-promise'; import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js'; import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { getAlias } from './network.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
@ -9,53 +11,28 @@ export const listPeerChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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/listPeerChannels'; options.url = req.session.selectedNode.ln_server_url + '/v1/listpeerchannels';
request(options).then((body) => { request.post(options).then((body) => {
body?.map((channel) => { logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body.channels });
if (!channel.alias || channel.alias === '') { channel.alias = channel.peer_id.substring(0, 20); } return Promise.all(body.channels?.map((channel) => {
const local = channel.to_us_msat || 0; channel.to_them_msat = channel.total_msat - channel.to_us_msat;
const remote = (channel.total_msat - local) || 0; 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);
const total = channel.total_msat || 0; return getAlias(req.session.selectedNode, channel, 'peer_id');
channel.to_them_msat = remote; })).then((values) => {
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List With Aliases Received', data: body.channels });
return channel; return res.status(200).json(body.channels || []);
}); });
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body });
res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'List Peer 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 }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const listChannels = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
request(options).then((body) => {
body?.map((channel) => {
if (!channel.alias || channel.alias === '') { channel.alias = channel.channel_id.substring(0, 20); }
const local = channel.to_us_msat || 0;
const remote = (channel.total_msat - local) || 0;
const total = channel.total_msat || 0;
channel.to_them_msat = remote;
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
return channel;
});
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);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const openChannel = (req, res, next) => { export const openChannel = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Opening Channel..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.ln_server_url + '/v1/fundchannel';
options.body = req.body; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Open Channel Options', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
@ -71,7 +48,7 @@ export const setChannelFee = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Setting Channel Fee..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Setting Channel Fee..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.ln_server_url + '/v1/setchannel';
options.body = req.body; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Update Channel Policy Options', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
@ -88,10 +65,10 @@ export const closeChannel = (req, res, next) => {
req.setTimeout(60000 * 10); // timeout 10 mins req.setTimeout(60000 * 10); // timeout 10 mins
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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/close';
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/closeChannel/' + req.params.channelId + unilateralTimeoutQuery; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Closing Channel', data: options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Closed', data: body });
res.status(204).json(body); res.status(204).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -100,30 +77,16 @@ export const closeChannel = (req, res, next) => {
}); });
}; };
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) => { export const listForwards = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channel List Forwards..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'); options.url = req.session.selectedNode.ln_server_url + '/v1/listforwards';
request.get(options).then((body) => { options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.query.status, data: body }); request.post(options).then((body) => {
res.status(200).json(body); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Forwarding History Received For Status ' + req.body.status, data: body });
body.forwards = !body.forwards ? [] : (req.body.status === 'failed' || req.body.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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode); const err = common.handleError(errRes, 'Channels', 'Forwarding History Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -134,37 +97,14 @@ export const funderUpdatePolicy = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting or Updating Funder Policy..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting or Updating Funder Policy..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/funderupdate';
if (req.body && req.body.policy) { options.body = req.body;
options.body = req.body;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: 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); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode); const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); 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,21 +0,0 @@
import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
let options = null;
const logger: LoggerService = Logger;
const common: CommonService = 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 });
});
};

@ -19,46 +19,38 @@ export const getInfo = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/getinfo'; 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 }); 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: 'Calling Info from Core Lightning server url ' + options.url }); 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) { if (!options.headers || !options.headers.rune) {
const errMsg = 'Core lightning get info failed due to bad or missing macaroon!'; const errMsg = 'Core lightning get info failed due to missing rune!';
const err = common.handleError({ statusCode: 502, message: 'Bad Macaroon', error: errMsg }, 'GetInfo', errMsg, req.session.selectedNode); 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 }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
} else { } else {
return request(options).then((body) => { return request.post(options).then((body) => {
const body_str = (!body) ? '' : JSON.stringify(body); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'GetInfo', msg: 'Node Information Before Update', data: body });
const search_idx = (!body) ? -1 : body_str.search('Not Found'); body.lnImplementation = 'Core Lightning';
if (!body || search_idx > -1 || body.error) { const chainObj = { chain: '', network: '' };
if (body && !body.error) { body.error = 'Error From Server!'; } if (body.network.includes('litecoin') || body.network.includes('feathercoin')) {
const err = common.handleError(body, 'GetInfo', 'Get Info Error', req.session.selectedNode); chainObj.chain = '';
return res.status(err.statusCode).json({ message: err.message, error: err.error }); chainObj.network = '';
} else if (body.network.includes('liquid')) {
chainObj.chain = 'Liquid';
chainObj.network = common.titleCase(body.network);
} else { } else {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'GetInfo', msg: 'Node Information Before Update', data: body }); chainObj.chain = 'Bitcoin';
body.lnImplementation = 'Core Lightning'; chainObj.network = common.titleCase(body.network);
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);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Node Information Received', data: body });
return res.status(200).json(body);
} }
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.ln_version = 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode); const err = common.handleError(errRes, 'GetInfo', 'Get Info Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });

@ -9,9 +9,9 @@ export const deleteExpiredInvoice = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Deleting Expired Invoices..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Deleting Expired Invoices..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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/delexpiredinvoice';
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/delExpiredInvoice' + queryStr; options.body = req.body;
request.delete(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body });
res.status(204).json({ status: 'Invoice Deleted Successfully' }); res.status(204).json({ status: 'Invoice Deleted Successfully' });
}).catch((errRes) => { }).catch((errRes) => {
@ -24,10 +24,10 @@ export const listInvoices = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting Invoices..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting Invoices..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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/listinvoices';
options.url = req.session.selectedNode.ln_server_url + '/v1/invoice/listInvoices' + labelQuery; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List URL', data: options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Invoice', msg: 'Invoices List Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -40,7 +40,7 @@ export const addInvoice = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.ln_server_url + '/v1/invoice';
options.body = req.body; options.body = req.body;
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body });

@ -1,6 +1,8 @@
import request from 'request-promise'; import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js'; import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { CommonSelectedNode } from '../../models/config.model.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
@ -9,36 +11,27 @@ export const getRoute = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Routes..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/getroute';
request(options).then((body) => { 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 }); 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Network', 'Query Routes Error', req.session.selectedNode); const err = common.handleError(errRes, 'Network', 'Query Routes Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const listNode = (req, res, next) => { export const listChannels = (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) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/listchannels';
request(options).then((body) => { 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Channel Lookup Finished', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -51,9 +44,10 @@ export const feeRates = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Fee Rates..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Getting Network Fee Rates..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/feerates';
request(options).then((body) => { options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Fee Rates Received for ' + req.params.feeRateStyle, data: body }); request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'Network Fee Rates Received for ' + req.body.style, data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Network', 'Fee Rates Error', req.session.selectedNode); const err = common.handleError(errRes, 'Network', 'Fee Rates Error', req.session.selectedNode);
@ -65,23 +59,39 @@ export const listNodes = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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/listnodes';
options.url = req.session.selectedNode.ln_server_url + '/v1/network/listNodes' + queryStr; const filter_liquidity_ads = !!req.body.liquidity_ads;
delete req.body.liquidity_ads;
options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Network', msg: 'List Nodes URL' + options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
body.forEach((node) => { let response = body.nodes;
if (node.option_will_fund) { if (filter_liquidity_ads) {
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' && response = body.nodes.filter((node) => ((node.option_will_fund) ? node : null));
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' && res.status(200).json(response);
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);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Network', 'Node Lookup Error', req.session.selectedNode); const err = common.handleError(errRes, 'Network', 'Node Lookup Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
}); });
}; };
export const getAlias = (selNode: CommonSelectedNode, peer: any, id: string) => {
options.url = selNode.ln_server_url + '/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;
});
};

@ -22,9 +22,9 @@ export const listOfferBookmarks = (req, res, next) => {
export const deleteOfferBookmark = (req, res, next) => { export const deleteOfferBookmark = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Deleting Offer Bookmark..' }); 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, req.body.offer_str).then((deleteRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Bookmark Deleted', data: 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(req.body.offer_str);
}).catch((errRes) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Offers', 'Offer Bookmark Delete Error', req.session.selectedNode); 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 }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -35,15 +35,9 @@ export const listOffers = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offers..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offers..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/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;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offers List URL', data: options.url }); 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offers List Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -56,7 +50,7 @@ export const createOffer = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Creating Offer..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Creating Offer..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.ln_server_url + '/v1/offer';
options.body = req.body; options.body = req.body;
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Created', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Created', data: body });
@ -71,7 +65,7 @@ export const fetchOfferInvoice = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Invoice..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Getting Offer Invoice..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.ln_server_url + '/v1/fetchinvoice';
options.body = req.body; options.body = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Invoice Body', data: options.body }); logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Offers', msg: 'Offer Invoice Body', data: options.body });
request.post(options).then((body) => { request.post(options).then((body) => {
@ -87,8 +81,9 @@ export const disableOffer = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Disabling Offer..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Disabling Offer..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/disableOffer';
request.delete(options).then((body) => { options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
res.status(202).json(body); res.status(202).json(body);
}).catch((errRes) => { }).catch((errRes) => {

@ -9,8 +9,9 @@ export const getNewAddress = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Generating New Address..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Generating New Address..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/newaddr';
request(options).then((body) => { 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'New Address Generated', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -39,10 +40,44 @@ export const getUTXOs = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Listing Funds..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Listing Funds..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listfunds';
request(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'OnChain', msg: 'Funds List Received', data: 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'OnChain', 'List Funds Error', req.session.selectedNode); const err = common.handleError(errRes, 'OnChain', 'List Funds Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });

@ -3,12 +3,26 @@ import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { Database, DatabaseService } from '../../utils/database.js'; import { Database, DatabaseService } from '../../utils/database.js';
import { CollectionFieldsEnum, CollectionsEnum, Offer } from '../../models/database.model.js'; import { CollectionFieldsEnum, CollectionsEnum, Offer } from '../../models/database.model.js';
import { CommonSelectedNode } from '../../models/config.model.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
const databaseService: DatabaseService = Database; const databaseService: DatabaseService = Database;
export const getMemo = (selNode: CommonSelectedNode, payment: any) => {
options.url = selNode.ln_server_url + '/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) { function paymentReducer(accumulator, currentPayment) {
const currPayHash = currentPayment.payment_hash; const currPayHash = currentPayment.payment_hash;
if (!currentPayment.partid) { currentPayment.partid = 0; } if (!currentPayment.partid) { currentPayment.partid = 0; }
@ -60,10 +74,14 @@ export const listPayments = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'List Payments..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'List Payments..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listsendpays';
request(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Payment List Received', data: body.payments }); 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode); const err = common.handleError(errRes, 'Payments', 'List Payments Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -73,25 +91,55 @@ export const listPayments = (req, res, next) => {
export const postPayment = (req, res, next) => { export const postPayment = (req, res, next) => {
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
const options_body = JSON.parse(JSON.stringify(req.body));
if (req.body.paymentType === 'KEYSEND') { if (req.body.paymentType === 'KEYSEND') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Keysend Payment..' }); 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.url = req.session.selectedNode.ln_server_url + '/v1/keysend';
options.body = req.body; 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 { } else {
if (req.body.paymentType === 'OFFER') { if (req.body.paymentType === 'OFFER') {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Offer Payment..' });
options.body = { invoice: req.body.invoice };
} else { } else {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Invoice Payment..' });
options.body = req.body;
} }
if (req.body.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.ln_server_url + '/v1/pay'; options.url = req.session.selectedNode.ln_server_url + '/v1/pay';
} }
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body });
if (req.body.paymentType === 'OFFER') { if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB && req.body.bolt12) { if (req.body.saveToDB && req.body.bolt12) {
const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() }; const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount_msat), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
if (req.body.issuer) { offerToUpdate['issuer'] = req.body.issuer; } if (req.body.issuer) { offerToUpdate['issuer'] = req.body.issuer; }
if (req.body.description) { offerToUpdate['description'] = req.body.description; } if (req.body.description) { offerToUpdate['description'] = req.body.description; }
// eslint-disable-next-line arrow-body-style // eslint-disable-next-line arrow-body-style

@ -1,6 +1,8 @@
import request from 'request-promise'; import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js'; import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { getAlias } from './network.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
@ -9,15 +11,14 @@ export const getPeers = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'List Peers..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'List Peers..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listpeers';
request(options).then((body) => { request.post(options).then((body) => {
body.forEach((peer) => { logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
if (!peer.alias || peer.alias === '') { const peers = !body.peers ? [] : body.peers;
peer.alias = peer.id.substring(0, 20); 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) => { }).catch((errRes) => {
const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode); const err = common.handleError(errRes, 'Peers', 'List Peers Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error }); return res.status(err.statusCode).json({ message: err.message, error: err.error });
@ -28,13 +29,14 @@ export const postPeer = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Connecting Peer..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Connecting Peer..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.ln_server_url + '/v1/connect';
options.body = req.body; options.body = req.body;
request.post(options).then((connectRes) => { request.post(options).then((connectRes) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peer Connected', data: 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'; const listOptions = common.getOptions(req);
request(options).then((listPeersRes) => { listOptions.url = req.session.selectedNode.ln_server_url + '/v1/listpeers';
const peers = listPeersRes ? common.newestOnTop(listPeersRes, 'id', req.body.id) : []; 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: peers });
res.status(201).json(peers); res.status(201).json(peers);
}).catch((errRes) => { }).catch((errRes) => {
@ -51,8 +53,9 @@ export const deletePeer = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Disconnecting Peer..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Disconnecting Peer..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/disconnect';
request.delete(options).then((body) => { options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peer Disconnected', data: body });
res.status(204).json({}); res.status(204).json({});
}).catch((errRes) => { }).catch((errRes) => {

@ -1,47 +1,18 @@
import request from 'request-promise'; import request from 'request-promise';
import { Logger, LoggerService } from '../../utils/logger.js'; import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { CommonSelectedNode } from '../../models/config.model.js';
let options = null; let options = null;
const logger: LoggerService = Logger; const logger: LoggerService = Logger;
const common: CommonService = Common; const common: CommonService = Common;
export const decodePaymentFromPaymentRequest = (selNode: CommonSelectedNode, 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) => { export const decodePayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payment..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Decoding Payment..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/decode';
request(options).then((body) => { options.body = req.body;
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Decoded', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Decoded', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -54,8 +25,8 @@ export const signMessage = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Signing Message..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Signing Message..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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.url = req.session.selectedNode.ln_server_url + '/v1/signmessage';
options.form = { message: req.body.message }; options.body = req.body;
request.post(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Signed', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Signed', data: body });
res.status(201).json(body); res.status(201).json(body);
@ -69,8 +40,9 @@ export const verifyMessage = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Verifying Message..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Verifying Message..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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; options.url = req.session.selectedNode.ln_server_url + '/v1/checkmessage';
request.get(options, (error, response, body) => { 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 }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Message', msg: 'Message Verified', data: body });
res.status(201).json(body); res.status(201).json(body);
}).catch((errRes) => { }).catch((errRes) => {
@ -83,8 +55,8 @@ export const listConfigs = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs..' }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs..' });
options = common.getOptions(req); options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); } 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'; options.url = req.session.selectedNode.ln_server_url + '/v1/listconfigs';
request(options).then((body) => { request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs Received', data: body }); logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Utility', msg: 'List Configs Received', data: body });
res.status(200).json(body); res.status(200).json(body);
}).catch((errRes) => { }).catch((errRes) => {

@ -1,7 +1,4 @@
import * as fs from 'fs'; import socketIOClient from 'socket.io-client';
import { join } from 'path';
import WebSocket from 'ws';
import { Logger, LoggerService } from '../../utils/logger.js'; import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js'; import { Common, CommonService } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js'; import { WSServer } from '../../utils/webSocketServer.js';
@ -25,7 +22,7 @@ export class CLWebSocketClient {
}); });
} }
public reconnet = (clWsClt) => { public reconnect = (clWsClt) => {
if (this.reconnectTimeOut) { return; } if (this.reconnectTimeOut) { return; }
this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2); this.waitTime = (this.waitTime >= 64) ? 64 : (this.waitTime * 2);
this.reconnectTimeOut = setTimeout(() => { this.reconnectTimeOut = setTimeout(() => {
@ -47,7 +44,7 @@ export class CLWebSocketClient {
this.webSocketClients.push(newWebSocketClient); this.webSocketClients.push(newWebSocketClient);
} }
} else { } else {
if ((!clientExists.webSocketClient || clientExists.webSocketClient.readyState !== WebSocket.OPEN) && selectedNode.ln_server_url) { if ((!clientExists.webSocketClient || !clientExists.webSocketClient.connected) && selectedNode.ln_server_url) {
clientExists.reConnect = true; clientExists.reConnect = true;
this.connectWithClient(clientExists); this.connectWithClient(clientExists);
} }
@ -59,43 +56,50 @@ export class CLWebSocketClient {
public connectWithClient = (clWsClt) => { public connectWithClient = (clWsClt) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' }); 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'; try {
const mcrnHexEncoded = Buffer.from(fs.readFileSync(join(clWsClt.selectedNode.macaroon_path, 'access.macaroon'))).toString('hex'); if (!clWsClt.selectedNode.rune_value) {
clWsClt.webSocketClient = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false }); clWsClt.selectedNode.rune_value = this.common.getRuneValue(clWsClt.selectedNode.rune_path);
}
clWsClt.webSocketClient = socketIOClient(clWsClt.selectedNode.ln_server_url, {
extraHeaders: { rune: clWsClt.selectedNode.rune_value },
transports: ['websocket'],
secure: true,
rejectUnauthorized: false
});
} catch (err) {
throw new Error(err);
}
clWsClt.webSocketClient.onopen = () => { clWsClt.webSocketClient.on('connect', () => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connected to the Core Lightning\'s Websocket Server..' });
this.waitTime = 0.5; this.waitTime = 0.5;
}; });
clWsClt.webSocketClient.onclose = (e) => { clWsClt.webSocketClient.on('disconnect', (reason) => {
if (clWsClt && clWsClt.selectedNode && clWsClt.selectedNode.ln_implementation === 'CLN') { 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...' }); this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Web socket disconnected, will reconnect again...', data: reason });
clWsClt.webSocketClient.close(); clWsClt.webSocketClient.close();
if (clWsClt.reConnect) { this.reconnet(clWsClt); } if (clWsClt.reConnect) { 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 }); 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; this.wsServer.sendEventsToAllLNClients(JSON.stringify({ source: 'CLN', data: msg }), clWsClt.selectedNode);
msg['source'] = 'CLN'; });
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
};
clWsClt.webSocketClient.onerror = (err) => { clWsClt.webSocketClient.on('error', (err) => {
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', 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 + ' }')); 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); this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
clWsClt.webSocketClient.close(); clWsClt.webSocketClient.close();
if (clWsClt.reConnect) { this.reconnet(clWsClt); } if (clWsClt.reConnect) { this.reconnect(clWsClt); }
}; });
}; };
public disconnect = (selectedNode: CommonSelectedNode) => { public disconnect = (selectedNode: CommonSelectedNode) => {
const clientExists = this.webSocketClients.find((wsc) => wsc.selectedNode.index === selectedNode.index); 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..' }); this.logger.log({ selectedNode: clientExists.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Disconnecting from the Core Lightning\'s Websocket Server..' });
clientExists.reConnect = false; clientExists.reConnect = false;
clientExists.webSocketClient.close(); clientExists.webSocketClient.close();

@ -4,6 +4,9 @@ export class CommonSelectedNode {
public options?: any, public options?: any,
public ln_server_url?: string, public ln_server_url?: string,
public macaroon_path?: string, public macaroon_path?: string,
public macaroon_value?: string,
public rune_path?: string,
public rune_value?: string,
public ln_api_password?: string, public ln_api_password?: string,
public swap_server_url?: string, public swap_server_url?: string,
public boltz_server_url?: string, public boltz_server_url?: string,

@ -1,10 +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,19 +1,16 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, listPeerChannels, 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(); const router = Router();
router.get('/listChannels', isAuthenticated, listChannels);
router.get('/listPeerChannels', isAuthenticated, listPeerChannels); router.get('/listPeerChannels', isAuthenticated, listPeerChannels);
router.post('/', isAuthenticated, openChannel); router.post('/', isAuthenticated, openChannel);
router.post('/setChannelFee', isAuthenticated, setChannelFee); router.post('/setChannelFee', isAuthenticated, setChannelFee);
router.delete('/:channelId', isAuthenticated, closeChannel); router.post('/close/', isAuthenticated, closeChannel);
router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance); router.post('/listForwards', isAuthenticated, listForwards);
router.get('/listForwards', isAuthenticated, listForwards);
router.get('/listForwardsPaginated', isAuthenticated, listForwardsPaginated);
router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy); router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy);

@ -1,10 +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'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import infoCLRoutes from './getInfo.js'; import infoCLRoutes from './getInfo.js';
import feesCLRoutes from './fees.js';
import balanceCLRoutes from './balance.js';
import channelsCLRoutes from './channels.js'; import channelsCLRoutes from './channels.js';
import invoicesCLRoutes from './invoices.js'; import invoicesCLRoutes from './invoices.js';
import onChainCLRoutes from './onchain.js'; import onChainCLRoutes from './onchain.js';
@ -16,8 +14,6 @@ const router = Router();
const clRoutes = [ const clRoutes = [
{ path: '/getinfo', route: infoCLRoutes }, { path: '/getinfo', route: infoCLRoutes },
{ path: '/fees', route: feesCLRoutes },
{ path: '/balance', route: balanceCLRoutes },
{ path: '/channels', route: channelsCLRoutes }, { path: '/channels', route: channelsCLRoutes },
{ path: '/invoices', route: invoicesCLRoutes }, { path: '/invoices', route: invoicesCLRoutes },
{ path: '/onchain', route: onChainCLRoutes }, { path: '/onchain', route: onChainCLRoutes },

@ -5,8 +5,8 @@ import { listInvoices, addInvoice, deleteExpiredInvoice } from '../../controller
const router = Router(); const router = Router();
router.get('/', isAuthenticated, listInvoices); router.post('/lookup/', isAuthenticated, listInvoices);
router.post('/', isAuthenticated, addInvoice); router.post('/', isAuthenticated, addInvoice);
router.delete('/', isAuthenticated, deleteExpiredInvoice); router.post('/delete/', isAuthenticated, deleteExpiredInvoice);
export default router; export default router;

@ -1,14 +1,13 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; 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(); const router = Router();
router.get('/getRoute/:destPubkey/:amount', isAuthenticated, getRoute); router.post('/listNodes', isAuthenticated, listNodes);
router.get('/listNode/:id', isAuthenticated, listNode); router.post('/getRoute', isAuthenticated, getRoute);
router.get('/listChannel/:channelShortId', isAuthenticated, listChannel); router.post('/feeRates', isAuthenticated, feeRates);
router.get('/feeRates/:feeRateStyle', isAuthenticated, feeRates); router.post('/listChannels', isAuthenticated, listChannels);
router.get('/listNodes', isAuthenticated, listNodes);
export default router; export default router;

@ -6,11 +6,11 @@ import { listOfferBookmarks, deleteOfferBookmark, listOffers, disableOffer, crea
const router = Router(); const router = Router();
router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks); router.get('/offerbookmarks', isAuthenticated, listOfferBookmarks);
router.delete('/offerbookmark/:offerStr', isAuthenticated, deleteOfferBookmark); router.post('/offerbookmark/delete', isAuthenticated, deleteOfferBookmark);
router.get('/', isAuthenticated, listOffers); router.get('/', isAuthenticated, listOffers);
router.post('/', isAuthenticated, createOffer); router.post('/', isAuthenticated, createOffer);
router.post('/fetchOfferInvoice', isAuthenticated, fetchOfferInvoice); router.post('/fetchOfferInvoice', isAuthenticated, fetchOfferInvoice);
router.delete('/:offerID', isAuthenticated, disableOffer); router.post('/disableOffer', isAuthenticated, disableOffer);
export default router; export default router;

@ -5,8 +5,8 @@ import { getNewAddress, onChainWithdraw, getUTXOs } from '../../controllers/cln/
const router = Router(); const router = Router();
router.get('/', isAuthenticated, getNewAddress);
router.post('/', isAuthenticated, onChainWithdraw); router.post('/', isAuthenticated, onChainWithdraw);
router.post('/newaddr', isAuthenticated, getNewAddress);
router.get('/utxos/', isAuthenticated, getUTXOs); router.get('/utxos/', isAuthenticated, getUTXOs);
export default router; export default router;

@ -7,6 +7,6 @@ const router = Router();
router.get('/', isAuthenticated, getPeers); router.get('/', isAuthenticated, getPeers);
router.post('/', isAuthenticated, postPeer); router.post('/', isAuthenticated, postPeer);
router.delete('/:peerId', isAuthenticated, deletePeer); router.post('/disconnect/', isAuthenticated, deletePeer);
export default router; export default router;

@ -1,12 +1,11 @@
import exprs from 'express'; import exprs from 'express';
const { Router } = exprs; const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js'; 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(); const router = Router();
router.get('/', isAuthenticated, decodePayments); router.post('/decode', isAuthenticated, decodePayment);
router.get('/decode/:payReq', isAuthenticated, decodePayment);
router.post('/sign', isAuthenticated, signMessage); router.post('/sign', isAuthenticated, signMessage);
router.post('/verify', isAuthenticated, verifyMessage); router.post('/verify', isAuthenticated, verifyMessage);
router.get('/listConfigs', isAuthenticated, listConfigs); router.get('/listConfigs', isAuthenticated, listConfigs);

@ -26,7 +26,7 @@ export class ExpressApplication {
public logger: LoggerService = Logger; public logger: LoggerService = Logger;
public common: CommonService = Common; public common: CommonService = Common;
public eclWsClient: ECLWebSocketClient = ECLWSClient; public eclWsClient: ECLWebSocketClient = ECLWSClient;
public clWsClient: CLWebSocketClient = CLWSClient; // public clWsClient: CLWebSocketClient = CLWSClient;
public lndWsClient: LNDWebSocketClient = LNDWSClient; public lndWsClient: LNDWebSocketClient = LNDWSClient;
public databaseService: DatabaseService = Database; public databaseService: DatabaseService = Database;
public directoryName = dirname(fileURLToPath(import.meta.url)); public directoryName = dirname(fileURLToPath(import.meta.url));

@ -22,6 +22,7 @@ export class CommonService {
public rtl_cookie_path = ''; public rtl_cookie_path = '';
public logout_redirect_link = ''; public logout_redirect_link = '';
public cookie_value = ''; public cookie_value = '';
public ln_version = '';
public api_version = ''; public api_version = '';
public secret_key = crypto.randomBytes(64).toString('hex'); public secret_key = crypto.randomBytes(64).toString('hex');
public read_dummy_data = false; public read_dummy_data = false;
@ -73,8 +74,9 @@ export class CommonService {
public getOptions = (req) => { public getOptions = (req) => {
if (req.session.selectedNode && req.session.selectedNode.options) { 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'; req.session.selectedNode.options.method = (req.session.selectedNode.ln_implementation && req.session.selectedNode.ln_implementation.toUpperCase() === 'LND') ? 'GET' : 'POST';
delete req.session.selectedNode.options.form; delete req.session.selectedNode.options.form;
delete req.session.selectedNode.options.body;
req.session.selectedNode.options.qs = {}; req.session.selectedNode.options.qs = {};
return req.session.selectedNode.options; return req.session.selectedNode.options;
} }
@ -95,7 +97,14 @@ export class CommonService {
if (req.session.selectedNode && req.session.selectedNode.ln_implementation) { if (req.session.selectedNode && req.session.selectedNode.ln_implementation) {
switch (req.session.selectedNode.ln_implementation.toUpperCase()) { switch (req.session.selectedNode.ln_implementation.toUpperCase()) {
case 'CLN': 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.rune_value) {
req.session.selectedNode.rune_value = this.getRuneValue(req.session.selectedNode.rune_path);
}
req.session.selectedNode.options.headers = { rune: req.session.selectedNode.rune_value };
} catch (err) {
throw new Error(err);
}
break; break;
case 'ECL': case 'ECL':
@ -123,6 +132,17 @@ export class CommonService {
} }
}; };
public 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.');
}
};
public setOptions = (req) => { public setOptions = (req) => {
if (this.nodes[0].options && this.nodes[0].options.headers) { return; } if (this.nodes[0].options && this.nodes[0].options.headers) { return; }
if (this.nodes && this.nodes.length > 0) { if (this.nodes && this.nodes.length > 0) {
@ -137,7 +157,14 @@ export class CommonService {
if (node.ln_implementation) { if (node.ln_implementation) {
switch (node.ln_implementation.toUpperCase()) { switch (node.ln_implementation.toUpperCase()) {
case 'CLN': case 'CLN':
node.options.headers = { macaroon: Buffer.from(fs.readFileSync(join(node.macaroon_path, 'access.macaroon'))).toString('base64') }; try {
if (!node.rune_value) {
node.rune_value = this.getRuneValue(node.rune_path);
}
node.options.headers = { rune: node.rune_value };
} catch (err) {
throw new Error(err);
}
break; break;
case 'ECL': case 'ECL':
@ -433,12 +460,24 @@ export class CommonService {
}; };
public isVersionCompatible = (currentVersion, checkVersion) => { public isVersionCompatible = (currentVersion, checkVersion) => {
if (currentVersion) { if (currentVersion && currentVersion !== '') {
const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || []; // eslint-disable-next-line prefer-named-capture-group
const checkVersionsArr = checkVersion.split('.'); const pattern = /v?(\d+(\.\d+)*)/;
return (+versionsArr[0] > +checkVersionsArr[0]) || const match = currentVersion.match(pattern);
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) || if (match && match.length && match.length > 1) {
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] === +checkVersionsArr[1] && +versionsArr[2] >= +checkVersionsArr[2]); this.logger.log({ selectedNode: this.initSelectedNode, level: 'INFO', fileName: 'Common', msg: 'Global Version ' + match[1] });
this.logger.log({ selectedNode: this.initSelectedNode, 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.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Invalid Version String ' + currentVersion });
return false;
}
} }
return false; return false;
}; };

@ -150,22 +150,36 @@ export class ConfigService {
this.common.nodes[idx].ln_node = node.lnNode; 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'; 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'; } if (this.common.nodes[idx].ln_implementation === 'CLT') { this.common.nodes[idx].ln_implementation = 'CLN'; }
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') { switch (this.common.nodes[idx].ln_implementation) {
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH; case 'CLN':
} else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') { if (process?.env?.RUNE_PATH && process?.env?.RUNE_PATH.trim() !== '') {
this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath; this.common.nodes[idx].rune_path = process?.env?.RUNE_PATH;
} else if (this.common.nodes[idx].ln_implementation !== 'ECL') { } else if (node.Authentication && node.Authentication.runePath && node.Authentication.runePath.trim() !== '') {
this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!'; this.common.nodes[idx].rune_path = node.Authentication.runePath;
} } else {
this.errMsg = 'Please set rune path for node index ' + node.index + ' in RTL-Config.json!';
}
break;
if (this.common.nodes[idx].ln_implementation === 'ECL') { case 'ECL':
if (process?.env?.LN_API_PASSWORD) { if (process?.env?.LN_API_PASSWORD) {
this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD; this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD;
} else if (node.Authentication && node.Authentication.lnApiPassword) { } else if (node.Authentication && node.Authentication.lnApiPassword) {
this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword; this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword;
} else { } else {
this.common.nodes[idx].ln_api_password = ''; this.common.nodes[idx].ln_api_password = '';
} }
break;
default:
if (process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH;
} else if (node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
this.common.nodes[idx].macaroon_path = 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) { if (process?.env?.CONFIG_PATH) {
this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH; this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH;

@ -167,7 +167,7 @@ export class RTLWebSocketServer {
try { try {
this.webSocketServer.clients.forEach((client) => { this.webSocketServer.clients.forEach((client) => {
if (+client.clientNodeIndex === +selectedNode.index) { 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.initSelectedNode : selectedNode, level: 'DEBUG', fileName: 'WebSocketServer', msg: 'Broadcasting message to client...: ' + client.clientId + ', Message: ' + newMessage });
client.send(newMessage); client.send(newMessage);
} }
}); });

@ -50,7 +50,7 @@ if (isDevMode()) { isDevEnvironemt = true; }
} }
}), }),
EffectsModule.forRoot([RTLEffects, LNDEffects, CLNEffects, ECLEffects]), EffectsModule.forRoot([RTLEffects, LNDEffects, CLNEffects, ECLEffects]),
isDevEnvironemt ? StoreDevtoolsModule.instrument() : [] isDevEnvironemt ? StoreDevtoolsModule.instrument({connectInZone: true}) : []
], ],
declarations: [AppComponent], declarations: [AppComponent],
providers: [ providers: [

@ -9,72 +9,72 @@
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Short Channel ID</h4> <h4 class="font-bold-500">Short Channel ID</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.short_channel_id}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.short_channel_id}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Active</h4> <h4 class="font-bold-500">Active</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.active ? 'True' : 'False'}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.active ? 'True' : 'False'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Last Update</h4> <h4 class="font-bold-500">Last Update</h4>
<span class="foreground-secondary-text">{{(lookupResult[0]?.last_update * 1000) | date:'dd/MMM/y HH:mm'}}</span> <span class="foreground-secondary-text">{{(lookupResult.channels[0]?.last_update * 1000) | date:'dd/MMM/y HH:mm'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Amount (Sats)</h4> <h4 class="font-bold-500">Amount (Sats)</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.amount_msat / 1000 | number:'1.0-0'}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.amount_msat / 1000 | number:'1.0-0'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Base Fee (mSats)</h4> <h4 class="font-bold-500">Base Fee (mSats)</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.base_fee_millisatoshi | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.base_fee_millisatoshi | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Fee/Millionth</h4> <h4 class="font-bold-500">Fee/Millionth</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.fee_per_millionth | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.fee_per_millionth | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Channel Flags</h4> <h4 class="font-bold-500">Channel Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.channel_flags | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.channel_flags | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Delay</h4> <h4 class="font-bold-500">Delay</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.delay | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.delay | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Max Htlc (mSat)</h4> <h4 class="font-bold-500">Max Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.htlc_maximum_msat | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.htlc_maximum_msat | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Min Htlc (mSat)</h4> <h4 class="font-bold-500">Min Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.htlc_minimum_msat | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.htlc_minimum_msat | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Message Flags</h4> <h4 class="font-bold-500">Message Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.message_flags | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.message_flags | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Public</h4> <h4 class="font-bold-500">Public</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.public ? 'Yes' : 'No'}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.public ? 'Yes' : 'No'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Source</h4> <h4 class="font-bold-500">Source</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.source}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.source}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Destination</h4> <h4 class="font-bold-500">Destination</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.destination}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[0]?.destination}}</span>
</div> </div>
</div> </div>
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start stretch" class="mt-1 bordered-box padding-gap-large"> <div fxLayout="column" fxFlex="49" fxLayoutAlign="start stretch" class="mt-1 bordered-box padding-gap-large">
@ -85,72 +85,72 @@
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Short Channel ID</h4> <h4 class="font-bold-500">Short Channel ID</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.short_channel_id}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.short_channel_id}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Active</h4> <h4 class="font-bold-500">Active</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.active ? 'True' : 'False'}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.active ? 'True' : 'False'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Last Update</h4> <h4 class="font-bold-500">Last Update</h4>
<span class="foreground-secondary-text">{{(lookupResult[1]?.last_update * 1000) | date:'dd/MMM/y HH:mm'}}</span> <span class="foreground-secondary-text">{{(lookupResult.channels[1]?.last_update * 1000) | date:'dd/MMM/y HH:mm'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Amount (Sats)</h4> <h4 class="font-bold-500">Amount (Sats)</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.amount_msat / 1000 | number:'1.0-0'}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.amount_msat / 1000 | number:'1.0-0'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Base Fee (mSats)</h4> <h4 class="font-bold-500">Base Fee (mSats)</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.base_fee_millisatoshi | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.base_fee_millisatoshi | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Fee/Millionth</h4> <h4 class="font-bold-500">Fee/Millionth</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.fee_per_millionth | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.fee_per_millionth | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Channel Flags</h4> <h4 class="font-bold-500">Channel Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.channel_flags | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.channel_flags | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Delay</h4> <h4 class="font-bold-500">Delay</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.delay | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.delay | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Max Htlc (mSat)</h4> <h4 class="font-bold-500">Max Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.htlc_maximum_msat | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.htlc_maximum_msat | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Min Htlc (mSat)</h4> <h4 class="font-bold-500">Min Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.htlc_minimum_msat | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.htlc_minimum_msat | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Message Flags</h4> <h4 class="font-bold-500">Message Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.message_flags | number}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.message_flags | number}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Public</h4> <h4 class="font-bold-500">Public</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.public ? 'Yes' : 'No'}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.public ? 'Yes' : 'No'}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Source</h4> <h4 class="font-bold-500">Source</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.source}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.source}}</span>
</div> </div>
<mat-divider class="my-1" /> <mat-divider class="my-1" />
<div fxLayout="column"> <div fxLayout="column">
<h4 class="font-bold-500">Destination</h4> <h4 class="font-bold-500">Destination</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.destination}}</span> <span class="foreground-secondary-text">{{lookupResult.channels[1]?.destination}}</span>
</div> </div>
</div> </div>
</div> </div>

@ -26,6 +26,7 @@ describe('CLNChannelLookupComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(CLNChannelLookupComponent); fixture = TestBed.createComponent(CLNChannelLookupComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.lookupResult = { channels: [] };
fixture.detectChanges(); fixture.detectChanges();
}); });

@ -3,7 +3,7 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { ChannelEdge, GetInfo } from '../../../../shared/models/clnModels'; import { GetInfo, ChannelEdge } from '../../../../shared/models/clnModels';
import { RTLState } from '../../../../store/rtl.state'; import { RTLState } from '../../../../store/rtl.state';
import { clnNodeInformation } from '../../../store/cln.selector'; import { clnNodeInformation } from '../../../store/cln.selector';
@ -14,7 +14,7 @@ import { clnNodeInformation } from '../../../store/cln.selector';
}) })
export class CLNChannelLookupComponent implements OnInit { export class CLNChannelLookupComponent implements OnInit {
@Input() lookupResult: ChannelEdge[] = []; @Input() lookupResult: ChannelEdge = {};
public node1_match = false; public node1_match = false;
public node2_match = false; public node2_match = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()]; private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
@ -24,10 +24,10 @@ export class CLNChannelLookupComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[0])). this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[0])).
subscribe((nodeInfo: GetInfo) => { subscribe((nodeInfo: GetInfo) => {
if (this.lookupResult.length > 0 && this.lookupResult[0].source === nodeInfo.id) { if (this.lookupResult.channels && this.lookupResult.channels.length > 0 && this.lookupResult.channels[0].source === nodeInfo.id) {
this.node1_match = true; this.node1_match = true;
} }
if (this.lookupResult.length > 1 && this.lookupResult[1].source === nodeInfo.id) { if (this.lookupResult.channels && this.lookupResult.channels.length > 1 && this.lookupResult.channels[1].source === nodeInfo.id) {
this.node2_match = true; this.node2_match = true;
} }
}); });

@ -25,7 +25,7 @@
</div> </div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center" [ngSwitch]="selectedFieldId"> <div fxLayout="row" fxFlex="100" fxLayoutAlign="start center" [ngSwitch]="selectedFieldId">
<span *ngSwitchCase="0" fxFlex="100"><div *ngIf="nodeLookupValue.nodeid !== ''; else errorBlock"><rtl-cln-node-lookup [lookupResult]="nodeLookupValue" /></div></span> <span *ngSwitchCase="0" fxFlex="100"><div *ngIf="nodeLookupValue.nodeid !== ''; else errorBlock"><rtl-cln-node-lookup [lookupResult]="nodeLookupValue" /></div></span>
<span *ngSwitchCase="1" fxFlex="100"><div *ngIf="channelLookupValue.length>0; else errorBlock"><rtl-cln-channel-lookup [lookupResult]="channelLookupValue" /></div></span> <span *ngSwitchCase="1" fxFlex="100"><div *ngIf="channelLookupValue.channels && channelLookupValue.channels.length>0; else errorBlock"><rtl-cln-channel-lookup [lookupResult]="channelLookupValue" /></div></span>
<span *ngSwitchDefault> fxFlex="100"<h3>Error! Unable to find details!</h3></span> <span *ngSwitchDefault> fxFlex="100"<h3>Error! Unable to find details!</h3></span>
</div> </div>
</div> </div>

@ -54,7 +54,7 @@ export class CLNLookupsComponent implements OnInit, OnDestroy {
this.nodeLookupValue = typeof resLookup.payload[0] !== 'object' ? { nodeid: '' } : JSON.parse(JSON.stringify(resLookup.payload[0])); this.nodeLookupValue = typeof resLookup.payload[0] !== 'object' ? { nodeid: '' } : JSON.parse(JSON.stringify(resLookup.payload[0]));
break; break;
case 1: case 1:
this.channelLookupValue = typeof resLookup.payload[0] !== 'object' ? [] : JSON.parse(JSON.stringify(resLookup.payload)); this.channelLookupValue = resLookup.payload.channels && typeof resLookup.payload.channels !== 'object' ? { channels: [] } : JSON.parse(JSON.stringify(resLookup.payload));
break; break;
default: default:
break; break;

@ -57,10 +57,9 @@ export class CLNQueryRoutesComponent implements OnInit, OnDestroy {
}); });
this.clnEffects.setQueryRoutesCL.pipe(takeUntil(this.unSubs[1])).subscribe((queryRoute) => { this.clnEffects.setQueryRoutesCL.pipe(takeUntil(this.unSubs[1])).subscribe((queryRoute) => {
this.qrHops.data = []; this.qrHops.data = [];
if (queryRoute.routes && queryRoute.routes.length && queryRoute.routes.length > 0) { if (queryRoute.route && queryRoute.route.length && queryRoute.route.length > 0) {
this.flgLoading[0] = false; this.flgLoading[0] = false;
this.qrHops = new MatTableDataSource<Routes>([...queryRoute.routes]); this.qrHops = new MatTableDataSource<Routes>([...queryRoute.route]);
this.qrHops.data = queryRoute.routes;
} else { } else {
this.flgLoading[0] = 'error'; this.flgLoading[0] = 'error';
} }

@ -26,23 +26,23 @@
<mat-card-content fxLayout="column" fxFlex="{{card.id === 'capacity' ? 90 : 70}}" <mat-card-content fxLayout="column" fxFlex="{{card.id === 'capacity' ? 90 : 70}}"
[ngClass]="{'dashboard-card-content': true, [ngClass]="{'dashboard-card-content': true,
'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) || 'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) ||
(card.id === 'balance' && (apiCallStatusBalance.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) || (card.id === 'balance' && apiCallStatusBalances.status === apiCallStatusEnum.ERROR) ||
(card.id === 'capacity' && (apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) || (card.id === 'capacity' && (apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusBalances.status === apiCallStatusEnum.ERROR)) ||
(card.id === 'fee' && (apiCallStatusFees.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusFHistory.status === apiCallStatusEnum.ERROR)) || (card.id === 'fee' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusFHistory.status === apiCallStatusEnum.ERROR)) ||
(card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR))}"> (card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusBalances.status === apiCallStatusEnum.ERROR))}">
<mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) || <mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'balance' && (apiCallStatusBalance.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) || (card.id === 'balance' && apiCallStatusBalances.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'capacity' && (apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) || (card.id === 'capacity' && (apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusBalances.status === apiCallStatusEnum.INITIATED)) ||
(card.id === 'fee' && (apiCallStatusFees.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusFHistory.status === apiCallStatusEnum.INITIATED)) || (card.id === 'fee' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusFHistory.status === apiCallStatusEnum.INITIATED)) ||
(card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED))" (card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusBalances.status === apiCallStatusEnum.INITIATED))"
mode="indeterminate" mode="indeterminate"
/> />
<div fxLayout="column" fxFlex="100" [ngSwitch]="card.id"> <div fxLayout="column" fxFlex="100" [ngSwitch]="card.id">
<rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" [showColorFieldSeparately]="false" /> <rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" [showColorFieldSeparately]="false" />
<rtl-cln-balances-info *ngSwitchCase="'balance'" fxFlex="100" [balances]="balances" [errorMessage]="errorMessages[2] + ' ' + errorMessages[3]" /> <rtl-cln-balances-info *ngSwitchCase="'balance'" fxFlex="100" [balances]="balances" [errorMessage]="errorMessages[1]" />
<rtl-cln-channel-capacity-info *ngSwitchCase="'capacity'" fxFlex="100" [sortBy]="sortField" [channelBalances]="channelBalances" [activeChannels]="activeChannelsCapacity" [errorMessage]="errorMessages[4] + ' ' + errorMessages[3]" /> <rtl-cln-channel-capacity-info *ngSwitchCase="'capacity'" fxFlex="100" [sortBy]="sortField" [channelBalances]="channelBalances" [activeChannels]="activeChannelsCapacity" [errorMessage]="errorMessages[2] + ' ' + errorMessages[1]" />
<rtl-cln-fee-info *ngSwitchCase="'fee'" fxFlex="100" [fees]="fees" [errorMessage]="errorMessages[1] + ' ' + errorMessages[4] + ' ' + errorMessages[5]" /> <rtl-cln-fee-info *ngSwitchCase="'fee'" fxFlex="100" [fees]="fees" [errorMessage]="errorMessages[0] + ' ' + errorMessages[2] + ' ' + errorMessages[3]" />
<rtl-cln-channel-status-info *ngSwitchCase="'status'" fxFlex="100" [channelsStatus]="channelsStatus" [errorMessage]="errorMessages[0] + ' ' + errorMessages[3] + ' ' + errorMessages[4]" /> <rtl-cln-channel-status-info *ngSwitchCase="'status'" fxFlex="100" [channelsStatus]="channelsStatus" [errorMessage]="errorMessages[0] + ' ' + errorMessages[1] + ' ' + errorMessages[2]" />
<h3 *ngSwitchDefault>Error! Unable to find information!</h3> <h3 *ngSwitchDefault>Error! Unable to find information!</h3>
</div> </div>
</mat-card-content> </mat-card-content>
@ -77,18 +77,18 @@
<mat-card-content fxLayout="column" fxLayoutAlign="start stretch" fxFlex="{{card.id === 'transactions' ? 100 : card.id === 'balance' ? 70: 90}}" <mat-card-content fxLayout="column" fxLayoutAlign="start stretch" fxFlex="{{card.id === 'transactions' ? 100 : card.id === 'balance' ? 70: 90}}"
[ngClass]="{'dashboard-card-content': true, [ngClass]="{'dashboard-card-content': true,
'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) || 'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) ||
(card.id === 'balance' && (apiCallStatusBalance.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) || (card.id === 'balance' && apiCallStatusBalances.status === apiCallStatusEnum.ERROR) ||
((card.id === 'inboundLiq' || card.id === 'outboundLiq') && apiCallStatusChannels.status === apiCallStatusEnum.ERROR)}"> ((card.id === 'inboundLiq' || card.id === 'outboundLiq') && apiCallStatusChannels.status === apiCallStatusEnum.ERROR)}">
<mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) || <mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'balance' && (apiCallStatusBalance.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) || (card.id === 'balance' && apiCallStatusBalances.status === apiCallStatusEnum.INITIATED) ||
((card.id === 'inboundLiq' || card.id === 'outboundLiq') && apiCallStatusChannels.status === apiCallStatusEnum.INITIATED)" ((card.id === 'inboundLiq' || card.id === 'outboundLiq') && apiCallStatusChannels.status === apiCallStatusEnum.INITIATED)"
mode="indeterminate" mode="indeterminate"
/> />
<div fxLayout="column" fxFlex="100" [ngSwitch]="card.id"> <div fxLayout="column" fxFlex="100" [ngSwitch]="card.id">
<rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" /> <rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" />
<rtl-cln-balances-info *ngSwitchCase="'balance'" fxFlex="100" [balances]="balances" [errorMessage]="errorMessages[2] + ' ' + errorMessages[3]" /> <rtl-cln-balances-info *ngSwitchCase="'balance'" fxFlex="100" [balances]="balances" [errorMessage]="errorMessages[1]" />
<rtl-cln-channel-liquidity-info *ngSwitchCase="'inboundLiq'" fxFlex="100" [direction]="'In'" [totalLiquidity]="totalInboundLiquidity" [activeChannels]="allInboundChannels" [errorMessage]="errorMessages[4]" /> <rtl-cln-channel-liquidity-info *ngSwitchCase="'inboundLiq'" fxFlex="100" [direction]="'In'" [totalLiquidity]="totalInboundLiquidity" [activeChannels]="allInboundChannels" [errorMessage]="errorMessages[2]" />
<rtl-cln-channel-liquidity-info *ngSwitchCase="'outboundLiq'" fxFlex="100" [direction]="'Out'" [totalLiquidity]="totalOutboundLiquidity" [activeChannels]="allOutboundChannels" [errorMessage]="errorMessages[4]" /> <rtl-cln-channel-liquidity-info *ngSwitchCase="'outboundLiq'" fxFlex="100" [direction]="'Out'" [totalLiquidity]="totalOutboundLiquidity" [activeChannels]="allOutboundChannels" [errorMessage]="errorMessages[2]" />
<span *ngSwitchCase="'transactions'" fxLayout="row" fxFlex="100" fxLayoutAlign="space-between start"> <span *ngSwitchCase="'transactions'" fxLayout="row" fxFlex="100" fxLayoutAlign="space-between start">
<mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start" fxLayout="column" class="dashboard-tabs-group"> <mat-tab-group mat-stretch-tabs="false" mat-align-tabs="start" fxLayout="column" class="dashboard-tabs-group">
<mat-tab label="Receive"><rtl-cln-lightning-invoices-table class="h-100" [calledFrom]="'home'" /></mat-tab> <mat-tab label="Receive"><rtl-cln-lightning-invoices-table class="h-100" [calledFrom]="'home'" /></mat-tab>

@ -8,13 +8,13 @@ import { faAngleDoubleDown, faAngleDoubleUp, faChartPie, faBolt, faServer, faNet
import { SelNodeChild } from '../../shared/models/RTLconfig'; import { SelNodeChild } from '../../shared/models/RTLconfig';
import { UserPersonaEnum, ScreenSizeEnum, APICallStatusEnum } from '../../shared/services/consts-enums-functions'; import { UserPersonaEnum, ScreenSizeEnum, APICallStatusEnum } from '../../shared/services/consts-enums-functions';
import { ChannelsStatus, GetInfo, Fees, Channel, Balance, LocalRemoteBalance } from '../../shared/models/clnModels'; import { ChannelsStatus, GetInfo, Fees, Channel, Balance, LocalRemoteBalance, UTXO } from '../../shared/models/clnModels';
import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload'; import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload';
import { LoggerService } from '../../shared/services/logger.service'; import { LoggerService } from '../../shared/services/logger.service';
import { CommonService } from '../../shared/services/common.service'; import { CommonService } from '../../shared/services/common.service';
import { RTLState } from '../../store/rtl.state'; import { RTLState } from '../../store/rtl.state';
import { balance, channels, fees, localRemoteBalance, nodeInfoAndNodeSettingsAndAPIsStatus } from '../store/cln.selector'; import { channels, utxoBalances, nodeInfoAndNodeSettingsAndAPIsStatus } from '../store/cln.selector';
export interface Tile { export interface Tile {
id: string; id: string;
@ -61,11 +61,9 @@ export class CLNHomeComponent implements OnInit, OnDestroy {
public operatorCardHeight = '390px'; public operatorCardHeight = '390px';
public merchantCardHeight = '62px'; public merchantCardHeight = '62px';
public sortField = 'Balance Score'; public sortField = 'Balance Score';
public errorMessages = ['', '', '', '', '', '']; public errorMessages = ['', '', '', ''];
public apiCallStatusNodeInfo: ApiCallStatusPayload | null = null; public apiCallStatusNodeInfo: ApiCallStatusPayload | null = null;
public apiCallStatusFees: ApiCallStatusPayload | null = null; public apiCallStatusBalances: ApiCallStatusPayload | null = null;
public apiCallStatusBalance: ApiCallStatusPayload | null = null;
public apiCallStatusLRBal: ApiCallStatusPayload | null = null;
public apiCallStatusChannels: ApiCallStatusPayload | null = null; public apiCallStatusChannels: ApiCallStatusPayload | null = null;
public apiCallStatusFHistory: ApiCallStatusPayload | null = null; public apiCallStatusFHistory: ApiCallStatusPayload | null = null;
public apiCallStatusEnum = APICallStatusEnum; public apiCallStatusEnum = APICallStatusEnum;
@ -120,36 +118,27 @@ export class CLNHomeComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.store.select(nodeInfoAndNodeSettingsAndAPIsStatus).pipe(takeUntil(this.unSubs[0])). this.store.select(nodeInfoAndNodeSettingsAndAPIsStatus).pipe(takeUntil(this.unSubs[0])).
subscribe((infoSettingsStatusSelector: { information: GetInfo, nodeSettings: SelNodeChild | null, apisCallStatus: ApiCallStatusPayload[] }) => { subscribe((infoSettingsStatusSelector: { information: GetInfo, nodeSettings: SelNodeChild | null, fees: Fees, apisCallStatus: ApiCallStatusPayload[] }) => {
this.errorMessages[0] = ''; this.errorMessages[0] = '';
this.errorMessages[5] = ''; this.errorMessages[3] = '';
this.apiCallStatusNodeInfo = infoSettingsStatusSelector.apisCallStatus[0]; this.apiCallStatusNodeInfo = infoSettingsStatusSelector.apisCallStatus[0];
this.apiCallStatusFHistory = infoSettingsStatusSelector.apisCallStatus[1]; this.apiCallStatusFHistory = infoSettingsStatusSelector.apisCallStatus[1];
if (this.apiCallStatusNodeInfo.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusNodeInfo.status === APICallStatusEnum.ERROR) {
this.errorMessages[0] = !this.apiCallStatusNodeInfo.message ? '' : (typeof (this.apiCallStatusNodeInfo.message) === 'object') ? JSON.stringify(this.apiCallStatusNodeInfo.message) : this.apiCallStatusNodeInfo.message; this.errorMessages[0] = !this.apiCallStatusNodeInfo.message ? '' : (typeof (this.apiCallStatusNodeInfo.message) === 'object') ? JSON.stringify(this.apiCallStatusNodeInfo.message) : this.apiCallStatusNodeInfo.message;
} }
if (this.apiCallStatusFHistory.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusFHistory.status === APICallStatusEnum.ERROR) {
this.errorMessages[5] = !this.apiCallStatusFHistory.message ? '' : (typeof (this.apiCallStatusFHistory.message) === 'object') ? JSON.stringify(this.apiCallStatusFHistory.message) : this.apiCallStatusFHistory.message; this.errorMessages[3] = !this.apiCallStatusFHistory.message ? '' : (typeof (this.apiCallStatusFHistory.message) === 'object') ? JSON.stringify(this.apiCallStatusFHistory.message) : this.apiCallStatusFHistory.message;
} }
this.selNode = infoSettingsStatusSelector.nodeSettings; this.selNode = infoSettingsStatusSelector.nodeSettings;
this.information = infoSettingsStatusSelector.information; this.information = infoSettingsStatusSelector.information;
}); this.fees = infoSettingsStatusSelector.fees;
this.store.select(fees).pipe(takeUntil(this.unSubs[1])).
subscribe((feesSeletor: { fees: Fees, apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessages[1] = '';
this.apiCallStatusFees = feesSeletor.apiCallStatus;
if (this.apiCallStatusFees.status === APICallStatusEnum.ERROR) {
this.errorMessages[1] = !this.apiCallStatusFees.message ? '' : (typeof (this.apiCallStatusFees.message) === 'object') ? JSON.stringify(this.apiCallStatusFees.message) : this.apiCallStatusFees.message;
}
this.fees = feesSeletor.fees;
this.logger.info(feesSeletor);
}); });
this.store.select(channels).pipe(takeUntil(this.unSubs[2])). this.store.select(channels).pipe(takeUntil(this.unSubs[2])).
subscribe((channelsSeletor: { activeChannels: Channel[], pendingChannels: Channel[], inactiveChannels: Channel[], apiCallStatus: ApiCallStatusPayload }) => { subscribe((channelsSeletor: { activeChannels: Channel[], pendingChannels: Channel[], inactiveChannels: Channel[], apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessages[4] = ''; this.errorMessages[2] = '';
this.apiCallStatusChannels = channelsSeletor.apiCallStatus; this.apiCallStatusChannels = channelsSeletor.apiCallStatus;
if (this.apiCallStatusChannels.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusChannels.status === APICallStatusEnum.ERROR) {
this.errorMessages[4] = !this.apiCallStatusChannels.message ? '' : (typeof (this.apiCallStatusChannels.message) === 'object') ? JSON.stringify(this.apiCallStatusChannels.message) : this.apiCallStatusChannels.message; this.errorMessages[2] = !this.apiCallStatusChannels.message ? '' : (typeof (this.apiCallStatusChannels.message) === 'object') ? JSON.stringify(this.apiCallStatusChannels.message) : this.apiCallStatusChannels.message;
} }
this.totalInboundLiquidity = 0; this.totalInboundLiquidity = 0;
this.totalOutboundLiquidity = 0; this.totalOutboundLiquidity = 0;
@ -166,34 +155,27 @@ export class CLNHomeComponent implements OnInit, OnDestroy {
this.channelsStatus.inactive.channels = channelsSeletor.inactiveChannels.length || 0; this.channelsStatus.inactive.channels = channelsSeletor.inactiveChannels.length || 0;
this.logger.info(channelsSeletor); this.logger.info(channelsSeletor);
}); });
this.store.select(balance).pipe(takeUntil(this.unSubs[3]), this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[3])).
withLatestFrom(this.store.select(localRemoteBalance))). subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
subscribe(([balanceSeletor, lrBalanceSeletor]: [{ balance: Balance, apiCallStatus: ApiCallStatusPayload }, { localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }]) => { this.errorMessages[1] = '';
this.errorMessages[2] = ''; this.apiCallStatusBalances = utxoBalancesSeletor.apiCallStatus;
this.apiCallStatusBalance = balanceSeletor.apiCallStatus; if (this.apiCallStatusBalances.status === APICallStatusEnum.ERROR) {
if (this.apiCallStatusBalance.status === APICallStatusEnum.ERROR) { this.errorMessages[1] = !this.apiCallStatusBalances.message ? '' : (typeof (this.apiCallStatusBalances.message) === 'object') ? JSON.stringify(this.apiCallStatusBalances.message) : this.apiCallStatusBalances.message;
this.errorMessages[2] = !this.apiCallStatusBalance.message ? '' : (typeof (this.apiCallStatusBalance.message) === 'object') ? JSON.stringify(this.apiCallStatusBalance.message) : this.apiCallStatusBalance.message;
}
this.errorMessages[3] = '';
this.apiCallStatusLRBal = lrBalanceSeletor.apiCallStatus;
if (this.apiCallStatusLRBal.status === APICallStatusEnum.ERROR) {
this.errorMessages[3] = !this.apiCallStatusLRBal.message ? '' : (typeof (this.apiCallStatusLRBal.message) === 'object') ? JSON.stringify(this.apiCallStatusLRBal.message) : this.apiCallStatusLRBal.message;
} }
this.totalBalance = balanceSeletor.balance; this.totalBalance = utxoBalancesSeletor.balance;
this.balances.onchain = balanceSeletor.balance.totalBalance || 0; this.balances.onchain = utxoBalancesSeletor.balance.totalBalance || 0;
this.balances.lightning = lrBalanceSeletor.localRemoteBalance.localBalance; this.balances.lightning = utxoBalancesSeletor.localRemoteBalance.localBalance;
this.balances.total = this.balances.lightning + this.balances.onchain; this.balances.total = this.balances.lightning + this.balances.onchain;
this.balances = Object.assign({}, this.balances); this.balances = Object.assign({}, this.balances);
const local = (lrBalanceSeletor.localRemoteBalance.localBalance) ? +lrBalanceSeletor.localRemoteBalance.localBalance : 0; const local = (utxoBalancesSeletor.localRemoteBalance.localBalance) ? +utxoBalancesSeletor.localRemoteBalance.localBalance : 0;
const remote = (lrBalanceSeletor.localRemoteBalance.remoteBalance) ? +lrBalanceSeletor.localRemoteBalance.remoteBalance : 0; const remote = (utxoBalancesSeletor.localRemoteBalance.remoteBalance) ? +utxoBalancesSeletor.localRemoteBalance.remoteBalance : 0;
const total = local + remote; const total = local + remote;
this.channelBalances = { localBalance: local, remoteBalance: remote, balancedness: +(1 - Math.abs((local - remote) / total)).toFixed(3) }; this.channelBalances = { localBalance: local, remoteBalance: remote, balancedness: +(1 - Math.abs((local - remote) / total)).toFixed(3) };
this.channelsStatus.active.capacity = lrBalanceSeletor.localRemoteBalance.localBalance || 0; this.channelsStatus.active.capacity = utxoBalancesSeletor.localRemoteBalance.localBalance || 0;
this.channelsStatus.pending.capacity = lrBalanceSeletor.localRemoteBalance.pendingBalance || 0; this.channelsStatus.pending.capacity = utxoBalancesSeletor.localRemoteBalance.pendingBalance || 0;
this.channelsStatus.inactive.capacity = lrBalanceSeletor.localRemoteBalance.inactiveBalance || 0; this.channelsStatus.inactive.capacity = utxoBalancesSeletor.localRemoteBalance.inactiveBalance || 0;
this.logger.info(balanceSeletor); this.logger.info(utxoBalancesSeletor);
this.logger.info(lrBalanceSeletor);
}); });
} }

@ -93,7 +93,7 @@ export class CLNLiquidityAdsListComponent implements OnInit, OnDestroy {
this.colWidth = this.displayedColumns.length ? ((this.commonService.getContainerSize().width / this.displayedColumns.length) / 14) + 'rem' : '20rem'; this.colWidth = this.displayedColumns.length ? ((this.commonService.getContainerSize().width / this.displayedColumns.length) / 14) + 'rem' : '20rem';
this.logger.info(this.displayedColumns); this.logger.info(this.displayedColumns);
}); });
combineLatest([this.store.select(nodeInfoAndNodeSettingsAndBalance), this.dataService.listNetworkNodes('?liquidity_ads=yes')]).pipe(takeUntil(this.unSubs[1])). combineLatest([this.store.select(nodeInfoAndNodeSettingsAndBalance), this.dataService.listNetworkNodes({ liquidity_ads: true })]).pipe(takeUntil(this.unSubs[1])).
subscribe({ subscribe({
next: ([infoSettingsBalSelector, nodeListRes]) => { next: ([infoSettingsBalSelector, nodeListRes]) => {
this.information = infoSettingsBalSelector.information; this.information = infoSettingsBalSelector.information;

@ -75,7 +75,7 @@ export class CLNOpenLiquidityChannelComponent implements OnInit, OnDestroy {
if (!this.node || !this.node.option_will_fund || !this.requestedAmount || !this.feeRate || !this.localAmount || this.localAmount < 20000) { if (!this.node || !this.node.option_will_fund || !this.requestedAmount || !this.feeRate || !this.localAmount || this.localAmount < 20000) {
return true; return true;
} }
const newChannel = { peerId: this.node.nodeid || '', satoshis: this.localAmount.toString(), feeRate: this.feeRate + 'perkb', requestAmount: this.requestedAmount.toString(), compactLease: this.node.option_will_fund.compact_lease, announce: true }; const newChannel = { peerId: this.node.nodeid || '', amount: this.localAmount.toString(), feeRate: this.feeRate + 'perkb', requestAmount: this.requestedAmount.toString(), compactLease: this.node.option_will_fund.compact_lease, announce: true };
this.store.dispatch(saveNewChannel({ payload: newChannel })); this.store.dispatch(saveNewChannel({ payload: newChannel }));
} }

@ -14,13 +14,13 @@
[ngClass]="{'dashboard-card-content': true, [ngClass]="{'dashboard-card-content': true,
'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) || 'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) ||
(card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) || (card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) ||
(card.id === 'fee' && (apiCallStatusFees.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusFHistory.status === apiCallStatusEnum.ERROR)) || (card.id === 'fee' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusFHistory.status === apiCallStatusEnum.ERROR)) ||
(card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.ERROR) || (card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.ERROR) ||
(card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR) || (card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR) ||
(card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR)}"> (card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR)}">
<mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) || <mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) || (card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) ||
(card.id === 'fee' && (apiCallStatusFees.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusFHistory.status === apiCallStatusEnum.INITIATED)) || (card.id === 'fee' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusFHistory.status === apiCallStatusEnum.INITIATED)) ||
(card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.INITIATED) || (card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED) || (card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED)" (card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED)"
@ -28,11 +28,11 @@
/> />
<div fxLayout="column" fxFlex="100" [ngSwitch]="card.id"> <div fxLayout="column" fxFlex="100" [ngSwitch]="card.id">
<rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" [showColorFieldSeparately]="false" /> <rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" [showColorFieldSeparately]="false" />
<rtl-cln-channel-status-info *ngSwitchCase="'status'" fxFlex="100" [channelsStatus]="channelsStatus" [errorMessage]="errorMessages[0] + ' ' + errorMessages[2]" /> <rtl-cln-channel-status-info *ngSwitchCase="'status'" fxFlex="100" [channelsStatus]="channelsStatus" [errorMessage]="errorMessages[0] + ' ' + errorMessages[1]" />
<rtl-cln-fee-info *ngSwitchCase="'fee'" fxFlex="100" [fees]="fees" [errorMessage]="errorMessages[1] + ' ' + errorMessages[3] + ' ' + errorMessages[4]" /> <rtl-cln-fee-info *ngSwitchCase="'fee'" fxFlex="100" [fees]="fees" [errorMessage]="errorMessages[0] + ' ' + errorMessages[2] + ' ' + errorMessages[3]" />
<rtl-cln-fee-rates *ngSwitchCase="'feeRatesKB'" class="h-100" [feeRates]="feeRatesPerKB" [feeRateStyle]="'KB'" [errorMessage]="errorMessages[5]" /> <rtl-cln-fee-rates *ngSwitchCase="'feeRatesKB'" class="h-100" [feeRates]="feeRatesPerKB" [feeRateStyle]="'KB'" [errorMessage]="errorMessages[4]" />
<rtl-cln-fee-rates *ngSwitchCase="'feeRatesKW'" class="h-100" [feeRates]="feeRatesPerKW" [feeRateStyle]="'KW'" [errorMessage]="errorMessages[6]" /> <rtl-cln-fee-rates *ngSwitchCase="'feeRatesKW'" class="h-100" [feeRates]="feeRatesPerKW" [feeRateStyle]="'KW'" [errorMessage]="errorMessages[5]" />
<rtl-cln-onchain-fee-estimates *ngSwitchCase="'onChainFeeEstimates'" class="h-100" [feeRates]="feeRatesPerKW" [errorMessage]="errorMessages[5]" /> <rtl-cln-onchain-fee-estimates *ngSwitchCase="'onChainFeeEstimates'" class="h-100" [feeRates]="feeRatesPerKW" [errorMessage]="errorMessages[4]" />
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
@ -55,13 +55,13 @@
[ngClass]="{'dashboard-card-content': true, [ngClass]="{'dashboard-card-content': true,
'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) || 'error-border': (card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR) ||
(card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) || (card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusLRBal.status === apiCallStatusEnum.ERROR)) ||
(card.id === 'fee' && (apiCallStatusFees.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusFHistory.status === apiCallStatusEnum.ERROR)) || (card.id === 'fee' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.ERROR || apiCallStatusChannels.status === apiCallStatusEnum.ERROR || apiCallStatusFHistory.status === apiCallStatusEnum.ERROR)) ||
(card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.ERROR) || (card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.ERROR) ||
(card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR) || (card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR) ||
(card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR)}"> (card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.ERROR)}">
<mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) || <mat-progress-bar *ngIf="(card.id === 'node' && apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) || (card.id === 'status' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusLRBal.status === apiCallStatusEnum.INITIATED)) ||
(card.id === 'fee' && (apiCallStatusFees.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusFHistory.status === apiCallStatusEnum.INITIATED)) || (card.id === 'fee' && (apiCallStatusNodeInfo.status === apiCallStatusEnum.INITIATED || apiCallStatusChannels.status === apiCallStatusEnum.INITIATED || apiCallStatusFHistory.status === apiCallStatusEnum.INITIATED)) ||
(card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.INITIATED) || (card.id === 'feeRatesKB' && apiCallStatusPerKB.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED) || (card.id === 'feeRatesKW' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED) ||
(card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED)" (card.id === 'onChainFeeEstimates' && apiCallStatusPerKW.status === apiCallStatusEnum.INITIATED)"
@ -69,11 +69,11 @@
/> />
<div fxLayout="column" fxFlex="100" [ngSwitch]="card.id"> <div fxLayout="column" fxFlex="100" [ngSwitch]="card.id">
<rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" [showColorFieldSeparately]="false" /> <rtl-cln-node-info *ngSwitchCase="'node'" fxFlex="100" [information]="information" [showColorFieldSeparately]="false" />
<rtl-cln-channel-status-info *ngSwitchCase="'status'" fxFlex="100" [channelsStatus]="channelsStatus" [errorMessage]="errorMessages[0] + ' ' + errorMessages[2]" /> <rtl-cln-channel-status-info *ngSwitchCase="'status'" fxFlex="100" [channelsStatus]="channelsStatus" [errorMessage]="errorMessages[0] + ' ' + errorMessages[1]" />
<rtl-cln-fee-info *ngSwitchCase="'fee'" fxFlex="100" [fees]="fees" [errorMessage]="errorMessages[1] + ' ' + errorMessages[3] + ' ' + errorMessages[4]" /> <rtl-cln-fee-info *ngSwitchCase="'fee'" fxFlex="100" [fees]="fees" [errorMessage]="errorMessages[0] + ' ' + errorMessages[2] + ' ' + errorMessages[3]" />
<rtl-cln-fee-rates *ngSwitchCase="'feeRatesKB'" class="h-100" [feeRates]="feeRatesPerKB" [feeRateStyle]="'KB'" [errorMessage]="errorMessages[5]" /> <rtl-cln-fee-rates *ngSwitchCase="'feeRatesKB'" class="h-100" [feeRates]="feeRatesPerKB" [feeRateStyle]="'KB'" [errorMessage]="errorMessages[4]" />
<rtl-cln-fee-rates *ngSwitchCase="'feeRatesKW'" class="h-100" [feeRates]="feeRatesPerKW" [feeRateStyle]="'KW'" [errorMessage]="errorMessages[5]" /> <rtl-cln-fee-rates *ngSwitchCase="'feeRatesKW'" class="h-100" [feeRates]="feeRatesPerKW" [feeRateStyle]="'KW'" [errorMessage]="errorMessages[4]" />
<rtl-cln-onchain-fee-estimates *ngSwitchCase="'onChainFeeEstimates'" class="h-100" [feeRates]="feeRatesPerKW" [errorMessage]="errorMessages[5]" /> <rtl-cln-onchain-fee-estimates *ngSwitchCase="'onChainFeeEstimates'" class="h-100" [feeRates]="feeRatesPerKW" [errorMessage]="errorMessages[4]" />
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

@ -5,14 +5,14 @@ import { Store } from '@ngrx/store';
import { faBolt, faServer, faNetworkWired, faLink } from '@fortawesome/free-solid-svg-icons'; import { faBolt, faServer, faNetworkWired, faLink } from '@fortawesome/free-solid-svg-icons';
import { SelNodeChild } from '../../shared/models/RTLconfig'; import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfo, Fees, ChannelsStatus, FeeRates, LocalRemoteBalance, Channel, ListForwards } from '../../shared/models/clnModels'; import { GetInfo, Fees, ChannelsStatus, FeeRates, LocalRemoteBalance, Channel, ListForwards, UTXO, Balance } from '../../shared/models/clnModels';
import { APICallStatusEnum, ScreenSizeEnum, UserPersonaEnum } from '../../shared/services/consts-enums-functions'; import { APICallStatusEnum, ScreenSizeEnum, UserPersonaEnum } from '../../shared/services/consts-enums-functions';
import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload'; import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload';
import { LoggerService } from '../../shared/services/logger.service'; import { LoggerService } from '../../shared/services/logger.service';
import { CommonService } from '../../shared/services/common.service'; import { CommonService } from '../../shared/services/common.service';
import { RTLState } from '../../store/rtl.state'; import { RTLState } from '../../store/rtl.state';
import { channels, feeRatesPerKB, feeRatesPerKW, fees, forwardingHistory, localRemoteBalance, nodeInfoAndNodeSettingsAndAPIsStatus } from '../store/cln.selector'; import { channels, feeRatesPerKB, feeRatesPerKW, forwardingHistory, utxoBalances, nodeInfoAndNodeSettingsAndAPIsStatus } from '../store/cln.selector';
@Component({ @Component({
selector: 'rtl-cln-network-info', selector: 'rtl-cln-network-info',
@ -36,11 +36,10 @@ export class CLNNetworkInfoComponent implements OnInit, OnDestroy {
public screenSize = ''; public screenSize = '';
public screenSizeEnum = ScreenSizeEnum; public screenSizeEnum = ScreenSizeEnum;
public userPersonaEnum = UserPersonaEnum; public userPersonaEnum = UserPersonaEnum;
public errorMessages = ['', '', '', '', '', '', '']; public errorMessages = ['', '', '', '', '', ''];
public apiCallStatusNodeInfo: ApiCallStatusPayload | null = null; public apiCallStatusNodeInfo: ApiCallStatusPayload | null = null;
public apiCallStatusLRBal: ApiCallStatusPayload | null = null; public apiCallStatusLRBal: ApiCallStatusPayload | null = null;
public apiCallStatusChannels: ApiCallStatusPayload | null = null; public apiCallStatusChannels: ApiCallStatusPayload | null = null;
public apiCallStatusFees: ApiCallStatusPayload | null = null;
public apiCallStatusFHistory: ApiCallStatusPayload | null = null; public apiCallStatusFHistory: ApiCallStatusPayload | null = null;
public apiCallStatusPerKB: ApiCallStatusPayload | null = null; public apiCallStatusPerKB: ApiCallStatusPayload | null = null;
public apiCallStatusPerKW: ApiCallStatusPayload | null = null; public apiCallStatusPerKW: ApiCallStatusPayload | null = null;
@ -82,7 +81,7 @@ export class CLNNetworkInfoComponent implements OnInit, OnDestroy {
ngOnInit() { ngOnInit() {
this.store.select(nodeInfoAndNodeSettingsAndAPIsStatus).pipe(takeUntil(this.unSubs[0])). this.store.select(nodeInfoAndNodeSettingsAndAPIsStatus).pipe(takeUntil(this.unSubs[0])).
subscribe((infoSettingsStatusSelector: { information: GetInfo, nodeSettings: SelNodeChild | null, apisCallStatus: ApiCallStatusPayload[] }) => { subscribe((infoSettingsStatusSelector: { information: GetInfo, nodeSettings: SelNodeChild | null, fees: Fees, apisCallStatus: ApiCallStatusPayload[] }) => {
this.errorMessages[0] = ''; this.errorMessages[0] = '';
this.apiCallStatusNodeInfo = infoSettingsStatusSelector.apisCallStatus[0]; this.apiCallStatusNodeInfo = infoSettingsStatusSelector.apisCallStatus[0];
if (this.apiCallStatusNodeInfo.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusNodeInfo.status === APICallStatusEnum.ERROR) {
@ -90,43 +89,35 @@ export class CLNNetworkInfoComponent implements OnInit, OnDestroy {
} }
this.selNode = infoSettingsStatusSelector.nodeSettings; this.selNode = infoSettingsStatusSelector.nodeSettings;
this.information = infoSettingsStatusSelector.information; this.information = infoSettingsStatusSelector.information;
this.fees = infoSettingsStatusSelector.fees;
this.logger.info(infoSettingsStatusSelector); this.logger.info(infoSettingsStatusSelector);
}); });
this.store.select(channels).pipe(takeUntil(this.unSubs[1]), this.store.select(channels).pipe(takeUntil(this.unSubs[1]),
withLatestFrom(this.store.select(localRemoteBalance))). withLatestFrom(this.store.select(utxoBalances))).
subscribe(([channelsSeletor, lrBalanceSeletor]: [{ activeChannels: Channel[], pendingChannels: Channel[], inactiveChannels: Channel[], apiCallStatus: ApiCallStatusPayload }, { localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }]) => { subscribe(([channelsSeletor, utxoBalancesSeletor]: [{ activeChannels: Channel[], pendingChannels: Channel[], inactiveChannels: Channel[], apiCallStatus: ApiCallStatusPayload }, { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }]) => {
this.errorMessages[1] = '';
this.errorMessages[2] = ''; this.errorMessages[2] = '';
this.errorMessages[3] = ''; this.apiCallStatusLRBal = utxoBalancesSeletor.apiCallStatus;
this.apiCallStatusLRBal = channelsSeletor.apiCallStatus; this.apiCallStatusChannels = channelsSeletor.apiCallStatus;
this.apiCallStatusChannels = lrBalanceSeletor.apiCallStatus;
if (this.apiCallStatusLRBal.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusLRBal.status === APICallStatusEnum.ERROR) {
this.errorMessages[2] = (typeof (this.apiCallStatusLRBal.message) === 'object') ? JSON.stringify(this.apiCallStatusLRBal.message) : this.apiCallStatusLRBal.message ? this.apiCallStatusLRBal.message : ''; this.errorMessages[1] = (typeof (this.apiCallStatusLRBal.message) === 'object') ? JSON.stringify(this.apiCallStatusLRBal.message) : this.apiCallStatusLRBal.message ? this.apiCallStatusLRBal.message : '';
} }
if (this.apiCallStatusChannels.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusChannels.status === APICallStatusEnum.ERROR) {
this.errorMessages[3] = (typeof (this.apiCallStatusChannels.message) === 'object') ? JSON.stringify(this.apiCallStatusChannels.message) : this.apiCallStatusChannels.message ? this.apiCallStatusChannels.message : ''; this.errorMessages[2] = (typeof (this.apiCallStatusChannels.message) === 'object') ? JSON.stringify(this.apiCallStatusChannels.message) : this.apiCallStatusChannels.message ? this.apiCallStatusChannels.message : '';
} }
this.channelsStatus.active.channels = channelsSeletor.activeChannels.length || 0; this.channelsStatus.active.channels = channelsSeletor.activeChannels.length || 0;
this.channelsStatus.pending.channels = channelsSeletor.pendingChannels.length || 0; this.channelsStatus.pending.channels = channelsSeletor.pendingChannels.length || 0;
this.channelsStatus.inactive.channels = channelsSeletor.inactiveChannels.length || 0; this.channelsStatus.inactive.channels = channelsSeletor.inactiveChannels.length || 0;
this.channelsStatus.active.capacity = lrBalanceSeletor.localRemoteBalance.localBalance || 0; this.channelsStatus.active.capacity = utxoBalancesSeletor.localRemoteBalance.localBalance || 0;
this.channelsStatus.pending.capacity = lrBalanceSeletor.localRemoteBalance.pendingBalance || 0; this.channelsStatus.pending.capacity = utxoBalancesSeletor.localRemoteBalance.pendingBalance || 0;
this.channelsStatus.inactive.capacity = lrBalanceSeletor.localRemoteBalance.inactiveBalance || 0; this.channelsStatus.inactive.capacity = utxoBalancesSeletor.localRemoteBalance.inactiveBalance || 0;
});
this.store.select(fees).pipe(takeUntil(this.unSubs[2])).
subscribe((feesSeletor: { fees: Fees, apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessages[1] = '';
this.apiCallStatusFees = feesSeletor.apiCallStatus;
if (this.apiCallStatusFees.status === APICallStatusEnum.ERROR) {
this.errorMessages[1] = (typeof (this.apiCallStatusFees.message) === 'object') ? JSON.stringify(this.apiCallStatusFees.message) : this.apiCallStatusFees.message ? this.apiCallStatusFees.message : '';
}
this.fees = feesSeletor.fees;
}); });
this.store.select(forwardingHistory).pipe(takeUntil(this.unSubs[3])). this.store.select(forwardingHistory).pipe(takeUntil(this.unSubs[3])).
subscribe((fhSeletor: { forwardingHistory: ListForwards, apiCallStatus: ApiCallStatusPayload }) => { subscribe((fhSeletor: { forwardingHistory: ListForwards, apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessages[4] = ''; this.errorMessages[3] = '';
this.apiCallStatusFHistory = fhSeletor.apiCallStatus; this.apiCallStatusFHistory = fhSeletor.apiCallStatus;
if (this.apiCallStatusFHistory.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusFHistory.status === APICallStatusEnum.ERROR) {
this.errorMessages[4] = (typeof (this.apiCallStatusFHistory.message) === 'object') ? JSON.stringify(this.apiCallStatusFHistory.message) : this.apiCallStatusFHistory.message ? this.apiCallStatusFHistory.message : ''; this.errorMessages[3] = (typeof (this.apiCallStatusFHistory.message) === 'object') ? JSON.stringify(this.apiCallStatusFHistory.message) : this.apiCallStatusFHistory.message ? this.apiCallStatusFHistory.message : '';
} }
if (fhSeletor.forwardingHistory && fhSeletor.forwardingHistory.listForwards && fhSeletor.forwardingHistory.listForwards.length) { if (fhSeletor.forwardingHistory && fhSeletor.forwardingHistory.listForwards && fhSeletor.forwardingHistory.listForwards.length) {
this.fees.totalTxCount = fhSeletor.forwardingHistory.listForwards.length; this.fees.totalTxCount = fhSeletor.forwardingHistory.listForwards.length;
@ -134,19 +125,19 @@ export class CLNNetworkInfoComponent implements OnInit, OnDestroy {
}); });
this.store.select(feeRatesPerKB).pipe(takeUntil(this.unSubs[4])). this.store.select(feeRatesPerKB).pipe(takeUntil(this.unSubs[4])).
subscribe((frbSeletor: { feeRatesPerKB: FeeRates, apiCallStatus: ApiCallStatusPayload }) => { subscribe((frbSeletor: { feeRatesPerKB: FeeRates, apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessages[5] = ''; this.errorMessages[4] = '';
this.apiCallStatusPerKB = frbSeletor.apiCallStatus; this.apiCallStatusPerKB = frbSeletor.apiCallStatus;
if (this.apiCallStatusPerKB.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusPerKB.status === APICallStatusEnum.ERROR) {
this.errorMessages[5] = (typeof (this.apiCallStatusPerKB.message) === 'object') ? JSON.stringify(this.apiCallStatusPerKB.message) : this.apiCallStatusPerKB.message ? this.apiCallStatusPerKB.message : ''; this.errorMessages[4] = (typeof (this.apiCallStatusPerKB.message) === 'object') ? JSON.stringify(this.apiCallStatusPerKB.message) : this.apiCallStatusPerKB.message ? this.apiCallStatusPerKB.message : '';
} }
this.feeRatesPerKB = frbSeletor.feeRatesPerKB; this.feeRatesPerKB = frbSeletor.feeRatesPerKB;
}); });
this.store.select(feeRatesPerKW).pipe(takeUntil(this.unSubs[5])). this.store.select(feeRatesPerKW).pipe(takeUntil(this.unSubs[5])).
subscribe((frwSeletor: { feeRatesPerKW: FeeRates, apiCallStatus: ApiCallStatusPayload }) => { subscribe((frwSeletor: { feeRatesPerKW: FeeRates, apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessages[6] = ''; this.errorMessages[5] = '';
this.apiCallStatusPerKW = frwSeletor.apiCallStatus; this.apiCallStatusPerKW = frwSeletor.apiCallStatus;
if (this.apiCallStatusPerKW.status === APICallStatusEnum.ERROR) { if (this.apiCallStatusPerKW.status === APICallStatusEnum.ERROR) {
this.errorMessages[6] = (typeof (this.apiCallStatusPerKW.message) === 'object') ? JSON.stringify(this.apiCallStatusPerKW.message) : this.apiCallStatusPerKW.message ? this.apiCallStatusPerKW.message : ''; this.errorMessages[5] = (typeof (this.apiCallStatusPerKW.message) === 'object') ? JSON.stringify(this.apiCallStatusPerKW.message) : this.apiCallStatusPerKW.message ? this.apiCallStatusPerKW.message : '';
} }
this.feeRatesPerKW = frwSeletor.feeRatesPerKW; this.feeRatesPerKW = frwSeletor.feeRatesPerKW;
}); });

@ -10,15 +10,15 @@
<form *ngIf="!sweepAll; else sweepAllBlock;" #form="ngForm" fxLayout="row wrap"fxLayoutAlign="space-between start" fxFlex="100" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()"> <form *ngIf="!sweepAll; else sweepAllBlock;" #form="ngForm" fxLayout="row wrap"fxLayoutAlign="space-between start" fxFlex="100" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()">
<mat-form-field fxLayout="column" fxFlex="55"> <mat-form-field fxLayout="column" fxFlex="55">
<mat-label>Bitcoin Address</mat-label> <mat-label>Bitcoin Address</mat-label>
<input #address="ngModel" matInput autoFocus tabindex="1" name="address" required [(ngModel)]="transaction.address"> <input #address="ngModel" matInput autoFocus tabindex="1" name="address" required [(ngModel)]="transaction.destination">
<mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error> <mat-error *ngIf="!transaction.destination">Bitcoin address is required.</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field fxLayout="column" fxFlex="30"> <mat-form-field fxLayout="column" fxFlex="30">
<mat-label>Amount</mat-label> <mat-label>Amount</mat-label>
<input #amount="ngModel" matInput name="amount" tabindex="2" required [type]="flgUseAllBalance ? 'text' : 'number'" [step]="100" [min]="0" [disabled]="flgUseAllBalance" [(ngModel)]="transaction.satoshis"> <input #amount="ngModel" matInput name="amount" tabindex="2" required [type]="flgUseAllBalance ? 'text' : 'number'" [step]="100" [min]="0" [disabled]="flgUseAllBalance" [(ngModel)]="transaction.satoshi">
<mat-hint *ngIf="flgUseAllBalance">Amount replaced by UTXO balance</mat-hint> <mat-hint *ngIf="flgUseAllBalance">Amount replaced by UTXO balance</mat-hint>
<span matSuffix>{{selAmountUnit}} </span> <span matSuffix>{{selAmountUnit}} </span>
<mat-error *ngIf="!transaction.satoshis">{{amountError}}</mat-error> <mat-error *ngIf="!transaction.satoshi">{{amountError}}</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field fxLayout="column" fxFlex="10" fxLayoutAlign="start end"> <mat-form-field fxLayout="column" fxFlex="10" fxLayoutAlign="start end">
<mat-select tabindex="3" required name="amountUnit" [value]="selAmountUnit" [disabled]="flgUseAllBalance" (selectionChange)="onAmountUnitChange($event)"> <mat-select tabindex="3" required name="amountUnit" [value]="selAmountUnit" [disabled]="flgUseAllBalance" (selectionChange)="onAmountUnitChange($event)">
@ -27,7 +27,7 @@
</mat-form-field> </mat-form-field>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch"> <div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayoutAlign.gt-sm="space-between center" fxLayout.gt-sm="row wrap"> <div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayoutAlign.gt-sm="space-between center" fxLayout.gt-sm="row wrap">
<div fxFlex="48" fxLayoutAlign="space-between end"> <div fxFlex="48" fxLayoutAlign="space-between start">
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '48' : '100'"> <mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '48' : '100'">
<mat-label>Fee Rate</mat-label> <mat-label>Fee Rate</mat-label>
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate" (selectionChange)="customFeeRate=null"> <mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate" (selectionChange)="customFeeRate=null">
@ -118,7 +118,7 @@
<mat-error *ngIf="sendFundFormGroup.controls.transactionAddress.errors?.required">Bitcoin address is required.</mat-error> <mat-error *ngIf="sendFundFormGroup.controls.transactionAddress.errors?.required">Bitcoin address is required.</mat-error>
</mat-form-field> </mat-form-field>
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap" fxFlex="100" fxLayoutAlign.gt-sm="space-between center"> <div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap" fxFlex="100" fxLayoutAlign.gt-sm="space-between center">
<div fxFlex="48" fxLayoutAlign="space-between end"> <div fxFlex="48" fxLayoutAlign="space-between start">
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="sendFundFormGroup.controls.selFeeRate.value === 'customperkb' && !sendFundFormGroup.controls.flgMinConf.value ? '48' : '100'"> <mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="sendFundFormGroup.controls.selFeeRate.value === 'customperkb' && !sendFundFormGroup.controls.flgMinConf.value ? '48' : '100'">
<mat-label>Fee Rate</mat-label> <mat-label>Fee Rate</mat-label>
<mat-select tabindex="4" formControlName="selFeeRate"> <mat-select tabindex="4" formControlName="selFeeRate">

@ -13,7 +13,7 @@ import * as sha256 from 'sha256';
import { SelNodeChild, RTLConfiguration } from '../../../shared/models/RTLconfig'; import { SelNodeChild, RTLConfiguration } from '../../../shared/models/RTLconfig';
import { CLNOnChainSendFunds } from '../../../shared/models/alertData'; import { CLNOnChainSendFunds } from '../../../shared/models/alertData';
import { GetInfo, Balance, OnChain, UTXO } from '../../../shared/models/clnModels'; import { GetInfo, Balance, OnChain, UTXO, LocalRemoteBalance } from '../../../shared/models/clnModels';
import { CURRENCY_UNITS, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, ADDRESS_TYPES, FEE_RATE_TYPES, APICallStatusEnum, CLNActions, ScreenSizeEnum } from '../../../shared/services/consts-enums-functions'; import { CURRENCY_UNITS, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, ADDRESS_TYPES, FEE_RATE_TYPES, APICallStatusEnum, CLNActions, ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
import { CommonService } from '../../../shared/services/common.service'; import { CommonService } from '../../../shared/services/common.service';
import { LoggerService } from '../../../shared/services/logger.service'; import { LoggerService } from '../../../shared/services/logger.service';
@ -23,7 +23,7 @@ import { RTLState } from '../../../store/rtl.state';
import { isAuthorized, openSnackBar } from '../../../store/rtl.actions'; import { isAuthorized, openSnackBar } from '../../../store/rtl.actions';
import { setChannelTransaction } from '../../store/cln.actions'; import { setChannelTransaction } from '../../store/cln.actions';
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector'; import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
import { clnNodeInformation, utxos } from '../../store/cln.selector'; import { clnNodeInformation, utxoBalances } from '../../store/cln.selector';
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload'; import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
@Component({ @Component({
@ -140,10 +140,10 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
subscribe((nodeInfo: GetInfo) => { subscribe((nodeInfo: GetInfo) => {
this.information = nodeInfo; this.information = nodeInfo;
}); });
this.store.select(utxos).pipe(takeUntil(this.unSubs[3])). this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[3])).
subscribe((utxosSeletor: { utxos: UTXO[], apiCallStatus: ApiCallStatusPayload }) => { subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
this.utxos = this.commonService.sortAscByKey(utxosSeletor.utxos?.filter((utxo) => utxo.status === 'confirmed'), 'value'); this.utxos = this.commonService.sortAscByKey(utxoBalancesSeletor.utxos?.filter((utxo) => utxo.status === 'confirmed'), 'value');
this.logger.info(utxosSeletor); this.logger.info(utxoBalancesSeletor);
}); });
this.actions.pipe( this.actions.pipe(
takeUntil(this.unSubs[4]), takeUntil(this.unSubs[4]),
@ -181,7 +181,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
onSendFunds(): boolean | void { onSendFunds(): boolean | void {
this.sendFundError = ''; this.sendFundError = '';
if (this.flgUseAllBalance) { if (this.flgUseAllBalance) {
this.transaction.satoshis = 'all'; this.transaction.satoshi = 'all';
} }
if (this.selUTXOs.length && this.selUTXOs.length > 0) { if (this.selUTXOs.length && this.selUTXOs.length > 0) {
this.transaction.utxos = []; this.transaction.utxos = [];
@ -195,14 +195,14 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
) { ) {
return true; return true;
} }
this.transaction.satoshis = 'all'; this.transaction.satoshi = 'all';
this.transaction.address = this.sendFundFormGroup.controls.transactionAddress.value; this.transaction.destination = this.sendFundFormGroup.controls.transactionAddress.value;
if (this.sendFundFormGroup.controls.flgMinConf.value) { if (this.sendFundFormGroup.controls.flgMinConf.value) {
delete this.transaction.feeRate; delete this.transaction.feerate;
this.transaction.minconf = this.sendFundFormGroup.controls.flgMinConf.value ? this.sendFundFormGroup.controls.minConfValue.value : null; this.transaction.minconf = this.sendFundFormGroup.controls.flgMinConf.value ? this.sendFundFormGroup.controls.minConfValue.value : null;
} else { } else {
delete this.transaction.minconf; delete this.transaction.minconf;
this.transaction.feeRate = (this.sendFundFormGroup.controls.selFeeRate.value === 'customperkb' && this.transaction.feerate = (this.sendFundFormGroup.controls.selFeeRate.value === 'customperkb' &&
!this.sendFundFormGroup.controls.flgMinConf.value && this.sendFundFormGroup.controls.customFeeRate.value) ? !this.sendFundFormGroup.controls.flgMinConf.value && this.sendFundFormGroup.controls.customFeeRate.value) ?
((this.sendFundFormGroup.controls.customFeeRate.value * 1000) + 'perkb') : this.sendFundFormGroup.controls.selFeeRate.value; ((this.sendFundFormGroup.controls.customFeeRate.value * 1000) + 'perkb') : this.sendFundFormGroup.controls.selFeeRate.value;
} }
@ -210,23 +210,23 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
this.store.dispatch(setChannelTransaction({ payload: this.transaction })); this.store.dispatch(setChannelTransaction({ payload: this.transaction }));
} else { } else {
this.transaction.minconf = this.flgMinConf ? this.minConfValue : null; this.transaction.minconf = this.flgMinConf ? this.minConfValue : null;
this.transaction['feeRate'] = (this.selFeeRate === 'customperkb' && !this.flgMinConf && this.customFeeRate) ? (this.customFeeRate * 1000) + 'perkb' : this.selFeeRate; this.transaction['feerate'] = (this.selFeeRate === 'customperkb' && !this.flgMinConf && this.customFeeRate) ? (this.customFeeRate * 1000) + 'perkb' : this.selFeeRate !== '' ? this.selFeeRate : null;
if ((!this.transaction.address || this.transaction.address === '') || if ((!this.transaction.destination || this.transaction.destination === '') ||
((!this.transaction.satoshis || +this.transaction.satoshis <= 0)) || ((!this.transaction.satoshi || +this.transaction.satoshi <= 0)) ||
(this.flgMinConf && (!this.transaction.minconf || this.transaction.minconf <= 0)) || (this.flgMinConf && (!this.transaction.minconf || this.transaction.minconf <= 0)) ||
(this.selFeeRate === 'customperkb' && !this.flgMinConf && !this.customFeeRate)) { (this.selFeeRate === 'customperkb' && !this.flgMinConf && !this.customFeeRate)) {
return true; return true;
} }
if (this.transaction.satoshis && this.transaction.satoshis !== 'all' && this.selAmountUnit !== CurrencyUnitEnum.SATS) { if (this.transaction.satoshi && this.transaction.satoshi !== 'all' && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
this.commonService.convertCurrency(+this.transaction.satoshis, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion). this.commonService.convertCurrency(+this.transaction.satoshi, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[5])). pipe(takeUntil(this.unSubs[5])).
subscribe({ subscribe({
next: (data) => { next: (data) => {
this.transaction.satoshis = data[CurrencyUnitEnum.SATS]; this.transaction.satoshi = data[CurrencyUnitEnum.SATS];
this.selAmountUnit = CurrencyUnitEnum.SATS; this.selAmountUnit = CurrencyUnitEnum.SATS;
this.store.dispatch(setChannelTransaction({ payload: this.transaction })); this.store.dispatch(setChannelTransaction({ payload: this.transaction }));
}, error: (err) => { }, error: (err) => {
this.transaction.satoshis = null; this.transaction.satoshi = null;
this.selAmountUnit = CurrencyUnitEnum.SATS; this.selAmountUnit = CurrencyUnitEnum.SATS;
this.amountError = 'Conversion Error: ' + err; this.amountError = 'Conversion Error: ' + err;
} }
@ -287,17 +287,17 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
} }
} else { } else {
this.totalSelectedUTXOAmount = null; this.totalSelectedUTXOAmount = null;
this.transaction.satoshis = null; this.transaction.satoshi = null;
this.flgUseAllBalance = false; this.flgUseAllBalance = false;
} }
} }
onUTXOAllBalanceChange() { onUTXOAllBalanceChange() {
if (this.flgUseAllBalance) { if (this.flgUseAllBalance) {
this.transaction.satoshis = this.totalSelectedUTXOAmount; this.transaction.satoshi = this.totalSelectedUTXOAmount;
this.selAmountUnit = CURRENCY_UNITS[0]; this.selAmountUnit = CURRENCY_UNITS[0];
} else { } else {
this.transaction.satoshis = null; this.transaction.satoshi = null;
} }
} }
@ -305,15 +305,15 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
const self = this; const self = this;
const prevSelectedUnit = (this.selAmountUnit === this.amountUnits[2]) ? CurrencyUnitEnum.OTHER : this.selAmountUnit; const prevSelectedUnit = (this.selAmountUnit === this.amountUnits[2]) ? CurrencyUnitEnum.OTHER : this.selAmountUnit;
let currSelectedUnit = event.value === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : event.value; let currSelectedUnit = event.value === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : event.value;
if (this.transaction.satoshis && this.selAmountUnit !== event.value) { if (this.transaction.satoshi && this.selAmountUnit !== event.value) {
this.commonService.convertCurrency(+this.transaction.satoshis, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion). this.commonService.convertCurrency(+this.transaction.satoshi, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[6])). pipe(takeUntil(this.unSubs[6])).
subscribe({ subscribe({
next: (data) => { next: (data) => {
this.selAmountUnit = event.value; this.selAmountUnit = event.value;
self.transaction.satoshis = self.decimalPipe.transform(data[currSelectedUnit], self.currencyUnitFormats[currSelectedUnit])?.replace(/,/g, ''); self.transaction.satoshi = self.decimalPipe.transform(data[currSelectedUnit], self.currencyUnitFormats[currSelectedUnit])?.replace(/,/g, '');
}, error: (err) => { }, error: (err) => {
self.transaction.satoshis = null; self.transaction.satoshi = null;
this.amountError = 'Conversion Error: ' + err; this.amountError = 'Conversion Error: ' + err;
this.selAmountUnit = prevSelectedUnit; this.selAmountUnit = prevSelectedUnit;
currSelectedUnit = prevSelectedUnit; currSelectedUnit = prevSelectedUnit;

@ -9,8 +9,8 @@ import { CLNOnChainSendModalComponent } from './on-chain-send-modal/on-chain-sen
import { SelNodeChild } from '../../shared/models/RTLconfig'; import { SelNodeChild } from '../../shared/models/RTLconfig';
import { RTLState } from '../../store/rtl.state'; import { RTLState } from '../../store/rtl.state';
import { openAlert } from '../../store/rtl.actions'; import { openAlert } from '../../store/rtl.actions';
import { balance, clnNodeSettings } from '../store/cln.selector'; import { utxoBalances, clnNodeSettings } from '../store/cln.selector';
import { Balance } from '../../shared/models/clnModels'; import { Balance, LocalRemoteBalance, UTXO } from '../../shared/models/clnModels';
import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload'; import { ApiCallStatusPayload } from '../../shared/models/apiCallsPayload';
@Component({ @Component({
@ -48,9 +48,9 @@ export class CLNOnChainComponent implements OnInit, OnDestroy {
subscribe((nodeSettings: SelNodeChild | null) => { subscribe((nodeSettings: SelNodeChild | null) => {
this.selNode = nodeSettings; this.selNode = nodeSettings;
}); });
this.store.select(balance).pipe(takeUntil(this.unSubs[2])). this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[2])).
subscribe((balanceSeletor: { balance: Balance, apiCallStatus: ApiCallStatusPayload }) => { subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
this.balances = [{ title: 'Total Balance', dataValue: balanceSeletor.balance.totalBalance || 0 }, { title: 'Confirmed', dataValue: (balanceSeletor.balance.confBalance || 0) }, { title: 'Unconfirmed', dataValue: (balanceSeletor.balance.unconfBalance || 0) }]; this.balances = [{ title: 'Total Balance', dataValue: utxoBalancesSeletor.balance.totalBalance || 0 }, { title: 'Confirmed', dataValue: (utxoBalancesSeletor.balance.confBalance || 0) }, { title: 'Unconfirmed', dataValue: (utxoBalancesSeletor.balance.unconfBalance || 0) }];
}); });
} }

@ -3,11 +3,11 @@ import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { UTXO } from '../../../shared/models/clnModels'; import { Balance, LocalRemoteBalance, UTXO } from '../../../shared/models/clnModels';
import { LoggerService } from '../../../shared/services/logger.service'; import { LoggerService } from '../../../shared/services/logger.service';
import { RTLState } from '../../../store/rtl.state'; import { RTLState } from '../../../store/rtl.state';
import { utxos } from '../../store/cln.selector'; import { utxoBalances } from '../../store/cln.selector';
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload'; import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
@Component({ @Component({
@ -27,13 +27,13 @@ export class CLNUTXOTablesComponent implements OnInit, OnDestroy {
constructor(private logger: LoggerService, private store: Store<RTLState>) { } constructor(private logger: LoggerService, private store: Store<RTLState>) { }
ngOnInit() { ngOnInit() {
this.store.select(utxos).pipe(takeUntil(this.unSubs[0])). this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[0])).
subscribe((utxosSeletor: { utxos: UTXO[], apiCallStatus: ApiCallStatusPayload }) => { subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
if (utxosSeletor.utxos && utxosSeletor.utxos.length > 0) { if (utxoBalancesSeletor.utxos && utxoBalancesSeletor.utxos.length > 0) {
this.numUtxos = utxosSeletor.utxos.length || 0; this.numUtxos = utxoBalancesSeletor.utxos.length || 0;
this.numDustUtxos = utxosSeletor.utxos?.filter((utxo) => (+(utxo.amount_msat || 0) / 1000) < this.DUST_AMOUNT).length || 0; this.numDustUtxos = utxoBalancesSeletor.utxos?.filter((utxo) => (+(utxo.amount_msat || 0) / 1000) < this.DUST_AMOUNT).length || 0;
} }
this.logger.info(utxosSeletor); this.logger.info(utxoBalancesSeletor);
}); });
} }

@ -1,6 +1,6 @@
.mat-column-is_dust { .mat-column-is_dust {
max-width: 2.2rem; max-width: 3rem;
width: 2.2rem; width: 3rem;
text-overflow: unset; text-overflow: unset;
} }

@ -6,7 +6,7 @@ import { Store } from '@ngrx/store';
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator'; import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { UTXO } from '../../../../shared/models/clnModels'; import { Balance, LocalRemoteBalance, UTXO } from '../../../../shared/models/clnModels';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, APICallStatusEnum, SortOrderEnum, CLN_DEFAULT_PAGE_SETTINGS, CLN_PAGE_DEFS } from '../../../../shared/services/consts-enums-functions'; import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, APICallStatusEnum, SortOrderEnum, CLN_DEFAULT_PAGE_SETTINGS, CLN_PAGE_DEFS } from '../../../../shared/services/consts-enums-functions';
import { ApiCallStatusPayload } from '../../../../shared/models/apiCallsPayload'; import { ApiCallStatusPayload } from '../../../../shared/models/apiCallsPayload';
import { LoggerService } from '../../../../shared/services/logger.service'; import { LoggerService } from '../../../../shared/services/logger.service';
@ -14,7 +14,7 @@ import { CommonService } from '../../../../shared/services/common.service';
import { RTLState } from '../../../../store/rtl.state'; import { RTLState } from '../../../../store/rtl.state';
import { openAlert } from '../../../../store/rtl.actions'; import { openAlert } from '../../../../store/rtl.actions';
import { clnPageSettings, utxos } from '../../../store/cln.selector'; import { clnPageSettings, utxoBalances } from '../../../store/cln.selector';
import { ColumnDefinition, PageSettings, TableSetting } from '../../../../shared/models/pageSettings'; import { ColumnDefinition, PageSettings, TableSetting } from '../../../../shared/models/pageSettings';
import { CamelCaseWithReplacePipe } from '../../../../shared/pipes/app.pipe'; import { CamelCaseWithReplacePipe } from '../../../../shared/pipes/app.pipe';
import { MAT_SELECT_CONFIG } from '@angular/material/select'; import { MAT_SELECT_CONFIG } from '@angular/material/select';
@ -79,39 +79,39 @@ export class CLNOnChainUtxosComponent implements OnInit, AfterViewInit, OnDestro
this.colWidth = this.displayedColumns.length ? ((this.commonService.getContainerSize().width / this.displayedColumns.length) / 14) + 'rem' : '20rem'; this.colWidth = this.displayedColumns.length ? ((this.commonService.getContainerSize().width / this.displayedColumns.length) / 14) + 'rem' : '20rem';
this.logger.info(this.displayedColumns); this.logger.info(this.displayedColumns);
}); });
this.store.select(utxos).pipe(takeUntil(this.unSubs[1])). this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[1])).
subscribe((utxosSelector: { utxos: UTXO[], apiCallStatus: ApiCallStatusPayload }) => { subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
this.errorMessage = ''; this.errorMessage = '';
this.apiCallStatus = utxosSelector.apiCallStatus; this.apiCallStatus = utxoBalancesSeletor.apiCallStatus;
if (this.apiCallStatus.status === APICallStatusEnum.ERROR) { if (this.apiCallStatus.status === APICallStatusEnum.ERROR) {
this.errorMessage = !this.apiCallStatus.message ? '' : (typeof (this.apiCallStatus.message) === 'object') ? JSON.stringify(this.apiCallStatus.message) : this.apiCallStatus.message; this.errorMessage = !this.apiCallStatus.message ? '' : (typeof (this.apiCallStatus.message) === 'object') ? JSON.stringify(this.apiCallStatus.message) : this.apiCallStatus.message;
} }
if (utxosSelector.utxos && utxosSelector.utxos.length > 0) { if (utxoBalancesSeletor.utxos && utxoBalancesSeletor.utxos.length > 0) {
this.dustUtxos = utxosSelector.utxos?.filter((utxo) => +(utxo.amount_msat || 0) / 1000 < this.dustAmount); this.dustUtxos = utxoBalancesSeletor.utxos?.filter((utxo) => +(utxo.amount_msat || 0) / 1000 < this.dustAmount);
this.utxos = utxosSelector.utxos; this.utxos = utxoBalancesSeletor.utxos;
if (this.isDustUTXO) { if (this.isDustUTXO) {
if (this.dustUtxos && this.dustUtxos.length > 0 && this.sort && this.paginator && this.displayedColumns.length > 0) { if (this.dustUtxos && this.sort && this.paginator && this.displayedColumns.length > 0) {
this.loadUTXOsTable(this.dustUtxos); this.loadUTXOsTable(this.dustUtxos);
} }
} else { } else {
this.displayedColumns.unshift('is_dust'); this.displayedColumns.unshift('is_dust');
if (this.utxos && this.utxos.length > 0 && this.sort && this.paginator && this.displayedColumns.length > 0) { if (this.utxos && this.sort && this.paginator && this.displayedColumns.length > 0) {
this.loadUTXOsTable(this.utxos); this.loadUTXOsTable(this.utxos);
} }
} }
} }
this.logger.info(utxosSelector); this.logger.info(utxoBalancesSeletor);
}); });
} }
ngAfterViewInit() { ngAfterViewInit() {
setTimeout(() => { setTimeout(() => {
if (this.isDustUTXO) { if (this.isDustUTXO) {
if (this.dustUtxos && this.dustUtxos.length > 0) { if (this.dustUtxos && this.sort && this.paginator && this.displayedColumns.length > 0) {
this.loadUTXOsTable(this.dustUtxos); this.loadUTXOsTable(this.dustUtxos);
} }
} else { } else {
if (this.utxos && this.utxos.length > 0) { if (this.utxos && this.sort && this.paginator && this.displayedColumns.length > 0) {
this.loadUTXOsTable(this.utxos); this.loadUTXOsTable(this.utxos);
} }
} }

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

Loading…
Cancel
Save