mirror of
https://github.com/Ride-The-Lightning/RTL.git
synced 2024-11-19 01:40:29 +01:00
Release 0.15.3 (#1467)
* Fix `Unknown command` error when disabling offers on CLN. ([#1443]) (#1451) * Add missing SSO options to config (#1455) * Fix for cln logic screen navigation (#1457) * Transactions destination address display fix (#1458) * cln delexpiredinvoices deprecation fix (#1459) * Read LN_IMPLEMENTATION from environment (#1460) * Add Fee Rate Information on Send Funds Modal (#1461) * Artifact script fix (#1464) * Add AMP toggle for LND Send Payments (#1466) --------- Co-authored-by: Se7enZ <118189041+s373nZ@users.noreply.github.com>
This commit is contained in:
parent
6027167e2a
commit
a594606d27
@ -116,7 +116,6 @@
|
|||||||
"no-label-var": "error",
|
"no-label-var": "error",
|
||||||
"no-restricted-globals": "error",
|
"no-restricted-globals": "error",
|
||||||
"no-undef-init": "error",
|
"no-undef-init": "error",
|
||||||
"no-undefined": "error",
|
|
||||||
"block-spacing": "error",
|
"block-spacing": "error",
|
||||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||||
"comma-style": "error",
|
"comma-style": "error",
|
||||||
|
44
.github/workflows/ci.yml
vendored
44
.github/workflows/ci.yml
vendored
@ -2,31 +2,29 @@ name: Artifact
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ master, 'Release-*' ]
|
|
||||||
tags: [ 'v*' ]
|
tags: [ 'v*' ]
|
||||||
release:
|
release:
|
||||||
types: [released]
|
types: [released]
|
||||||
# Triggers the workflow only when merging pull request to the branches.
|
|
||||||
pull_request:
|
|
||||||
types: [closed]
|
|
||||||
branches: [ master, 'Release-*' ]
|
|
||||||
# Allows you to run this workflow manually from the Actions tab
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
version:
|
||||||
|
description: 'Release version'
|
||||||
|
required: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v2
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 18.x
|
node-version: 18.x
|
||||||
|
|
||||||
- name: Cache node_modules
|
- name: Cache node_modules
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
id: cache-npm-packages
|
id: cache-npm-packages
|
||||||
with:
|
with:
|
||||||
path: node_modules
|
path: node_modules
|
||||||
@ -37,7 +35,7 @@ jobs:
|
|||||||
run: npm ci --legacy-peer-deps
|
run: npm ci --legacy-peer-deps
|
||||||
|
|
||||||
- name: Cache build frontend
|
- name: Cache build frontend
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
id: cache-build-frontend
|
id: cache-build-frontend
|
||||||
with:
|
with:
|
||||||
path: frontend
|
path: frontend
|
||||||
@ -47,7 +45,7 @@ jobs:
|
|||||||
run: npm run buildfrontend
|
run: npm run buildfrontend
|
||||||
|
|
||||||
- name: Cache build backend
|
- name: Cache build backend
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
id: cache-build-backend
|
id: cache-build-backend
|
||||||
with:
|
with:
|
||||||
path: backend
|
path: backend
|
||||||
@ -61,26 +59,32 @@ jobs:
|
|||||||
needs: build
|
needs: build
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Cache build frontend
|
- name: Cache build frontend
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
id: cache-build-frontend
|
id: cache-build-frontend
|
||||||
with:
|
with:
|
||||||
path: frontend
|
path: frontend
|
||||||
key: ${{ runner.os }}-frontend-${{ github.sha }}
|
key: ${{ runner.os }}-frontend-${{ github.sha }}
|
||||||
|
|
||||||
- name: Cache build backend
|
- name: Cache build backend
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
id: cache-build-backend
|
id: cache-build-backend
|
||||||
with:
|
with:
|
||||||
path: backend
|
path: backend
|
||||||
key: ${{ runner.os }}-backend-${{ github.sha }}
|
key: ${{ runner.os }}-backend-${{ github.sha }}
|
||||||
|
|
||||||
- name: Compress files
|
- name: Compress files
|
||||||
run: tar -czf /tmp/rtlbuild.tar.gz frontend backend rtl.js package.json package-lock.json
|
env:
|
||||||
|
VERSION: "${{ github.event.release.tag_name || github.event.inputs.version || '' }}"
|
||||||
- uses: actions/upload-artifact@v2
|
run: |
|
||||||
|
tar -czf /tmp/rtl-build-$VERSION.tar.gz frontend backend rtl.js package.json package-lock.json
|
||||||
|
zip -r /tmp/rtl-build-$VERSION.zip frontend backend rtl.js package.json package-lock.json
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: rtl-build-${{ github.event.release.tag_name }}
|
name: rtl-build-${{ github.event.release.tag_name || github.event.inputs.version || '' }}
|
||||||
path: /tmp/rtlbuild.tar.gz
|
path: |
|
||||||
|
/tmp/rtl-build-${{ github.event.release.tag_name || github.event.inputs.version || '' }}.tar.gz
|
||||||
|
/tmp/rtl-build-${{ github.event.release.tag_name || github.event.inputs.version || '' }}.zip
|
||||||
|
@ -10,11 +10,11 @@ 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 });
|
||||||
}
|
}
|
||||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/delexpiredinvoice';
|
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/autoclean-once';
|
||||||
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: '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(201).json({ status: 'Cleaned Invoices: ' + body.autoclean.expiredinvoices.cleaned + ', Uncleaned Invoices: ' + body.autoclean.expiredinvoices.uncleaned });
|
||||||
}).catch((errRes) => {
|
}).catch((errRes) => {
|
||||||
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice Error', req.session.selectedNode);
|
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice 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,7 +83,7 @@ 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.settings.lnServerUrl + '/v1/disableOffer';
|
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableoffer';
|
||||||
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 Disabled', data: body });
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
|
||||||
|
@ -28,7 +28,7 @@ export const simplifyAllChannels = (selNode, channels) => {
|
|||||||
});
|
});
|
||||||
channelNodeIds = channelNodeIds.substring(1);
|
channelNodeIds = channelNodeIds.substring(1);
|
||||||
options.url = selNode.settings.lnServerUrl + '/nodes';
|
options.url = selNode.settings.lnServerUrl + '/nodes';
|
||||||
options.form = { nodeIds: channelNodeIds };
|
options.form = channelNodeIds;
|
||||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Node Ids to find alias', data: channelNodeIds });
|
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Node Ids to find alias', data: channelNodeIds });
|
||||||
return request.post(options).then((nodes) => {
|
return request.post(options).then((nodes) => {
|
||||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
|
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
|
||||||
|
@ -155,47 +155,6 @@ export const postChannel = (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 postTransactions = (req, res, next) => {
|
|
||||||
const { paymentReq, paymentAmount, feeLimit, outgoingChannel, allowSelfPayment, lastHopPubkey } = req.body;
|
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sending Payment..' });
|
|
||||||
options = common.getOptions(req);
|
|
||||||
if (options.error) {
|
|
||||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
|
||||||
}
|
|
||||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/transaction-stream';
|
|
||||||
options.form = { payment_request: paymentReq };
|
|
||||||
if (paymentAmount) {
|
|
||||||
options.form.amt = paymentAmount;
|
|
||||||
}
|
|
||||||
if (feeLimit) {
|
|
||||||
options.form.fee_limit = feeLimit;
|
|
||||||
}
|
|
||||||
if (outgoingChannel) {
|
|
||||||
options.form.outgoing_chan_id = outgoingChannel;
|
|
||||||
}
|
|
||||||
if (allowSelfPayment) {
|
|
||||||
options.form.allow_self_payment = allowSelfPayment;
|
|
||||||
}
|
|
||||||
if (lastHopPubkey) {
|
|
||||||
options.form.last_hop_pubkey = Buffer.from(lastHopPubkey, 'hex').toString('base64');
|
|
||||||
}
|
|
||||||
options.form = JSON.stringify(options.form);
|
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
|
|
||||||
request.post(options).then((body) => {
|
|
||||||
body = body.result ? body.result : body;
|
|
||||||
if (body.payment_error) {
|
|
||||||
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
|
||||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Payment Sent', data: body });
|
|
||||||
res.status(201).json(body);
|
|
||||||
}
|
|
||||||
}).catch((errRes) => {
|
|
||||||
const err = common.handleError(errRes, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
|
||||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
export const closeChannel = (req, res, next) => {
|
export const closeChannel = (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
||||||
|
@ -97,3 +97,33 @@ export const paymentLookup = (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 sendPayment = (req, res, next) => {
|
||||||
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Payment..' });
|
||||||
|
options = common.getOptions(req);
|
||||||
|
if (options.error) {
|
||||||
|
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||||
|
}
|
||||||
|
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/router/send';
|
||||||
|
if (req.body.last_hop_pubkey) {
|
||||||
|
req.body.last_hop_pubkey = Buffer.from(req.body.last_hop_pubkey, 'hex').toString('base64');
|
||||||
|
}
|
||||||
|
req.body.amp = req.body.amp ?? false;
|
||||||
|
req.body.timeout_seconds = req.body.timeout_seconds || 600;
|
||||||
|
options.form = JSON.stringify(req.body);
|
||||||
|
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment Options', data: options.form });
|
||||||
|
request.post(options).then((body) => {
|
||||||
|
const results = body.split('\n').filter(Boolean).map((jsonString) => JSON.parse(jsonString));
|
||||||
|
body = results.length > 0 ? results[results.length - 1] : { result: { status: 'UNKNOWN' } };
|
||||||
|
if (body.result.status === 'FAILED') {
|
||||||
|
const err = common.handleError(common.titleCase(body.result.failure_reason.replace(/_/g, ' ').replace('FAILURE REASON ', '')), 'Payments', 'Send Payment Error', req.session.selectedNode);
|
||||||
|
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||||
|
}
|
||||||
|
if (body.result.status === 'SUCCEEDED') {
|
||||||
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body.result });
|
||||||
|
res.status(201).json(body.result);
|
||||||
|
}
|
||||||
|
}).catch((errRes) => {
|
||||||
|
const err = common.handleError(errRes, 'Payments', 'Send Payment Error', req.session.selectedNode);
|
||||||
|
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,13 +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 { getAllChannels, getPendingChannels, getClosedChannels, postChannel, postTransactions, closeChannel, postChanPolicy } from '../../controllers/lnd/channels.js';
|
import { getAllChannels, getPendingChannels, getClosedChannels, postChannel, closeChannel, postChanPolicy } from '../../controllers/lnd/channels.js';
|
||||||
const router = Router();
|
const router = Router();
|
||||||
router.get('/', isAuthenticated, getAllChannels);
|
router.get('/', isAuthenticated, getAllChannels);
|
||||||
router.get('/pending', isAuthenticated, getPendingChannels);
|
router.get('/pending', isAuthenticated, getPendingChannels);
|
||||||
router.get('/closed', isAuthenticated, getClosedChannels);
|
router.get('/closed', isAuthenticated, getClosedChannels);
|
||||||
router.post('/', isAuthenticated, postChannel);
|
router.post('/', isAuthenticated, postChannel);
|
||||||
router.post('/transactions', isAuthenticated, postTransactions);
|
|
||||||
router.delete('/:channelPoint', isAuthenticated, closeChannel);
|
router.delete('/:channelPoint', isAuthenticated, closeChannel);
|
||||||
router.post('/chanPolicy', isAuthenticated, postChanPolicy);
|
router.post('/chanPolicy', isAuthenticated, postChanPolicy);
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -1,11 +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 { decodePayment, decodePayments, getPayments, getAllLightningTransactions, paymentLookup } from '../../controllers/lnd/payments.js';
|
import { decodePayment, decodePayments, getPayments, getAllLightningTransactions, paymentLookup, sendPayment } from '../../controllers/lnd/payments.js';
|
||||||
const router = Router();
|
const router = Router();
|
||||||
router.get('/', isAuthenticated, getPayments);
|
router.get('/', isAuthenticated, getPayments);
|
||||||
router.get('/alltransactions', isAuthenticated, getAllLightningTransactions);
|
router.get('/alltransactions', isAuthenticated, getAllLightningTransactions);
|
||||||
router.get('/decode/:payRequest', isAuthenticated, decodePayment);
|
router.get('/decode/:payRequest', isAuthenticated, decodePayment);
|
||||||
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
|
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
|
||||||
router.post('/', isAuthenticated, decodePayments);
|
router.post('/', isAuthenticated, decodePayments);
|
||||||
|
router.post('/send', isAuthenticated, sendPayment);
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -395,7 +395,8 @@ export class CommonService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
this.readCookie = () => {
|
this.readCookie = (config) => {
|
||||||
|
this.appConfig.SSO = config.SSO;
|
||||||
const exists = fs.existsSync(this.appConfig.SSO.rtlCookiePath);
|
const exists = fs.existsSync(this.appConfig.SSO.rtlCookiePath);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
try {
|
try {
|
||||||
|
@ -146,7 +146,7 @@ export class ConfigService {
|
|||||||
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
|
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
|
||||||
this.common.nodes[idx].index = node.index;
|
this.common.nodes[idx].index = node.index;
|
||||||
this.common.nodes[idx].lnNode = node.lnNode;
|
this.common.nodes[idx].lnNode = node.lnNode;
|
||||||
this.common.nodes[idx].lnImplementation = (process?.env?.lnImplementation) ? process?.env?.lnImplementation : node.lnImplementation ? node.lnImplementation : 'LND';
|
this.common.nodes[idx].lnImplementation = (process?.env?.LN_IMPLEMENTATION) ? process?.env?.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
|
||||||
if (this.common.nodes[idx].lnImplementation === 'CLT') {
|
if (this.common.nodes[idx].lnImplementation === 'CLT') {
|
||||||
this.common.nodes[idx].lnImplementation = 'CLN';
|
this.common.nodes[idx].lnImplementation = 'CLN';
|
||||||
}
|
}
|
||||||
@ -343,7 +343,7 @@ export class ConfigService {
|
|||||||
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
|
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.common.readCookie();
|
this.common.readCookie(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -74,6 +74,7 @@ docker build -t rtl:${RTL_VERSION} -f dockerfiles/Dockerfile .
|
|||||||
Create an environment file with your required configurations. Sample .env:
|
Create an environment file with your required configurations. Sample .env:
|
||||||
```
|
```
|
||||||
RTL_CONFIG_PATH=/RTLConfig
|
RTL_CONFIG_PATH=/RTLConfig
|
||||||
|
LN_IMPLEMENTATION=LND
|
||||||
MACAROON_PATH=/LNDMacaroon
|
MACAROON_PATH=/LNDMacaroon
|
||||||
LN_SERVER_URL=https://host.docker.internal:8080
|
LN_SERVER_URL=https://host.docker.internal:8080
|
||||||
|
|
||||||
|
1
frontend/17.6fa7154eb6e447e2.js
Normal file
1
frontend/17.6fa7154eb6e447e2.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/190.03f035c34a56c8be.js
Normal file
1
frontend/190.03f035c34a56c8be.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/193.0e1a81316bbc29da.js
Normal file
1
frontend/193.0e1a81316bbc29da.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,27 @@
|
|||||||
@angular/animations
|
@angular/animations
|
||||||
MIT
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
@angular/cdk
|
@angular/cdk
|
||||||
MIT
|
MIT
|
||||||
@ -28,15 +50,81 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
@angular/common
|
@angular/common
|
||||||
MIT
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
@angular/core
|
@angular/core
|
||||||
MIT
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
@angular/flex-layout
|
@angular/flex-layout
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
@angular/forms
|
@angular/forms
|
||||||
MIT
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
@angular/material
|
@angular/material
|
||||||
MIT
|
MIT
|
||||||
@ -65,9 +153,53 @@ THE SOFTWARE.
|
|||||||
|
|
||||||
@angular/platform-browser
|
@angular/platform-browser
|
||||||
MIT
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
@angular/router
|
@angular/router
|
||||||
MIT
|
MIT
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2010-2024 Google LLC. https://angular.dev/license
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
@babel/runtime
|
@babel/runtime
|
||||||
MIT
|
MIT
|
||||||
@ -1565,9 +1697,6 @@ THE SOFTWARE.
|
|||||||
elliptic
|
elliptic
|
||||||
MIT
|
MIT
|
||||||
|
|
||||||
encode-utf8
|
|
||||||
MIT
|
|
||||||
|
|
||||||
events
|
events
|
||||||
MIT
|
MIT
|
||||||
MIT
|
MIT
|
||||||
|
1
frontend/853.50b06a24091d386f.js
Normal file
1
frontend/853.50b06a24091d386f.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/main.6e2567250e1fcca3.js
Normal file
1
frontend/main.6e2567250e1fcca3.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/polyfills.2fc68655e9a361e8.js
Normal file
1
frontend/polyfills.2fc68655e9a361e8.js
Normal file
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
(()=>{"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],c=!0,l=0;l<t.length;l++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,f<a&&(a=f));if(c){e.splice(n--,1);var d=i();void 0!==d&&(o=d)}}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+"."+{17:"d00d31d08d7bad32",190:"88ca997666a3998a",193:"b1206fbf24aa1327",853:"c4ce8a9a0bef2bb7"}[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,c;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==o+f){a=u;break}}a||(c=!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 s=(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(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&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={121: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(121!=i){var a=new Promise((u,s)=>n=e[i]=[u,s]);f.push(n[2]=a);var c=r.p+r.u(i),l=new Error;r.l(c,u=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+i+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var o=(i,f)=>{var l,d,[n,a,c]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(i&&i(f);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[d]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();
|
|
1
frontend/runtime.8f43557e02af97f0.js
Normal file
1
frontend/runtime.8f43557e02af97f0.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
(()=>{"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,d)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,d]=e[n],c=!0,o=0;o<t.length;o++)(!1&d||a>=d)&&Object.keys(r.O).every(b=>r.O[b](t[o]))?t.splice(o--,1):(c=!1,d<a&&(a=d));if(c){e.splice(n--,1);var u=i();void 0!==u&&(f=u)}}return f}d=d||0;for(var n=e.length;n>0&&e[n-1][2]>d;n--)e[n]=e[n-1];e[n]=[t,i,d]},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+"."+{17:"6fa7154eb6e447e2",190:"03f035c34a56c8be",193:"0e1a81316bbc29da",853:"50b06a24091d386f"}[e]+".js",r.miniCssF=e=>{},r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),(()=>{var e={},f="RTLApp:";r.l=(t,i,d,n)=>{if(e[t])e[t].push(i);else{var a,c;if(void 0!==d)for(var o=document.getElementsByTagName("script"),u=0;u<o.length;u++){var l=o[u];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==f+d){a=l;break}}a||(c=!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+d),a.src=r.tu(t)),e[t]=[i];var s=(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(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&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={121:0};r.f.j=(i,d)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)d.push(n[2]);else if(121!=i){var a=new Promise((l,s)=>n=e[i]=[l,s]);d.push(n[2]=a);var c=r.p+r.u(i),o=new Error;r.l(c,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var s=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;o.message="Loading chunk "+i+" failed.\n("+s+": "+p+")",o.name="ChunkLoadError",o.type=s,o.request=p,n[1](o)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var f=(i,d)=>{var o,u,[n,a,c]=d,l=0;if(n.some(p=>0!==e[p])){for(o in a)r.o(a,o)&&(r.m[o]=a[o]);if(c)var s=c(r)}for(i&&i(d);l<n.length;l++)r.o(e,u=n[l])&&e[u]&&e[u][0](),e[u]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(f.bind(null,0)),t.push=f.bind(null,t.push.bind(t))})()})();
|
1
frontend/styles.6286abca7d059982.css
Normal file
1
frontend/styles.6286abca7d059982.css
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7371
package-lock.json
generated
7371
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "rtl",
|
"name": "rtl",
|
||||||
"version": "0.15.2-beta",
|
"version": "0.15.3-beta",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -9,11 +9,11 @@ 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 }); }
|
||||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/delexpiredinvoice';
|
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/autoclean-once';
|
||||||
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: '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(201).json({ status: 'Cleaned Invoices: ' + body.autoclean.expiredinvoices.cleaned + ', Uncleaned Invoices: ' + body.autoclean.expiredinvoices.uncleaned });
|
||||||
}).catch((errRes) => {
|
}).catch((errRes) => {
|
||||||
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice Error', req.session.selectedNode);
|
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice 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 });
|
||||||
|
@ -82,7 +82,7 @@ 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.settings.lnServerUrl + '/v1/disableOffer';
|
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableoffer';
|
||||||
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 Disabled', data: body });
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
|
||||||
|
@ -31,7 +31,7 @@ export const simplifyAllChannels = (selNode: SelectedNode, channels) => {
|
|||||||
});
|
});
|
||||||
channelNodeIds = channelNodeIds.substring(1);
|
channelNodeIds = channelNodeIds.substring(1);
|
||||||
options.url = selNode.settings.lnServerUrl + '/nodes';
|
options.url = selNode.settings.lnServerUrl + '/nodes';
|
||||||
options.form = { nodeIds: channelNodeIds };
|
options.form = channelNodeIds;
|
||||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Node Ids to find alias', data: channelNodeIds });
|
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Node Ids to find alias', data: channelNodeIds });
|
||||||
return request.post(options).then((nodes) => {
|
return request.post(options).then((nodes) => {
|
||||||
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
|
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
|
||||||
|
@ -155,37 +155,6 @@ export const postChannel = (req, res, next) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const postTransactions = (req, res, next) => {
|
|
||||||
const { paymentReq, paymentAmount, feeLimit, outgoingChannel, allowSelfPayment, lastHopPubkey } = req.body;
|
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sending Payment..' });
|
|
||||||
options = common.getOptions(req);
|
|
||||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
|
||||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/transaction-stream';
|
|
||||||
options.form = { payment_request: paymentReq };
|
|
||||||
if (paymentAmount) {
|
|
||||||
options.form.amt = paymentAmount;
|
|
||||||
}
|
|
||||||
if (feeLimit) { options.form.fee_limit = feeLimit; }
|
|
||||||
if (outgoingChannel) { options.form.outgoing_chan_id = outgoingChannel; }
|
|
||||||
if (allowSelfPayment) { options.form.allow_self_payment = allowSelfPayment; }
|
|
||||||
if (lastHopPubkey) { options.form.last_hop_pubkey = Buffer.from(lastHopPubkey, 'hex').toString('base64'); }
|
|
||||||
options.form = JSON.stringify(options.form);
|
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
|
|
||||||
request.post(options).then((body) => {
|
|
||||||
body = body.result ? body.result : body;
|
|
||||||
if (body.payment_error) {
|
|
||||||
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
|
||||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
|
||||||
} else {
|
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Payment Sent', data: body });
|
|
||||||
res.status(201).json(body);
|
|
||||||
}
|
|
||||||
}).catch((errRes) => {
|
|
||||||
const err = common.handleError(errRes, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
|
||||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const closeChannel = (req, res, next) => {
|
export const closeChannel = (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
||||||
|
@ -96,3 +96,32 @@ export const paymentLookup = (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 sendPayment = (req, res, next) => {
|
||||||
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Sending Payment..' });
|
||||||
|
options = common.getOptions(req);
|
||||||
|
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||||
|
options.url = req.session.selectedNode.settings.lnServerUrl + '/v2/router/send';
|
||||||
|
if (req.body.last_hop_pubkey) {
|
||||||
|
req.body.last_hop_pubkey = Buffer.from(req.body.last_hop_pubkey, 'hex').toString('base64');
|
||||||
|
}
|
||||||
|
req.body.amp = req.body.amp ?? false;
|
||||||
|
req.body.timeout_seconds = req.body.timeout_seconds || 600;
|
||||||
|
options.form = JSON.stringify(req.body);
|
||||||
|
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment Options', data: options.form });
|
||||||
|
request.post(options).then((body) => {
|
||||||
|
const results = body.split('\n').filter(Boolean).map((jsonString) => JSON.parse(jsonString));
|
||||||
|
body = results.length > 0 ? results[results.length - 1] : { result: { status: 'UNKNOWN' } };
|
||||||
|
if (body.result.status === 'FAILED') {
|
||||||
|
const err = common.handleError(common.titleCase(body.result.failure_reason.replace(/_/g, ' ').replace('FAILURE REASON ', '')), 'Payments', 'Send Payment Error', req.session.selectedNode);
|
||||||
|
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||||
|
}
|
||||||
|
if (body.result.status === 'SUCCEEDED') {
|
||||||
|
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent', data: body.result });
|
||||||
|
res.status(201).json(body.result);
|
||||||
|
}
|
||||||
|
}).catch((errRes) => {
|
||||||
|
const err = common.handleError(errRes, 'Payments', 'Send Payment Error', req.session.selectedNode);
|
||||||
|
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||||
|
});
|
||||||
|
};
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 { getAllChannels, getPendingChannels, getClosedChannels, postChannel, postTransactions, closeChannel, postChanPolicy } from '../../controllers/lnd/channels.js';
|
import { getAllChannels, getPendingChannels, getClosedChannels, postChannel, closeChannel, postChanPolicy } from '../../controllers/lnd/channels.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -9,7 +9,6 @@ router.get('/', isAuthenticated, getAllChannels);
|
|||||||
router.get('/pending', isAuthenticated, getPendingChannels);
|
router.get('/pending', isAuthenticated, getPendingChannels);
|
||||||
router.get('/closed', isAuthenticated, getClosedChannels);
|
router.get('/closed', isAuthenticated, getClosedChannels);
|
||||||
router.post('/', isAuthenticated, postChannel);
|
router.post('/', isAuthenticated, postChannel);
|
||||||
router.post('/transactions', isAuthenticated, postTransactions);
|
|
||||||
router.delete('/:channelPoint', isAuthenticated, closeChannel);
|
router.delete('/:channelPoint', isAuthenticated, closeChannel);
|
||||||
router.post('/chanPolicy', isAuthenticated, postChanPolicy);
|
router.post('/chanPolicy', isAuthenticated, postChanPolicy);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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 { decodePayment, decodePayments, getPayments, getAllLightningTransactions, paymentLookup } from '../../controllers/lnd/payments.js';
|
import { decodePayment, decodePayments, getPayments, getAllLightningTransactions, paymentLookup, sendPayment } from '../../controllers/lnd/payments.js';
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
@ -10,5 +10,6 @@ router.get('/alltransactions', isAuthenticated, getAllLightningTransactions);
|
|||||||
router.get('/decode/:payRequest', isAuthenticated, decodePayment);
|
router.get('/decode/:payRequest', isAuthenticated, decodePayment);
|
||||||
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
|
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
|
||||||
router.post('/', isAuthenticated, decodePayments);
|
router.post('/', isAuthenticated, decodePayments);
|
||||||
|
router.post('/send', isAuthenticated, sendPayment);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
@ -413,7 +413,8 @@ export class CommonService {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
public readCookie = () => {
|
public readCookie = (config) => {
|
||||||
|
this.appConfig.SSO = config.SSO;
|
||||||
const exists = fs.existsSync(this.appConfig.SSO.rtlCookiePath);
|
const exists = fs.existsSync(this.appConfig.SSO.rtlCookiePath);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
try {
|
try {
|
||||||
|
@ -150,7 +150,7 @@ export class ConfigService {
|
|||||||
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
|
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
|
||||||
this.common.nodes[idx].index = node.index;
|
this.common.nodes[idx].index = node.index;
|
||||||
this.common.nodes[idx].lnNode = node.lnNode;
|
this.common.nodes[idx].lnNode = node.lnNode;
|
||||||
this.common.nodes[idx].lnImplementation = (process?.env?.lnImplementation) ? process?.env?.lnImplementation : node.lnImplementation ? node.lnImplementation : 'LND';
|
this.common.nodes[idx].lnImplementation = (process?.env?.LN_IMPLEMENTATION) ? process?.env?.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
|
||||||
if (this.common.nodes[idx].lnImplementation === 'CLT') { this.common.nodes[idx].lnImplementation = 'CLN'; }
|
if (this.common.nodes[idx].lnImplementation === 'CLT') { this.common.nodes[idx].lnImplementation = 'CLN'; }
|
||||||
switch (this.common.nodes[idx].lnImplementation) {
|
switch (this.common.nodes[idx].lnImplementation) {
|
||||||
case 'CLN':
|
case 'CLN':
|
||||||
@ -321,7 +321,7 @@ export class ConfigService {
|
|||||||
if (!config.SSO.rtlCookiePath || config.SSO.rtlCookiePath.trim() === '') {
|
if (!config.SSO.rtlCookiePath || config.SSO.rtlCookiePath.trim() === '') {
|
||||||
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
|
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
|
||||||
} else {
|
} else {
|
||||||
this.common.readCookie();
|
this.common.readCookie(config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -6,7 +6,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content class="padding-gap-x-large">
|
<mat-card-content class="padding-gap-x-large" fxLayout="column">
|
||||||
|
<div *ngIf="recommendedFee.minimumFee" fxFlex="100" class="alert alert-info mb-2">
|
||||||
|
<fa-icon class="mr-1 alert-icon" [icon]="faInfoCircle" />
|
||||||
|
<span fxLayout="column" fxFlex="100">
|
||||||
|
<div>Fee rates recommended by mempool (sat/vByte):</div>
|
||||||
|
<span class="pr-2" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
|
||||||
|
<span>- High: {{recommendedFee.fastestFee || 'Unknown'}}</span>
|
||||||
|
<span>- Medium: {{recommendedFee.halfHourFee || 'Unknown'}}</span>
|
||||||
|
<span>- Low: {{recommendedFee.hourFee || 'Unknown'}}</span>
|
||||||
|
<span>- Economy: {{recommendedFee.economyFee || 'Unknown'}}</span>
|
||||||
|
<span>- Minimum: {{recommendedFee.minimumFee || 'Unknown'}}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<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>
|
||||||
|
@ -8,7 +8,7 @@ import { Actions } from '@ngrx/effects';
|
|||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MatStepper } from '@angular/material/stepper';
|
import { MatStepper } from '@angular/material/stepper';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
import * as sha256 from 'sha256';
|
import * as sha256 from 'sha256';
|
||||||
|
|
||||||
import { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
|
import { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
|
||||||
@ -25,6 +25,8 @@ import { setChannelTransaction } from '../../store/cln.actions';
|
|||||||
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
|
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
|
||||||
import { clnNodeInformation, utxoBalances } from '../../store/cln.selector';
|
import { clnNodeInformation, utxoBalances } from '../../store/cln.selector';
|
||||||
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
|
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
|
||||||
|
import { DataService } from 'src/app/shared/services/data.service';
|
||||||
|
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'rtl-cln-on-chain-send-modal',
|
selector: 'rtl-cln-on-chain-send-modal',
|
||||||
@ -37,6 +39,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
|
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
|
||||||
@ViewChild('stepper', { static: false }) stepper: MatStepper;
|
@ViewChild('stepper', { static: false }) stepper: MatStepper;
|
||||||
public faExclamationTriangle = faExclamationTriangle;
|
public faExclamationTriangle = faExclamationTriangle;
|
||||||
|
public faInfoCircle = faInfoCircle;
|
||||||
public sweepAll = false;
|
public sweepAll = false;
|
||||||
public selNode: Node | null;
|
public selNode: Node | null;
|
||||||
public appConfig: RTLConfiguration;
|
public appConfig: RTLConfiguration;
|
||||||
@ -65,6 +68,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
public advancedTitle = 'Advanced Options';
|
public advancedTitle = 'Advanced Options';
|
||||||
public flgValidated = false;
|
public flgValidated = false;
|
||||||
public flgEditable = true;
|
public flgEditable = true;
|
||||||
|
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
|
||||||
public passwordFormLabel = 'Authenticate with your RTL password';
|
public passwordFormLabel = 'Authenticate with your RTL password';
|
||||||
public sendFundFormLabel = 'Sweep funds';
|
public sendFundFormLabel = 'Sweep funds';
|
||||||
public confirmFormLabel = 'Confirm sweep';
|
public confirmFormLabel = 'Confirm sweep';
|
||||||
@ -74,12 +78,13 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
confirmFormGroup: UntypedFormGroup;
|
confirmFormGroup: UntypedFormGroup;
|
||||||
public screenSize = '';
|
public screenSize = '';
|
||||||
public screenSizeEnum = ScreenSizeEnum;
|
public screenSizeEnum = ScreenSizeEnum;
|
||||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<CLNOnChainSendModalComponent>,
|
public dialogRef: MatDialogRef<CLNOnChainSendModalComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: CLNOnChainSendFunds,
|
@Inject(MAT_DIALOG_DATA) public data: CLNOnChainSendFunds,
|
||||||
private logger: LoggerService,
|
private logger: LoggerService,
|
||||||
|
private dataService: DataService,
|
||||||
private store: Store<RTLState>,
|
private store: Store<RTLState>,
|
||||||
private commonService: CommonService,
|
private commonService: CommonService,
|
||||||
private decimalPipe: DecimalPipe,
|
private decimalPipe: DecimalPipe,
|
||||||
@ -91,6 +96,13 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
|
||||||
|
next: (rfRes: RecommendedFeeRates) => {
|
||||||
|
this.recommendedFee = rfRes;
|
||||||
|
}, error: (err) => {
|
||||||
|
this.logger.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.sweepAll = this.data.sweepAll;
|
this.sweepAll = this.data.sweepAll;
|
||||||
this.passwordFormGroup = this.formBuilder.group({
|
this.passwordFormGroup = this.formBuilder.group({
|
||||||
hiddenPassword: ['', [Validators.required]],
|
hiddenPassword: ['', [Validators.required]],
|
||||||
@ -104,7 +116,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
minConfValue: [{ value: null, disabled: true }]
|
minConfValue: [{ value: null, disabled: true }]
|
||||||
});
|
});
|
||||||
this.confirmFormGroup = this.formBuilder.group({});
|
this.confirmFormGroup = this.formBuilder.group({});
|
||||||
this.sendFundFormGroup.controls.flgMinConf.valueChanges.pipe(takeUntil(this.unSubs[0])).subscribe((flg) => {
|
this.sendFundFormGroup.controls.flgMinConf.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((flg) => {
|
||||||
if (flg) {
|
if (flg) {
|
||||||
this.sendFundFormGroup.controls.selFeeRate.disable();
|
this.sendFundFormGroup.controls.selFeeRate.disable();
|
||||||
this.sendFundFormGroup.controls.selFeeRate.setValue(null);
|
this.sendFundFormGroup.controls.selFeeRate.setValue(null);
|
||||||
@ -121,7 +133,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
this.sendFundFormGroup.controls.minConfValue.setErrors(null);
|
this.sendFundFormGroup.controls.minConfValue.setErrors(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.sendFundFormGroup.controls.selFeeRate.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((feeRate) => {
|
this.sendFundFormGroup.controls.selFeeRate.valueChanges.pipe(takeUntil(this.unSubs[2])).subscribe((feeRate) => {
|
||||||
this.sendFundFormGroup.controls.customFeeRate.setValue(null);
|
this.sendFundFormGroup.controls.customFeeRate.setValue(null);
|
||||||
this.sendFundFormGroup.controls.customFeeRate.reset();
|
this.sendFundFormGroup.controls.customFeeRate.reset();
|
||||||
if (feeRate === 'customperkb' && !this.sendFundFormGroup.controls.flgMinConf.value) {
|
if (feeRate === 'customperkb' && !this.sendFundFormGroup.controls.flgMinConf.value) {
|
||||||
@ -130,23 +142,23 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
this.sendFundFormGroup.controls.customFeeRate.setValidators(null);
|
this.sendFundFormGroup.controls.customFeeRate.setValidators(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
combineLatest([this.store.select(rootSelectedNode), this.store.select(rootAppConfig)]).pipe(takeUntil(this.unSubs[1])).
|
combineLatest([this.store.select(rootSelectedNode), this.store.select(rootAppConfig)]).pipe(takeUntil(this.unSubs[3])).
|
||||||
subscribe(([selNode, appConfig]) => {
|
subscribe(([selNode, appConfig]) => {
|
||||||
this.fiatConversion = selNode.settings.fiatConversion;
|
this.fiatConversion = selNode.settings.fiatConversion;
|
||||||
this.amountUnits = selNode.settings.currencyUnits;
|
this.amountUnits = selNode.settings.currencyUnits;
|
||||||
this.appConfig = appConfig;
|
this.appConfig = appConfig;
|
||||||
});
|
});
|
||||||
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[2])).
|
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[4])).
|
||||||
subscribe((nodeInfo: GetInfo) => {
|
subscribe((nodeInfo: GetInfo) => {
|
||||||
this.information = nodeInfo;
|
this.information = nodeInfo;
|
||||||
});
|
});
|
||||||
this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[3])).
|
this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[5])).
|
||||||
subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
|
subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
|
||||||
this.utxos = this.commonService.sortAscByKey(utxoBalancesSeletor.utxos?.filter((utxo) => utxo.status === 'confirmed'), 'value');
|
this.utxos = this.commonService.sortAscByKey(utxoBalancesSeletor.utxos?.filter((utxo) => utxo.status === 'confirmed'), 'value');
|
||||||
this.logger.info(utxoBalancesSeletor);
|
this.logger.info(utxoBalancesSeletor);
|
||||||
});
|
});
|
||||||
this.actions.pipe(
|
this.actions.pipe(
|
||||||
takeUntil(this.unSubs[4]),
|
takeUntil(this.unSubs[6]),
|
||||||
filter((action) => action.type === CLNActions.UPDATE_API_CALL_STATUS_CLN || action.type === CLNActions.SET_CHANNEL_TRANSACTION_RES_CLN)).
|
filter((action) => action.type === CLNActions.UPDATE_API_CALL_STATUS_CLN || action.type === CLNActions.SET_CHANNEL_TRANSACTION_RES_CLN)).
|
||||||
subscribe((action: any) => {
|
subscribe((action: any) => {
|
||||||
if (action.type === CLNActions.SET_CHANNEL_TRANSACTION_RES_CLN) {
|
if (action.type === CLNActions.SET_CHANNEL_TRANSACTION_RES_CLN) {
|
||||||
@ -219,7 +231,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
if (this.transaction.satoshi && this.transaction.satoshi !== 'all' && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
|
if (this.transaction.satoshi && this.transaction.satoshi !== 'all' && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
|
||||||
this.commonService.convertCurrency(+this.transaction.satoshi, 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[7])).
|
||||||
subscribe({
|
subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.transaction.satoshi = data[CurrencyUnitEnum.SATS];
|
this.transaction.satoshi = data[CurrencyUnitEnum.SATS];
|
||||||
@ -307,7 +319,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
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.satoshi && this.selAmountUnit !== event.value) {
|
if (this.transaction.satoshi && this.selAmountUnit !== event.value) {
|
||||||
this.commonService.convertCurrency(+this.transaction.satoshi, 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[8])).
|
||||||
subscribe({
|
subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.selAmountUnit = event.value;
|
this.selAmountUnit = event.value;
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
<div fxFlex="64" fxLayout="row" fxLayoutAlign="space-between center">
|
<div fxFlex="64" fxLayout="row" fxLayoutAlign="space-between center">
|
||||||
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '40' : '100'">
|
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '40' : '100'">
|
||||||
<mat-label>Fee Rate</mat-label>
|
<mat-label>Fee Rate</mat-label>
|
||||||
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate" (selectionChange)="onSelFeeRateChanged($event)">
|
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate">
|
||||||
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
|
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
|
||||||
{{feeRateType.feeRateType}}
|
{{feeRateType.feeRateType}}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
|
@ -168,6 +168,13 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.advancedTitle = 'Advanced Options';
|
this.advancedTitle = 'Advanced Options';
|
||||||
|
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
|
||||||
|
next: (rfRes: RecommendedFeeRates) => {
|
||||||
|
this.recommendedFee = rfRes;
|
||||||
|
}, error: (err) => {
|
||||||
|
this.logger.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,19 +216,6 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
|
|||||||
this.store.dispatch(saveNewChannel({ payload: newChannel }));
|
this.store.dispatch(saveNewChannel({ payload: newChannel }));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelFeeRateChanged(event) {
|
|
||||||
this.customFeeRate = null;
|
|
||||||
if (event.value === 'customperkb') {
|
|
||||||
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
|
|
||||||
next: (rfRes: RecommendedFeeRates) => {
|
|
||||||
this.recommendedFee = rfRes;
|
|
||||||
}, error: (err) => {
|
|
||||||
this.logger.error(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.unSubs.forEach((completeSub) => {
|
this.unSubs.forEach((completeSub) => {
|
||||||
completeSub.next(<any>null);
|
completeSub.next(<any>null);
|
||||||
|
@ -14,7 +14,7 @@ import { WebSocketClientService } from '../../shared/services/web-socket.service
|
|||||||
import { ErrorMessageComponent } from '../../shared/components/data-modal/error-message/error-message.component';
|
import { ErrorMessageComponent } from '../../shared/components/data-modal/error-message/error-message.component';
|
||||||
import { CLNInvoiceInformationComponent } from '../transactions/invoices/invoice-information-modal/invoice-information.component';
|
import { CLNInvoiceInformationComponent } from '../transactions/invoices/invoice-information-modal/invoice-information.component';
|
||||||
import { GetInfo, Payment, FeeRates, ListInvoices, Invoice, Peer, OnChain, QueryRoutes, SaveChannel, GetNewAddress, DetachPeer, UpdateChannel, CloseChannel, SendPayment, GetQueryRoutes, ChannelLookup, Channel, OfferInvoice, Offer } from '../../shared/models/clnModels';
|
import { GetInfo, Payment, FeeRates, ListInvoices, Invoice, Peer, OnChain, QueryRoutes, SaveChannel, GetNewAddress, DetachPeer, UpdateChannel, CloseChannel, SendPayment, GetQueryRoutes, ChannelLookup, Channel, OfferInvoice, Offer } from '../../shared/models/clnModels';
|
||||||
import { API_URL, API_END_POINTS, AlertTypeEnum, APICallStatusEnum, UI_MESSAGES, CLNWSEventTypeEnum, CLNActions, RTLActions, CLNForwardingEventsStatusEnum } from '../../shared/services/consts-enums-functions';
|
import { API_URL, API_END_POINTS, SECS_IN_YEAR, AlertTypeEnum, APICallStatusEnum, UI_MESSAGES, CLNWSEventTypeEnum, CLNActions, RTLActions, CLNForwardingEventsStatusEnum } from '../../shared/services/consts-enums-functions';
|
||||||
import { closeAllDialogs, closeSpinner, logout, openAlert, openSnackBar, openSpinner, setApiUrl, setNodeData } from '../../store/rtl.actions';
|
import { closeAllDialogs, closeSpinner, logout, openAlert, openSnackBar, openSpinner, setApiUrl, setNodeData } from '../../store/rtl.actions';
|
||||||
|
|
||||||
import { RTLState } from '../../store/rtl.state';
|
import { RTLState } from '../../store/rtl.state';
|
||||||
@ -114,8 +114,8 @@ export class CLNEffects implements OnDestroy {
|
|||||||
}),
|
}),
|
||||||
catchError((err) => {
|
catchError((err) => {
|
||||||
const code = this.commonService.extractErrorCode(err);
|
const code = this.commonService.extractErrorCode(err);
|
||||||
const msg = (code === 'ETIMEDOUT') ? 'Unable to Connect to Core Lightning Server.' : this.commonService.extractErrorMessage(err);
|
const msg = (code === 503) ? 'Unable to Connect to Core Lightning Server.' : this.commonService.extractErrorMessage(err);
|
||||||
this.router.navigate(['/login'], { state: { logoutReason: JSON.stringify(msg) } });
|
this.router.navigate(['/error'], { state: { errorCode: code, errorMessage: msg } });
|
||||||
this.handleErrorWithoutAlert('FetchInfo', UI_MESSAGES.GET_NODE_INFO, 'Fetching Node Info Failed.', { status: code, error: msg });
|
this.handleErrorWithoutAlert('FetchInfo', UI_MESSAGES.GET_NODE_INFO, 'Fetching Node Info Failed.', { status: code, error: msg });
|
||||||
return of({ type: RTLActions.VOID });
|
return of({ type: RTLActions.VOID });
|
||||||
})
|
})
|
||||||
@ -608,12 +608,12 @@ export class CLNEffects implements OnDestroy {
|
|||||||
ofType(CLNActions.DELETE_EXPIRED_INVOICE_CLN),
|
ofType(CLNActions.DELETE_EXPIRED_INVOICE_CLN),
|
||||||
mergeMap((action: { type: string, payload: number }) => {
|
mergeMap((action: { type: string, payload: number }) => {
|
||||||
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.DELETE_INVOICE }));
|
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.DELETE_INVOICE }));
|
||||||
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.INVOICES_API + '/delete', { maxexpiry: action.payload }).
|
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.INVOICES_API + '/delete', { subsystem: 'expiredinvoices', age: SECS_IN_YEAR }).
|
||||||
pipe(
|
pipe(
|
||||||
map((postRes: any) => {
|
map((postRes: any) => {
|
||||||
this.logger.info(postRes);
|
this.logger.info(postRes);
|
||||||
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.DELETE_INVOICE }));
|
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.DELETE_INVOICE }));
|
||||||
this.store.dispatch(openSnackBar({ payload: 'Invoices Deleted Successfully!' }));
|
this.store.dispatch(openSnackBar({ payload: postRes.status }));
|
||||||
return { type: CLNActions.FETCH_INVOICES_CLN };
|
return { type: CLNActions.FETCH_INVOICES_CLN };
|
||||||
}),
|
}),
|
||||||
catchError((err: any) => {
|
catchError((err: any) => {
|
||||||
|
@ -6,7 +6,20 @@
|
|||||||
</div>
|
</div>
|
||||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content class="padding-gap-x-large">
|
<mat-card-content class="padding-gap-x-large" fxLayout="column">
|
||||||
|
<div *ngIf="recommendedFee.minimumFee" fxFlex="100" class="alert alert-info mb-2">
|
||||||
|
<fa-icon class="mr-1 alert-icon" [icon]="faInfoCircle" />
|
||||||
|
<span fxLayout="column" fxFlex="100">
|
||||||
|
<div>Fee rates recommended by mempool (sat/vByte):</div>
|
||||||
|
<span class="pr-2" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
|
||||||
|
<span>- High: {{recommendedFee.fastestFee || 'Unknown'}}</span>
|
||||||
|
<span>- Medium: {{recommendedFee.halfHourFee || 'Unknown'}}</span>
|
||||||
|
<span>- Low: {{recommendedFee.hourFee || 'Unknown'}}</span>
|
||||||
|
<span>- Economy: {{recommendedFee.economyFee || 'Unknown'}}</span>
|
||||||
|
<span>- Minimum: {{recommendedFee.minimumFee || 'Unknown'}}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
<form #form="ngForm" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()">
|
<form #form="ngForm" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start" 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>
|
||||||
|
@ -5,7 +5,7 @@ import { takeUntil, filter } from 'rxjs/operators';
|
|||||||
import { Store } from '@ngrx/store';
|
import { Store } from '@ngrx/store';
|
||||||
import { Actions } from '@ngrx/effects';
|
import { Actions } from '@ngrx/effects';
|
||||||
import { MatDialogRef } from '@angular/material/dialog';
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { Node } from '../../../shared/models/RTLconfig';
|
import { Node } from '../../../shared/models/RTLconfig';
|
||||||
import { GetInfo, OnChainBalance, SendPaymentOnChain } from '../../../shared/models/eclModels';
|
import { GetInfo, OnChainBalance, SendPaymentOnChain } from '../../../shared/models/eclModels';
|
||||||
@ -17,6 +17,8 @@ import { RTLState } from '../../../store/rtl.state';
|
|||||||
import { openSnackBar } from '../../../store/rtl.actions';
|
import { openSnackBar } from '../../../store/rtl.actions';
|
||||||
import { sendOnchainFunds } from '../../store/ecl.actions';
|
import { sendOnchainFunds } from '../../store/ecl.actions';
|
||||||
import { rootSelectedNode } from '../../../store/rtl.selector';
|
import { rootSelectedNode } from '../../../store/rtl.selector';
|
||||||
|
import { DataService } from 'src/app/shared/services/data.service';
|
||||||
|
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'rtl-ecl-on-chain-send-modal',
|
selector: 'rtl-ecl-on-chain-send-modal',
|
||||||
@ -27,6 +29,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
@ViewChild('form', { static: true }) form: any;
|
@ViewChild('form', { static: true }) form: any;
|
||||||
public faExclamationTriangle = faExclamationTriangle;
|
public faExclamationTriangle = faExclamationTriangle;
|
||||||
|
public faInfoCircle = faInfoCircle;
|
||||||
public selNode: Node | null;
|
public selNode: Node | null;
|
||||||
public addressTypes = [];
|
public addressTypes = [];
|
||||||
public selectedAddress = ADDRESS_TYPES[1];
|
public selectedAddress = ADDRESS_TYPES[1];
|
||||||
@ -41,19 +44,27 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
public currConvertorRate = {};
|
public currConvertorRate = {};
|
||||||
public unitConversionValue = 0;
|
public unitConversionValue = 0;
|
||||||
public currencyUnitFormats = CURRENCY_UNIT_FORMATS;
|
public currencyUnitFormats = CURRENCY_UNIT_FORMATS;
|
||||||
|
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
|
||||||
public amountError = 'Amount is Required.';
|
public amountError = 'Amount is Required.';
|
||||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||||
|
|
||||||
constructor(public dialogRef: MatDialogRef<ECLOnChainSendModalComponent>, private logger: LoggerService, private store: Store<RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions: Actions) { }
|
constructor(public dialogRef: MatDialogRef<ECLOnChainSendModalComponent>, private logger: LoggerService, private dataService: DataService, private store: Store<RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions: Actions) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[0])).subscribe((selNode) => {
|
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
|
||||||
|
next: (rfRes: RecommendedFeeRates) => {
|
||||||
|
this.recommendedFee = rfRes;
|
||||||
|
}, error: (err) => {
|
||||||
|
this.logger.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[1])).subscribe((selNode) => {
|
||||||
this.fiatConversion = selNode.settings.fiatConversion;
|
this.fiatConversion = selNode.settings.fiatConversion;
|
||||||
this.amountUnits = selNode.settings.currencyUnits;
|
this.amountUnits = selNode.settings.currencyUnits;
|
||||||
this.logger.info(selNode);
|
this.logger.info(selNode);
|
||||||
});
|
});
|
||||||
this.actions.pipe(
|
this.actions.pipe(
|
||||||
takeUntil(this.unSubs[1]),
|
takeUntil(this.unSubs[2]),
|
||||||
filter((action) => action.type === ECLActions.UPDATE_API_CALL_STATUS_ECL || action.type === ECLActions.SEND_ONCHAIN_FUNDS_RES_ECL)
|
filter((action) => action.type === ECLActions.UPDATE_API_CALL_STATUS_ECL || action.type === ECLActions.SEND_ONCHAIN_FUNDS_RES_ECL)
|
||||||
).
|
).
|
||||||
subscribe((action: any) => {
|
subscribe((action: any) => {
|
||||||
@ -74,7 +85,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
this.sendFundError = '';
|
this.sendFundError = '';
|
||||||
if (this.transaction.amount && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
|
if (this.transaction.amount && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
|
||||||
this.commonService.convertCurrency(this.transaction.amount, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
|
this.commonService.convertCurrency(this.transaction.amount, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
|
||||||
pipe(takeUntil(this.unSubs[2])).
|
pipe(takeUntil(this.unSubs[3])).
|
||||||
subscribe({
|
subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.transaction.amount = parseInt(data[CurrencyUnitEnum.SATS]);
|
this.transaction.amount = parseInt(data[CurrencyUnitEnum.SATS]);
|
||||||
@ -107,7 +118,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
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.amount && this.selAmountUnit !== event.value) {
|
if (this.transaction.amount && this.selAmountUnit !== event.value) {
|
||||||
this.commonService.convertCurrency(this.transaction.amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
this.commonService.convertCurrency(this.transaction.amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
||||||
pipe(takeUntil(this.unSubs[3])).
|
pipe(takeUntil(this.unSubs[4])).
|
||||||
subscribe({
|
subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.selAmountUnit = event.value;
|
this.selAmountUnit = event.value;
|
||||||
|
@ -6,8 +6,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content class="padding-gap-x-large">
|
<mat-card-content class="padding-gap-x-large" fxLayout="column">
|
||||||
<form *ngIf="!sweepAll; else sweepAllBlock;" #form="ngForm" fxLayout="row wrap"fxLayoutAlign="space-between start" fxFlex="100" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()">
|
<div *ngIf="recommendedFee.minimumFee" fxFlex="100" class="alert alert-info mb-2">
|
||||||
|
<fa-icon class="mr-1 alert-icon" [icon]="faInfoCircle" />
|
||||||
|
<span fxLayout="column" fxFlex="100">
|
||||||
|
<div>Fee rates recommended by mempool (sat/vByte):</div>
|
||||||
|
<span class="pr-2" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
|
||||||
|
<span>- High: {{recommendedFee.fastestFee || 'Unknown'}}</span>
|
||||||
|
<span>- Medium: {{recommendedFee.halfHourFee || 'Unknown'}}</span>
|
||||||
|
<span>- Low: {{recommendedFee.hourFee || 'Unknown'}}</span>
|
||||||
|
<span>- Economy: {{recommendedFee.economyFee || 'Unknown'}}</span>
|
||||||
|
<span>- Minimum: {{recommendedFee.minimumFee || 'Unknown'}}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<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.gt-sm="55">
|
<mat-form-field fxLayout="column" fxFlex.gt-sm="55">
|
||||||
<mat-label>Bitcoin Address</mat-label>
|
<mat-label>Bitcoin Address</mat-label>
|
||||||
<input #address="ngModel" autoFocus matInput tabindex="1" name="address" required [(ngModel)]="transactionAddress">
|
<input #address="ngModel" autoFocus matInput tabindex="1" name="address" required [(ngModel)]="transactionAddress">
|
||||||
|
@ -8,7 +8,7 @@ import { Actions } from '@ngrx/effects';
|
|||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||||
import { MatStepper } from '@angular/material/stepper';
|
import { MatStepper } from '@angular/material/stepper';
|
||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { OnChainSendFunds } from '../../../shared/models/alertData';
|
import { OnChainSendFunds } from '../../../shared/models/alertData';
|
||||||
import { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
|
import { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
|
||||||
@ -23,6 +23,8 @@ import { RTLState } from '../../../store/rtl.state';
|
|||||||
import { isAuthorized, openSnackBar } from '../../../store/rtl.actions';
|
import { isAuthorized, openSnackBar } from '../../../store/rtl.actions';
|
||||||
import { setChannelTransaction } from '../../store/lnd.actions';
|
import { setChannelTransaction } from '../../store/lnd.actions';
|
||||||
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
|
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
|
||||||
|
import { DataService } from 'src/app/shared/services/data.service';
|
||||||
|
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'rtl-on-chain-send-modal',
|
selector: 'rtl-on-chain-send-modal',
|
||||||
@ -35,6 +37,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
|
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
|
||||||
@ViewChild('stepper', { static: false }) stepper: MatStepper;
|
@ViewChild('stepper', { static: false }) stepper: MatStepper;
|
||||||
public faExclamationTriangle = faExclamationTriangle;
|
public faExclamationTriangle = faExclamationTriangle;
|
||||||
|
public faInfoCircle = faInfoCircle;
|
||||||
public sweepAll = false;
|
public sweepAll = false;
|
||||||
public selNode: Node | null;
|
public selNode: Node | null;
|
||||||
public appConfig: RTLConfiguration;
|
public appConfig: RTLConfiguration;
|
||||||
@ -58,6 +61,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
public sendFundError = '';
|
public sendFundError = '';
|
||||||
public flgValidated = false;
|
public flgValidated = false;
|
||||||
public flgEditable = true;
|
public flgEditable = true;
|
||||||
|
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
|
||||||
public passwordFormLabel = 'Authenticate with your RTL password';
|
public passwordFormLabel = 'Authenticate with your RTL password';
|
||||||
public sendFundFormLabel = 'Sweep funds';
|
public sendFundFormLabel = 'Sweep funds';
|
||||||
public confirmFormLabel = 'Confirm sweep';
|
public confirmFormLabel = 'Confirm sweep';
|
||||||
@ -65,12 +69,13 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
passwordFormGroup: UntypedFormGroup;
|
passwordFormGroup: UntypedFormGroup;
|
||||||
sendFundFormGroup: UntypedFormGroup;
|
sendFundFormGroup: UntypedFormGroup;
|
||||||
confirmFormGroup: UntypedFormGroup;
|
confirmFormGroup: UntypedFormGroup;
|
||||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<OnChainSendModalComponent>,
|
public dialogRef: MatDialogRef<OnChainSendModalComponent>,
|
||||||
@Inject(MAT_DIALOG_DATA) public data: OnChainSendFunds,
|
@Inject(MAT_DIALOG_DATA) public data: OnChainSendFunds,
|
||||||
private logger: LoggerService,
|
private logger: LoggerService,
|
||||||
|
private dataService: DataService,
|
||||||
private store: Store<RTLState>,
|
private store: Store<RTLState>,
|
||||||
private rtlEffects: RTLEffects,
|
private rtlEffects: RTLEffects,
|
||||||
private commonService: CommonService,
|
private commonService: CommonService,
|
||||||
@ -80,6 +85,13 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
private formBuilder: UntypedFormBuilder) { }
|
private formBuilder: UntypedFormBuilder) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
|
||||||
|
next: (rfRes: RecommendedFeeRates) => {
|
||||||
|
this.recommendedFee = rfRes;
|
||||||
|
}, error: (err) => {
|
||||||
|
this.logger.error(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
this.sweepAll = this.data.sweepAll;
|
this.sweepAll = this.data.sweepAll;
|
||||||
this.passwordFormGroup = this.formBuilder.group({
|
this.passwordFormGroup = this.formBuilder.group({
|
||||||
hiddenPassword: ['', [Validators.required]],
|
hiddenPassword: ['', [Validators.required]],
|
||||||
@ -92,7 +104,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
selTransType: ['1', Validators.required]
|
selTransType: ['1', Validators.required]
|
||||||
});
|
});
|
||||||
this.confirmFormGroup = this.formBuilder.group({});
|
this.confirmFormGroup = this.formBuilder.group({});
|
||||||
this.sendFundFormGroup.controls.selTransType.valueChanges.pipe(takeUntil(this.unSubs[0])).subscribe((transType) => {
|
this.sendFundFormGroup.controls.selTransType.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((transType) => {
|
||||||
if (transType === '1') {
|
if (transType === '1') {
|
||||||
this.sendFundFormGroup.controls.transactionBlocks.setValidators([Validators.required]);
|
this.sendFundFormGroup.controls.transactionBlocks.setValidators([Validators.required]);
|
||||||
this.sendFundFormGroup.controls.transactionBlocks.setValue(null);
|
this.sendFundFormGroup.controls.transactionBlocks.setValue(null);
|
||||||
@ -105,16 +117,16 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
this.sendFundFormGroup.controls.transactionFees.setValue(null);
|
this.sendFundFormGroup.controls.transactionFees.setValue(null);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.store.select(rootAppConfig).pipe(takeUntil(this.unSubs[1])).subscribe((appConfig) => {
|
this.store.select(rootAppConfig).pipe(takeUntil(this.unSubs[2])).subscribe((appConfig) => {
|
||||||
this.appConfig = appConfig;
|
this.appConfig = appConfig;
|
||||||
});
|
});
|
||||||
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[2])).subscribe((selNode) => {
|
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[3])).subscribe((selNode) => {
|
||||||
this.fiatConversion = selNode.settings.fiatConversion;
|
this.fiatConversion = selNode.settings.fiatConversion;
|
||||||
this.amountUnits = selNode.settings.currencyUnits;
|
this.amountUnits = selNode.settings.currencyUnits;
|
||||||
this.logger.info(selNode);
|
this.logger.info(selNode);
|
||||||
});
|
});
|
||||||
this.actions.pipe(
|
this.actions.pipe(
|
||||||
takeUntil(this.unSubs[3]),
|
takeUntil(this.unSubs[4]),
|
||||||
filter((action) => action.type === LNDActions.UPDATE_API_CALL_STATUS_LND || action.type === LNDActions.SET_CHANNEL_TRANSACTION_RES_LND)).
|
filter((action) => action.type === LNDActions.UPDATE_API_CALL_STATUS_LND || action.type === LNDActions.SET_CHANNEL_TRANSACTION_RES_LND)).
|
||||||
subscribe((action: any) => {
|
subscribe((action: any) => {
|
||||||
if (action.type === LNDActions.SET_CHANNEL_TRANSACTION_RES_LND) {
|
if (action.type === LNDActions.SET_CHANNEL_TRANSACTION_RES_LND) {
|
||||||
@ -173,7 +185,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
if (this.transactionAmount && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
|
if (this.transactionAmount && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
|
||||||
this.commonService.convertCurrency(this.transactionAmount, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
|
this.commonService.convertCurrency(this.transactionAmount, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
|
||||||
pipe(takeUntil(this.unSubs[4])).
|
pipe(takeUntil(this.unSubs[5])).
|
||||||
subscribe({
|
subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.selAmountUnit = CurrencyUnitEnum.SATS;
|
this.selAmountUnit = CurrencyUnitEnum.SATS;
|
||||||
@ -254,7 +266,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
|||||||
if (this.transactionAmount && this.selAmountUnit !== event.value) {
|
if (this.transactionAmount && this.selAmountUnit !== event.value) {
|
||||||
const amount = this.transactionAmount ? this.transactionAmount : 0;
|
const amount = this.transactionAmount ? this.transactionAmount : 0;
|
||||||
this.commonService.convertCurrency(amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
this.commonService.convertCurrency(amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
||||||
pipe(takeUntil(this.unSubs[5])).
|
pipe(takeUntil(this.unSubs[6])).
|
||||||
subscribe({
|
subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.selAmountUnit = event.value;
|
this.selAmountUnit = event.value;
|
||||||
|
@ -89,7 +89,7 @@
|
|||||||
<mat-option (click)="onUTXOClick(utxo)">View Info</mat-option>
|
<mat-option (click)="onUTXOClick(utxo)">View Info</mat-option>
|
||||||
<mat-option (click)="onLabelUTXO(utxo)">Label</mat-option>
|
<mat-option (click)="onLabelUTXO(utxo)">Label</mat-option>
|
||||||
<mat-option (click)="onLeaseUTXO(utxo)">Lease</mat-option>
|
<mat-option (click)="onLeaseUTXO(utxo)">Lease</mat-option>
|
||||||
<mat-option *ngIf="!utxo.label.toLowerCase().includes('sweep') && utxo.confirmations === '0'" (click)="onBumpFee(utxo)">Bump Fee</mat-option>
|
<mat-option *ngIf="utxo.label && !utxo.label.toLowerCase().includes('sweep') && utxo.confirmations === '0'" (click)="onBumpFee(utxo)">Bump Fee</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
@ -12,8 +12,8 @@ import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
|||||||
import { ChannelRebalanceAlert } from '../../../../shared/models/alertData';
|
import { ChannelRebalanceAlert } from '../../../../shared/models/alertData';
|
||||||
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 { Channel, QueryRoutes, ListInvoices } from '../../../../shared/models/lndModels';
|
import { Channel, QueryRoutes, ListInvoices, SendPayment } from '../../../../shared/models/lndModels';
|
||||||
import { DEFAULT_INVOICE_EXPIRY, FEE_LIMIT_TYPES, LNDActions, PAGE_SIZE, ScreenSizeEnum, UI_MESSAGES } from '../../../../shared/services/consts-enums-functions';
|
import { DEFAULT_INVOICE_EXPIRY, FEE_LIMIT_TYPES, LNDActions, PAGE_SIZE, ScreenSizeEnum, UI_MESSAGES, getFeeLimitSat } from '../../../../shared/services/consts-enums-functions';
|
||||||
|
|
||||||
import { RTLState } from '../../../../store/rtl.state';
|
import { RTLState } from '../../../../store/rtl.state';
|
||||||
import { saveNewInvoice, sendPayment } from '../../../store/lnd.actions';
|
import { saveNewInvoice, sendPayment } from '../../../store/lnd.actions';
|
||||||
@ -239,13 +239,29 @@ export class ChannelRebalanceComponent implements OnInit, OnDestroy {
|
|||||||
this.flgInvoiceGenerated = true;
|
this.flgInvoiceGenerated = true;
|
||||||
this.paymentRequest = payReq;
|
this.paymentRequest = payReq;
|
||||||
if (this.feeFormGroup.controls.selFeeLimitType.value.id === 'percent' && !(+this.feeFormGroup.controls.feeLimit.value % 1 === 0)) {
|
if (this.feeFormGroup.controls.selFeeLimitType.value.id === 'percent' && !(+this.feeFormGroup.controls.feeLimit.value % 1 === 0)) {
|
||||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.NO_SPINNER, paymentReq: payReq, outgoingChannel: this.selChannel,
|
const payload: SendPayment = {
|
||||||
feeLimitType: 'fixed', feeLimit: Math.ceil((+this.feeFormGroup.controls.feeLimit.value * +this.inputFormGroup.controls.rebalanceAmount.value) / 100),
|
uiMessage: UI_MESSAGES.NO_SPINNER,
|
||||||
allowSelfPayment: true, lastHopPubkey: this.inputFormGroup.controls.selRebalancePeer.value.remote_pubkey, fromDialog: true } }));
|
payment_request: payReq,
|
||||||
|
amp: false,
|
||||||
|
outgoing_chan_ids: this.selChannel?.chan_id ? [this.selChannel?.chan_id] : undefined,
|
||||||
|
fee_limit_sat: Math.ceil(getFeeLimitSat('fixed', this.feeFormGroup.controls.feeLimit.value, (this.inputFormGroup.controls.rebalanceAmount.value || 0))),
|
||||||
|
allow_self_payment: true,
|
||||||
|
last_hop_pubkey: this.inputFormGroup.controls.selRebalancePeer.value.remote_pubkey,
|
||||||
|
fromDialog: true
|
||||||
|
};
|
||||||
|
this.store.dispatch(sendPayment({ payload }));
|
||||||
} else {
|
} else {
|
||||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.NO_SPINNER, paymentReq: payReq, outgoingChannel: this.selChannel,
|
const payload: SendPayment = {
|
||||||
feeLimitType: this.feeFormGroup.controls.selFeeLimitType.value.id, feeLimit: this.feeFormGroup.controls.feeLimit.value, allowSelfPayment: true,
|
uiMessage: UI_MESSAGES.NO_SPINNER,
|
||||||
lastHopPubkey: this.inputFormGroup.controls.selRebalancePeer.value.remote_pubkey, fromDialog: true } }));
|
payment_request: payReq,
|
||||||
|
amp: false,
|
||||||
|
outgoing_chan_ids: this.selChannel?.chan_id ? [this.selChannel?.chan_id] : undefined,
|
||||||
|
fee_limit_sat: getFeeLimitSat(this.feeFormGroup.controls.selFeeLimitType.value.id, this.feeFormGroup.controls.feeLimit.value, (this.inputFormGroup.controls.rebalanceAmount.value || 0)),
|
||||||
|
allow_self_payment: true,
|
||||||
|
last_hop_pubkey: this.inputFormGroup.controls.selRebalancePeer.value.remote_pubkey,
|
||||||
|
fromDialog: true
|
||||||
|
};
|
||||||
|
this.store.dispatch(sendPayment({ payload }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -691,31 +691,16 @@ export class LNDEffects implements OnDestroy {
|
|||||||
mergeMap((action: { type: string, payload: SendPayment }) => {
|
mergeMap((action: { type: string, payload: SendPayment }) => {
|
||||||
this.store.dispatch(openSpinner({ payload: action.payload.uiMessage }));
|
this.store.dispatch(openSpinner({ payload: action.payload.uiMessage }));
|
||||||
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.INITIATED } }));
|
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.INITIATED } }));
|
||||||
const queryHeaders = {};
|
const queryParams = JSON.parse(JSON.stringify(action.payload));
|
||||||
queryHeaders['paymentReq'] = action.payload.paymentReq;
|
delete queryParams.uiMessage;
|
||||||
if (action.payload.paymentAmount) {
|
delete queryParams.fromDialog;
|
||||||
queryHeaders['paymentAmount'] = action.payload.paymentAmount;
|
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.PAYMENTS_API + '/send', queryParams).pipe(
|
||||||
}
|
|
||||||
if (action.payload.outgoingChannel) {
|
|
||||||
queryHeaders['outgoingChannel'] = action.payload.outgoingChannel.chan_id;
|
|
||||||
}
|
|
||||||
if (action.payload.allowSelfPayment) {
|
|
||||||
queryHeaders['allowSelfPayment'] = action.payload.allowSelfPayment;
|
|
||||||
} // Channel Rebalancing
|
|
||||||
if (action.payload.lastHopPubkey) {
|
|
||||||
queryHeaders['lastHopPubkey'] = action.payload.lastHopPubkey;
|
|
||||||
}
|
|
||||||
if (action.payload.feeLimitType && action.payload.feeLimitType !== FEE_LIMIT_TYPES[0].id) {
|
|
||||||
queryHeaders['feeLimit'] = {};
|
|
||||||
queryHeaders['feeLimit'][action.payload.feeLimitType] = action.payload.feeLimit;
|
|
||||||
}
|
|
||||||
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.CHANNELS_API + '/transactions', queryHeaders).pipe(
|
|
||||||
map((sendRes: any) => {
|
map((sendRes: any) => {
|
||||||
this.logger.info(sendRes);
|
this.logger.info(sendRes);
|
||||||
this.store.dispatch(closeSpinner({ payload: action.payload.uiMessage }));
|
this.store.dispatch(closeSpinner({ payload: action.payload.uiMessage }));
|
||||||
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.COMPLETED } }));
|
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.COMPLETED } }));
|
||||||
if (sendRes.payment_error) {
|
if (sendRes.payment_error) {
|
||||||
if (action.payload.allowSelfPayment) {
|
if (action.payload.allow_self_payment) {
|
||||||
this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
||||||
return {
|
return {
|
||||||
type: LNDActions.SEND_PAYMENT_STATUS_LND,
|
type: LNDActions.SEND_PAYMENT_STATUS_LND,
|
||||||
@ -734,7 +719,7 @@ export class LNDEffects implements OnDestroy {
|
|||||||
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.COMPLETED } }));
|
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.COMPLETED } }));
|
||||||
this.store.dispatch(fetchChannels());
|
this.store.dispatch(fetchChannels());
|
||||||
this.store.dispatch(fetchPayments({ payload: { max_payments: this.paymentsPageSettings?.recordsPerPage, reversed: true } }));
|
this.store.dispatch(fetchPayments({ payload: { max_payments: this.paymentsPageSettings?.recordsPerPage, reversed: true } }));
|
||||||
if (action.payload.allowSelfPayment) {
|
if (action.payload.allow_self_payment) {
|
||||||
this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
||||||
} else {
|
} else {
|
||||||
let msg = 'Payment Sent Successfully.';
|
let msg = 'Payment Sent Successfully.';
|
||||||
@ -751,7 +736,7 @@ export class LNDEffects implements OnDestroy {
|
|||||||
}),
|
}),
|
||||||
catchError((err: any) => {
|
catchError((err: any) => {
|
||||||
this.logger.error('Error: ' + JSON.stringify(err));
|
this.logger.error('Error: ' + JSON.stringify(err));
|
||||||
if (action.payload.allowSelfPayment) {
|
if (action.payload.allow_self_payment) {
|
||||||
this.handleErrorWithoutAlert('SendPayment', action.payload.uiMessage, 'Send Payment Failed.', err);
|
this.handleErrorWithoutAlert('SendPayment', action.payload.uiMessage, 'Send Payment Failed.', err);
|
||||||
this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
this.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
||||||
return of({
|
return of({
|
||||||
|
@ -195,7 +195,7 @@ export class LightningPaymentsComponent implements OnInit, AfterViewInit, OnDest
|
|||||||
subscribe((confirmRes) => {
|
subscribe((confirmRes) => {
|
||||||
if (confirmRes) {
|
if (confirmRes) {
|
||||||
this.paymentDecoded.num_satoshis = confirmRes[0].inputValue;
|
this.paymentDecoded.num_satoshis = confirmRes[0].inputValue;
|
||||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentReq: this.paymentRequest, paymentAmount: confirmRes[0].inputValue, fromDialog: false } }));
|
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, payment_request: this.paymentRequest, amp: false, amt: confirmRes[0].inputValue, fromDialog: false } }));
|
||||||
this.resetData();
|
this.resetData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -224,7 +224,7 @@ export class LightningPaymentsComponent implements OnInit, AfterViewInit, OnDest
|
|||||||
pipe(take(1)).
|
pipe(take(1)).
|
||||||
subscribe((confirmRes) => {
|
subscribe((confirmRes) => {
|
||||||
if (confirmRes) {
|
if (confirmRes) {
|
||||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentReq: this.paymentRequest, fromDialog: false } }));
|
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, payment_request: this.paymentRequest, amp: false, fromDialog: false } }));
|
||||||
this.resetData();
|
this.resetData();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<div fxFlex="95" fxLayoutAlign="start start">
|
<div fxFlex="95" fxLayoutAlign="start start">
|
||||||
<span class="page-title">Send Payment</span>
|
<span class="page-title">Send Payment</span>
|
||||||
</div>
|
</div>
|
||||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
<button tabindex="11" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
|
||||||
</mat-card-header>
|
</mat-card-header>
|
||||||
<mat-card-content class="padding-gap-x-large">
|
<mat-card-content class="padding-gap-x-large">
|
||||||
<form #sendPaymentForm="ngForm" fxLayoutAlign="space-between stretch" fxLayout="column">
|
<form #sendPaymentForm="ngForm" fxLayoutAlign="space-between stretch" fxLayout="column">
|
||||||
@ -56,6 +56,7 @@
|
|||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
<mat-error *ngIf="selectedChannelCtrl.errors?.notfound">Channel not found in the list.</mat-error>
|
<mat-error *ngIf="selectedChannelCtrl.errors?.notfound">Channel not found in the list.</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-slide-toggle fxFlex="25" tabindex="8" color="primary" name="isAmp" [(ngModel)]="isAmp">AMP Payment</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
</mat-expansion-panel>
|
</mat-expansion-panel>
|
||||||
<div *ngIf="paymentError !== ''" fxFlex="100" class="alert alert-danger mt-1">
|
<div *ngIf="paymentError !== ''" fxFlex="100" class="alert alert-danger mt-1">
|
||||||
@ -63,8 +64,8 @@
|
|||||||
<span *ngIf="paymentError !== ''">{{paymentError}}</span>
|
<span *ngIf="paymentError !== ''">{{paymentError}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="end center">
|
<div class="mt-2" fxLayout="row" fxLayoutAlign="end center">
|
||||||
<button class="mr-1" mat-button color="primary" tabindex="2" type="reset" (click)="resetData()">Clear Fields</button>
|
<button class="mr-1" mat-button color="primary" tabindex="9" type="reset" (click)="resetData()">Clear Fields</button>
|
||||||
<button mat-button id="sendBtn" color="primary" tabindex="3" (click)="onSendPayment()">Send Payment</button>
|
<button mat-button id="sendBtn" color="primary" tabindex="10" (click)="onSendPayment()">Send Payment</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</mat-card-content>
|
</mat-card-content>
|
||||||
|
@ -90,8 +90,8 @@ describe('LightningSendPaymentsComponent', () => {
|
|||||||
const sendButton = fixture.debugElement.nativeElement.querySelector('#sendBtn');
|
const sendButton = fixture.debugElement.nativeElement.querySelector('#sendBtn');
|
||||||
sendButton.click();
|
sendButton.click();
|
||||||
const expectedSendPaymentPayload: SendPayment = {
|
const expectedSendPaymentPayload: SendPayment = {
|
||||||
uiMessage: UI_MESSAGES.SEND_PAYMENT, outgoingChannel: null, feeLimitType: 'none', feeLimit: null, fromDialog: true,
|
uiMessage: UI_MESSAGES.SEND_PAYMENT, outgoing_chan_ids: undefined, fee_limit_sat: 1000000, fromDialog: true, amp: false,
|
||||||
paymentReq: 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq' +
|
payment_request: 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq' +
|
||||||
'6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5c' +
|
'6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5c' +
|
||||||
'qqqzgqqqqqqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq' +
|
'qqqzgqqqqqqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq' +
|
||||||
'38j0s3hrgpv4eel3'
|
'38j0s3hrgpv4eel3'
|
||||||
|
@ -9,8 +9,8 @@ import { MatDialogRef } from '@angular/material/dialog';
|
|||||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { Node } from '../../../shared/models/RTLconfig';
|
import { Node } from '../../../shared/models/RTLconfig';
|
||||||
import { PayRequest, Channel, ChannelsSummary, LightningBalance } from '../../../shared/models/lndModels';
|
import { PayRequest, Channel, ChannelsSummary, LightningBalance, SendPayment } from '../../../shared/models/lndModels';
|
||||||
import { APICallStatusEnum, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, FEE_LIMIT_TYPES, LNDActions, UI_MESSAGES } from '../../../shared/services/consts-enums-functions';
|
import { APICallStatusEnum, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, FEE_LIMIT_TYPES, LNDActions, UI_MESSAGES, getFeeLimitSat } 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';
|
||||||
import { DataService } from '../../../shared/services/data.service';
|
import { DataService } from '../../../shared/services/data.service';
|
||||||
@ -43,6 +43,7 @@ export class LightningSendPaymentsComponent implements OnInit, OnDestroy {
|
|||||||
public activeChannels: Channel[] = [];
|
public activeChannels: Channel[] = [];
|
||||||
public filteredMinAmtActvChannels: Channel[] = [];
|
public filteredMinAmtActvChannels: Channel[] = [];
|
||||||
public selectedChannelCtrl = new UntypedFormControl();
|
public selectedChannelCtrl = new UntypedFormControl();
|
||||||
|
public isAmp = false;
|
||||||
public feeLimit: number | null = null;
|
public feeLimit: number | null = null;
|
||||||
public selFeeLimitType = FEE_LIMIT_TYPES[0];
|
public selFeeLimitType = FEE_LIMIT_TYPES[0];
|
||||||
public feeLimitTypes = FEE_LIMIT_TYPES;
|
public feeLimitTypes = FEE_LIMIT_TYPES;
|
||||||
@ -142,10 +143,27 @@ export class LightningSendPaymentsComponent implements OnInit, OnDestroy {
|
|||||||
if (!this.paymentDecoded.num_satoshis || this.paymentDecoded.num_satoshis === '' || this.paymentDecoded.num_satoshis === '0') {
|
if (!this.paymentDecoded.num_satoshis || this.paymentDecoded.num_satoshis === '' || this.paymentDecoded.num_satoshis === '0') {
|
||||||
this.zeroAmtInvoice = true;
|
this.zeroAmtInvoice = true;
|
||||||
this.paymentDecoded.num_satoshis = this.paymentAmount?.toString() || '';
|
this.paymentDecoded.num_satoshis = this.paymentAmount?.toString() || '';
|
||||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentReq: this.paymentRequest, paymentAmount: this.paymentAmount || 0, outgoingChannel: this.selectedChannelCtrl.value, feeLimitType: this.selFeeLimitType.id, feeLimit: this.feeLimit, fromDialog: true } }));
|
const payload: SendPayment = {
|
||||||
|
uiMessage: UI_MESSAGES.SEND_PAYMENT,
|
||||||
|
payment_request: this.paymentRequest,
|
||||||
|
amp: this.isAmp,
|
||||||
|
amt: this.paymentAmount || 0,
|
||||||
|
outgoing_chan_ids: this.selectedChannelCtrl.value?.chan_id ? [this.selectedChannelCtrl.value.chan_id] : undefined,
|
||||||
|
fee_limit_sat: getFeeLimitSat(this.selFeeLimitType.id, this.feeLimit, (this.paymentAmount || 0)),
|
||||||
|
fromDialog: true
|
||||||
|
};
|
||||||
|
this.store.dispatch(sendPayment({ payload }));
|
||||||
} else {
|
} else {
|
||||||
this.zeroAmtInvoice = false;
|
this.zeroAmtInvoice = false;
|
||||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_PAYMENT, paymentReq: this.paymentRequest, outgoingChannel: this.selectedChannelCtrl.value, feeLimitType: this.selFeeLimitType.id, feeLimit: this.feeLimit, fromDialog: true } }));
|
const payload: SendPayment = {
|
||||||
|
uiMessage: UI_MESSAGES.SEND_PAYMENT,
|
||||||
|
payment_request: this.paymentRequest,
|
||||||
|
amp: this.isAmp,
|
||||||
|
outgoing_chan_ids: this.selectedChannelCtrl.value?.chan_id ? [this.selectedChannelCtrl.value.chan_id] : undefined,
|
||||||
|
fee_limit_sat: getFeeLimitSat(this.selFeeLimitType.id, this.feeLimit, (+this.paymentDecoded.num_satoshis || 0)),
|
||||||
|
fromDialog: true
|
||||||
|
};
|
||||||
|
this.store.dispatch(sendPayment({ payload }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,6 +249,7 @@ export class LightningSendPaymentsComponent implements OnInit, OnDestroy {
|
|||||||
resetData() {
|
resetData() {
|
||||||
this.paymentDecoded = {};
|
this.paymentDecoded = {};
|
||||||
this.paymentRequest = '';
|
this.paymentRequest = '';
|
||||||
|
this.isAmp = false;
|
||||||
this.selectedChannelCtrl.setValue(null);
|
this.selectedChannelCtrl.setValue(null);
|
||||||
this.filteredMinAmtActvChannels = this.activeChannels;
|
this.filteredMinAmtActvChannels = this.activeChannels;
|
||||||
if (this.filteredMinAmtActvChannels.length && this.filteredMinAmtActvChannels.length > 0) {
|
if (this.filteredMinAmtActvChannels.length && this.filteredMinAmtActvChannels.length > 0) {
|
||||||
|
@ -21,8 +21,8 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="data.scrollable && shouldScroll" fxLayout="row" fxLayoutAlign="start end" class="btn-sticky-container padding-gap-x-large">
|
<div *ngIf="data.scrollable && shouldScroll" fxLayout="row" fxLayoutAlign="start end" class="btn-sticky-container padding-gap-x-large">
|
||||||
<button mat-mini-fab aria-label="Scroll" fxLayoutAlign="center center" (click)="onScroll()">
|
<button mat-mini-fab aria-label="Scroll" fxLayoutAlign="center center" (click)="onScroll()">
|
||||||
<mat-icon *ngIf="scrollDirection === 'DOWN'" fxLayoutAlign="center center">arrow_downward</mat-icon>
|
<mat-icon *ngIf="scrollDirection === 'DOWN'" class="arrow-downward" fxLayoutAlign="center center">arrow_downward</mat-icon>
|
||||||
<mat-icon *ngIf="scrollDirection === 'UP'" fxLayoutAlign="center center">arrow_upward</mat-icon>
|
<mat-icon *ngIf="scrollDirection === 'UP'" class="arrow-upward" fxLayoutAlign="center center">arrow_upward</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div fxLayout="row" fxLayoutAlign="end center" class="padding-gap-x-large padding-gap-bottom-large">
|
<div fxLayout="row" fxLayoutAlign="end center" class="padding-gap-x-large padding-gap-bottom-large">
|
||||||
@ -50,7 +50,9 @@
|
|||||||
<span *ngIf="obj && (!!obj.value || obj.value === 0); else emptyField">
|
<span *ngIf="obj && (!!obj.value || obj.value === 0); else emptyField">
|
||||||
<span class="foreground-secondary-text" fxLayout="row" fxFlex="100" fxLayoutAlign="start stretch" [ngSwitch]="obj.type">
|
<span class="foreground-secondary-text" fxLayout="row" fxFlex="100" fxLayoutAlign="start stretch" [ngSwitch]="obj.type">
|
||||||
<ng-container *ngSwitchCase="dataTypeEnum.ARRAY">
|
<ng-container *ngSwitchCase="dataTypeEnum.ARRAY">
|
||||||
<span *ngFor="let arrayObj of obj.value" class="display-block w-100" [innerHTML]="arrayObj"></span>
|
<span fxLayout="column" fxFlex="100">
|
||||||
|
<span *ngFor="let arrayObj of obj.value" class="display-block w-100" [innerHTML]="arrayObj"></span>
|
||||||
|
</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngSwitchCase="dataTypeEnum.DATE_TIME">{{(obj.value * 1000) | date:'dd/MMM/y HH:mm'}}</ng-container>
|
<ng-container *ngSwitchCase="dataTypeEnum.DATE_TIME">{{(obj.value * 1000) | date:'dd/MMM/y HH:mm'}}</ng-container>
|
||||||
<ng-container *ngSwitchCase="dataTypeEnum.NUMBER">{{obj.value | number: obj.digitsInfo ? obj.digitsInfo : '1.0-3'}}</ng-container>
|
<ng-container *ngSwitchCase="dataTypeEnum.NUMBER">{{obj.value | number: obj.digitsInfo ? obj.digitsInfo : '1.0-3'}}</ng-container>
|
||||||
|
@ -552,13 +552,13 @@ export interface FetchPayments {
|
|||||||
export interface SendPayment {
|
export interface SendPayment {
|
||||||
uiMessage: string;
|
uiMessage: string;
|
||||||
fromDialog: boolean;
|
fromDialog: boolean;
|
||||||
paymentReq: string;
|
payment_request: string;
|
||||||
paymentAmount?: number;
|
amp: boolean;
|
||||||
outgoingChannel?: Channel | null;
|
amt?: number;
|
||||||
feeLimitType?: string;
|
outgoing_chan_ids?: string[] | [];
|
||||||
feeLimit?: number | null;
|
fee_limit_sat?: number | null;
|
||||||
allowSelfPayment?: boolean;
|
allow_self_payment?: boolean;
|
||||||
lastHopPubkey?: string;
|
last_hop_pubkey?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetNewAddress {
|
export interface GetNewAddress {
|
||||||
|
@ -12,10 +12,11 @@ export function getPaginatorLabel(field: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const HOUR_SECONDS = 3600;
|
export const HOUR_SECONDS = 3600;
|
||||||
|
export const SECS_IN_YEAR = 31536000;
|
||||||
|
|
||||||
export const DEFAULT_INVOICE_EXPIRY = HOUR_SECONDS * 24 * 7;
|
export const DEFAULT_INVOICE_EXPIRY = HOUR_SECONDS * 24 * 7;
|
||||||
|
|
||||||
export const VERSION = '0.15.2-beta';
|
export const VERSION = '0.15.3-beta';
|
||||||
|
|
||||||
export const API_URL = isDevMode() ? 'http://localhost:3000/rtl/api' : './api';
|
export const API_URL = isDevMode() ? 'http://localhost:3000/rtl/api' : './api';
|
||||||
|
|
||||||
@ -69,7 +70,7 @@ export const TRANS_TYPES = [
|
|||||||
export const FEE_LIMIT_TYPES = [
|
export const FEE_LIMIT_TYPES = [
|
||||||
{ id: 'none', name: 'No Fee Limit', placeholder: 'No Limit' },
|
{ id: 'none', name: 'No Fee Limit', placeholder: 'No Limit' },
|
||||||
{ id: 'fixed', name: 'Fixed Limit (Sats)', placeholder: 'Fixed Limit in Sats' },
|
{ id: 'fixed', name: 'Fixed Limit (Sats)', placeholder: 'Fixed Limit in Sats' },
|
||||||
{ id: 'percent', name: 'Percentage of Payment Amount', placeholder: 'Percentage Limit' }
|
{ id: 'percent', name: 'Percentage of Amount', placeholder: 'Percentage Limit' }
|
||||||
];
|
];
|
||||||
|
|
||||||
export const FEE_RATE_TYPES = [
|
export const FEE_RATE_TYPES = [
|
||||||
@ -1396,3 +1397,14 @@ export function getSelectedCurrency(currencyID: string) {
|
|||||||
}
|
}
|
||||||
return foundCurrency;
|
return foundCurrency;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function getFeeLimitSat(selFeeLimitTypeID: string, feeLimit: number, amount?: number) {
|
||||||
|
if (selFeeLimitTypeID === 'fixed') {
|
||||||
|
return feeLimit;
|
||||||
|
}
|
||||||
|
if (selFeeLimitTypeID === 'percent') {
|
||||||
|
return Math.ceil(((amount || 0) * feeLimit) / 100);
|
||||||
|
}
|
||||||
|
return 1000000;
|
||||||
|
}
|
||||||
|
@ -402,7 +402,7 @@
|
|||||||
&.info-icon-primary {
|
&.info-icon-primary {
|
||||||
color: $primary-darker;
|
color: $primary-darker;
|
||||||
}
|
}
|
||||||
&.info-icon-text {
|
&.info-icon-text, &.arrow-downward, &.arrow-upward {
|
||||||
color: $foreground-text;
|
color: $foreground-text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -323,6 +323,10 @@
|
|||||||
&.info-icon-text {
|
&.info-icon-text {
|
||||||
color: $foreground-secondary-text;
|
color: $foreground-secondary-text;
|
||||||
}
|
}
|
||||||
|
&.arrow-downward, &.arrow-upward {
|
||||||
|
font-size: 150%;
|
||||||
|
color: $background-color;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user