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-restricted-globals": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-undefined": "error",
|
||||
"block-spacing": "error",
|
||||
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
|
||||
"comma-style": "error",
|
||||
|
40
.github/workflows/ci.yml
vendored
40
.github/workflows/ci.yml
vendored
@ -2,31 +2,29 @@ name: Artifact
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, 'Release-*' ]
|
||||
tags: [ 'v*' ]
|
||||
release:
|
||||
types: [released]
|
||||
# Triggers the workflow only when merging pull request to the branches.
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches: [ master, 'Release-*' ]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18.x
|
||||
|
||||
- name: Cache node_modules
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
id: cache-npm-packages
|
||||
with:
|
||||
path: node_modules
|
||||
@ -37,7 +35,7 @@ jobs:
|
||||
run: npm ci --legacy-peer-deps
|
||||
|
||||
- name: Cache build frontend
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
id: cache-build-frontend
|
||||
with:
|
||||
path: frontend
|
||||
@ -47,7 +45,7 @@ jobs:
|
||||
run: npm run buildfrontend
|
||||
|
||||
- name: Cache build backend
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
id: cache-build-backend
|
||||
with:
|
||||
path: backend
|
||||
@ -61,26 +59,32 @@ jobs:
|
||||
needs: build
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Cache build frontend
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
id: cache-build-frontend
|
||||
with:
|
||||
path: frontend
|
||||
key: ${{ runner.os }}-frontend-${{ github.sha }}
|
||||
|
||||
- name: Cache build backend
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v4
|
||||
id: cache-build-backend
|
||||
with:
|
||||
path: backend
|
||||
key: ${{ runner.os }}-backend-${{ github.sha }}
|
||||
|
||||
- name: Compress files
|
||||
run: tar -czf /tmp/rtlbuild.tar.gz frontend backend rtl.js package.json package-lock.json
|
||||
env:
|
||||
VERSION: "${{ github.event.release.tag_name || github.event.inputs.version || '' }}"
|
||||
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@v2
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: rtl-build-${{ github.event.release.tag_name }}
|
||||
path: /tmp/rtlbuild.tar.gz
|
||||
name: rtl-build-${{ github.event.release.tag_name || github.event.inputs.version || '' }}
|
||||
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) {
|
||||
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;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body });
|
||||
res.status(204).json({ status: 'Invoice Deleted Successfully' });
|
||||
res.status(201).json({ status: 'Cleaned Invoices: ' + body.autoclean.expiredinvoices.cleaned + ', Uncleaned Invoices: ' + body.autoclean.expiredinvoices.uncleaned });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
|
@ -83,7 +83,7 @@ export const disableOffer = (req, res, next) => {
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableOffer';
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableoffer';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
|
||||
|
@ -28,7 +28,7 @@ export const simplifyAllChannels = (selNode, channels) => {
|
||||
});
|
||||
channelNodeIds = channelNodeIds.substring(1);
|
||||
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 });
|
||||
return request.post(options).then((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 });
|
||||
});
|
||||
};
|
||||
export const postTransactions = (req, res, next) => {
|
||||
const { paymentReq, paymentAmount, feeLimit, outgoingChannel, allowSelfPayment, lastHopPubkey } = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Sending Payment..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/channels/transaction-stream';
|
||||
options.form = { payment_request: paymentReq };
|
||||
if (paymentAmount) {
|
||||
options.form.amt = paymentAmount;
|
||||
}
|
||||
if (feeLimit) {
|
||||
options.form.fee_limit = feeLimit;
|
||||
}
|
||||
if (outgoingChannel) {
|
||||
options.form.outgoing_chan_id = outgoingChannel;
|
||||
}
|
||||
if (allowSelfPayment) {
|
||||
options.form.allow_self_payment = allowSelfPayment;
|
||||
}
|
||||
if (lastHopPubkey) {
|
||||
options.form.last_hop_pubkey = Buffer.from(lastHopPubkey, 'hex').toString('base64');
|
||||
}
|
||||
options.form = JSON.stringify(options.form);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
body = body.result ? body.result : body;
|
||||
if (body.payment_error) {
|
||||
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
}
|
||||
else {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Payment Sent', data: body });
|
||||
res.status(201).json(body);
|
||||
}
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const closeChannel = (req, res, next) => {
|
||||
try {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
|
||||
|
@ -97,3 +97,33 @@ export const paymentLookup = (req, res, next) => {
|
||||
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';
|
||||
const { Router } = exprs;
|
||||
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();
|
||||
router.get('/', isAuthenticated, getAllChannels);
|
||||
router.get('/pending', isAuthenticated, getPendingChannels);
|
||||
router.get('/closed', isAuthenticated, getClosedChannels);
|
||||
router.post('/', isAuthenticated, postChannel);
|
||||
router.post('/transactions', isAuthenticated, postTransactions);
|
||||
router.delete('/:channelPoint', isAuthenticated, closeChannel);
|
||||
router.post('/chanPolicy', isAuthenticated, postChanPolicy);
|
||||
export default router;
|
||||
|
@ -1,11 +1,12 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
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();
|
||||
router.get('/', isAuthenticated, getPayments);
|
||||
router.get('/alltransactions', isAuthenticated, getAllLightningTransactions);
|
||||
router.get('/decode/:payRequest', isAuthenticated, decodePayment);
|
||||
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
|
||||
router.post('/', isAuthenticated, decodePayments);
|
||||
router.post('/send', isAuthenticated, sendPayment);
|
||||
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);
|
||||
if (exists) {
|
||||
try {
|
||||
|
@ -146,7 +146,7 @@ export class ConfigService {
|
||||
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
|
||||
this.common.nodes[idx].index = node.index;
|
||||
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';
|
||||
}
|
||||
@ -343,7 +343,7 @@ export class ConfigService {
|
||||
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
|
||||
}
|
||||
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:
|
||||
```
|
||||
RTL_CONFIG_PATH=/RTLConfig
|
||||
LN_IMPLEMENTATION=LND
|
||||
MACAROON_PATH=/LNDMacaroon
|
||||
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
|
||||
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
|
||||
MIT
|
||||
@ -28,15 +50,81 @@ THE SOFTWARE.
|
||||
|
||||
@angular/common
|
||||
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
|
||||
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
|
||||
MIT
|
||||
|
||||
@angular/forms
|
||||
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
|
||||
MIT
|
||||
@ -65,9 +153,53 @@ THE SOFTWARE.
|
||||
|
||||
@angular/platform-browser
|
||||
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
|
||||
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
|
||||
MIT
|
||||
@ -1565,9 +1697,6 @@ THE SOFTWARE.
|
||||
elliptic
|
||||
MIT
|
||||
|
||||
encode-utf8
|
||||
MIT
|
||||
|
||||
events
|
||||
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",
|
||||
"version": "0.15.2-beta",
|
||||
"version": "0.15.3-beta",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"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..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/delexpiredinvoice';
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/autoclean-once';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoices Deleted', data: body });
|
||||
res.status(204).json({ status: 'Invoice Deleted Successfully' });
|
||||
res.status(201).json({ status: 'Cleaned Invoices: ' + body.autoclean.expiredinvoices.cleaned + ', Uncleaned Invoices: ' + body.autoclean.expiredinvoices.uncleaned });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoice', 'Delete Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
|
@ -82,7 +82,7 @@ export const disableOffer = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Disabling Offer..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableOffer';
|
||||
options.url = req.session.selectedNode.settings.lnServerUrl + '/v1/disableoffer';
|
||||
options.body = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Offers', msg: 'Offer Disabled', data: body });
|
||||
|
@ -31,7 +31,7 @@ export const simplifyAllChannels = (selNode: SelectedNode, channels) => {
|
||||
});
|
||||
channelNodeIds = channelNodeIds.substring(1);
|
||||
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 });
|
||||
return request.post(options).then((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) => {
|
||||
try {
|
||||
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 });
|
||||
});
|
||||
};
|
||||
|
||||
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';
|
||||
const { Router } = exprs;
|
||||
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();
|
||||
|
||||
@ -9,7 +9,6 @@ router.get('/', isAuthenticated, getAllChannels);
|
||||
router.get('/pending', isAuthenticated, getPendingChannels);
|
||||
router.get('/closed', isAuthenticated, getClosedChannels);
|
||||
router.post('/', isAuthenticated, postChannel);
|
||||
router.post('/transactions', isAuthenticated, postTransactions);
|
||||
router.delete('/:channelPoint', isAuthenticated, closeChannel);
|
||||
router.post('/chanPolicy', isAuthenticated, postChanPolicy);
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
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();
|
||||
|
||||
@ -10,5 +10,6 @@ router.get('/alltransactions', isAuthenticated, getAllLightningTransactions);
|
||||
router.get('/decode/:payRequest', isAuthenticated, decodePayment);
|
||||
router.get('/lookup/:paymentHash', isAuthenticated, paymentLookup);
|
||||
router.post('/', isAuthenticated, decodePayments);
|
||||
router.post('/send', isAuthenticated, sendPayment);
|
||||
|
||||
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);
|
||||
if (exists) {
|
||||
try {
|
||||
|
@ -150,7 +150,7 @@ export class ConfigService {
|
||||
this.common.nodes[idx] = { settings: { blockExplorerUrl: '' }, authentication: {} };
|
||||
this.common.nodes[idx].index = node.index;
|
||||
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'; }
|
||||
switch (this.common.nodes[idx].lnImplementation) {
|
||||
case 'CLN':
|
||||
@ -321,7 +321,7 @@ export class ConfigService {
|
||||
if (!config.SSO.rtlCookiePath || config.SSO.rtlCookiePath.trim() === '') {
|
||||
this.errMsg = 'Please set rtlCookiePath value for single sign on option!';
|
||||
} else {
|
||||
this.common.readCookie();
|
||||
this.common.readCookie(config);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -6,7 +6,20 @@
|
||||
</div>
|
||||
<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-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()">
|
||||
<mat-form-field fxLayout="column" fxFlex="55">
|
||||
<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 { MatSnackBar } from '@angular/material/snack-bar';
|
||||
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 { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
|
||||
@ -25,6 +25,8 @@ import { setChannelTransaction } from '../../store/cln.actions';
|
||||
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
|
||||
import { clnNodeInformation, utxoBalances } from '../../store/cln.selector';
|
||||
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
|
||||
import { DataService } from 'src/app/shared/services/data.service';
|
||||
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cln-on-chain-send-modal',
|
||||
@ -37,6 +39,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
|
||||
@ViewChild('stepper', { static: false }) stepper: MatStepper;
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public faInfoCircle = faInfoCircle;
|
||||
public sweepAll = false;
|
||||
public selNode: Node | null;
|
||||
public appConfig: RTLConfiguration;
|
||||
@ -65,6 +68,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
public advancedTitle = 'Advanced Options';
|
||||
public flgValidated = false;
|
||||
public flgEditable = true;
|
||||
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
|
||||
public passwordFormLabel = 'Authenticate with your RTL password';
|
||||
public sendFundFormLabel = 'Sweep funds';
|
||||
public confirmFormLabel = 'Confirm sweep';
|
||||
@ -74,12 +78,13 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
confirmFormGroup: UntypedFormGroup;
|
||||
public screenSize = '';
|
||||
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(
|
||||
public dialogRef: MatDialogRef<CLNOnChainSendModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: CLNOnChainSendFunds,
|
||||
private logger: LoggerService,
|
||||
private dataService: DataService,
|
||||
private store: Store<RTLState>,
|
||||
private commonService: CommonService,
|
||||
private decimalPipe: DecimalPipe,
|
||||
@ -91,6 +96,13 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
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.passwordFormGroup = this.formBuilder.group({
|
||||
hiddenPassword: ['', [Validators.required]],
|
||||
@ -104,7 +116,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
minConfValue: [{ value: null, disabled: true }]
|
||||
});
|
||||
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) {
|
||||
this.sendFundFormGroup.controls.selFeeRate.disable();
|
||||
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.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.reset();
|
||||
if (feeRate === 'customperkb' && !this.sendFundFormGroup.controls.flgMinConf.value) {
|
||||
@ -130,23 +142,23 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
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]) => {
|
||||
this.fiatConversion = selNode.settings.fiatConversion;
|
||||
this.amountUnits = selNode.settings.currencyUnits;
|
||||
this.appConfig = appConfig;
|
||||
});
|
||||
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[2])).
|
||||
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[4])).
|
||||
subscribe((nodeInfo: GetInfo) => {
|
||||
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 }) => {
|
||||
this.utxos = this.commonService.sortAscByKey(utxoBalancesSeletor.utxos?.filter((utxo) => utxo.status === 'confirmed'), 'value');
|
||||
this.logger.info(utxoBalancesSeletor);
|
||||
});
|
||||
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)).
|
||||
subscribe((action: any) => {
|
||||
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) {
|
||||
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({
|
||||
next: (data) => {
|
||||
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;
|
||||
if (this.transaction.satoshi && this.selAmountUnit !== event.value) {
|
||||
this.commonService.convertCurrency(+this.transaction.satoshi, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
||||
pipe(takeUntil(this.unSubs[6])).
|
||||
pipe(takeUntil(this.unSubs[8])).
|
||||
subscribe({
|
||||
next: (data) => {
|
||||
this.selAmountUnit = event.value;
|
||||
|
@ -58,7 +58,7 @@
|
||||
<div fxFlex="64" fxLayout="row" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '40' : '100'">
|
||||
<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">
|
||||
{{feeRateType.feeRateType}}
|
||||
</mat-option>
|
||||
|
@ -168,6 +168,13 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
} else {
|
||||
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 }));
|
||||
}
|
||||
|
||||
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() {
|
||||
this.unSubs.forEach((completeSub) => {
|
||||
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 { 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 { 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 { RTLState } from '../../store/rtl.state';
|
||||
@ -114,8 +114,8 @@ export class CLNEffects implements OnDestroy {
|
||||
}),
|
||||
catchError((err) => {
|
||||
const code = this.commonService.extractErrorCode(err);
|
||||
const msg = (code === 'ETIMEDOUT') ? 'Unable to Connect to Core Lightning Server.' : this.commonService.extractErrorMessage(err);
|
||||
this.router.navigate(['/login'], { state: { logoutReason: JSON.stringify(msg) } });
|
||||
const msg = (code === 503) ? 'Unable to Connect to Core Lightning Server.' : this.commonService.extractErrorMessage(err);
|
||||
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 });
|
||||
return of({ type: RTLActions.VOID });
|
||||
})
|
||||
@ -608,12 +608,12 @@ export class CLNEffects implements OnDestroy {
|
||||
ofType(CLNActions.DELETE_EXPIRED_INVOICE_CLN),
|
||||
mergeMap((action: { type: string, payload: number }) => {
|
||||
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(
|
||||
map((postRes: any) => {
|
||||
this.logger.info(postRes);
|
||||
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 };
|
||||
}),
|
||||
catchError((err: any) => {
|
||||
|
@ -6,7 +6,20 @@
|
||||
</div>
|
||||
<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-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()">
|
||||
<mat-form-field fxLayout="column" fxFlex="55">
|
||||
<mat-label>Bitcoin Address</mat-label>
|
||||
|
@ -5,7 +5,7 @@ import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
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 { GetInfo, OnChainBalance, SendPaymentOnChain } from '../../../shared/models/eclModels';
|
||||
@ -17,6 +17,8 @@ import { RTLState } from '../../../store/rtl.state';
|
||||
import { openSnackBar } from '../../../store/rtl.actions';
|
||||
import { sendOnchainFunds } from '../../store/ecl.actions';
|
||||
import { rootSelectedNode } from '../../../store/rtl.selector';
|
||||
import { DataService } from 'src/app/shared/services/data.service';
|
||||
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-ecl-on-chain-send-modal',
|
||||
@ -27,6 +29,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('form', { static: true }) form: any;
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public faInfoCircle = faInfoCircle;
|
||||
public selNode: Node | null;
|
||||
public addressTypes = [];
|
||||
public selectedAddress = ADDRESS_TYPES[1];
|
||||
@ -41,19 +44,27 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
public currConvertorRate = {};
|
||||
public unitConversionValue = 0;
|
||||
public currencyUnitFormats = CURRENCY_UNIT_FORMATS;
|
||||
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
|
||||
public amountError = 'Amount is Required.';
|
||||
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() {
|
||||
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.amountUnits = selNode.settings.currencyUnits;
|
||||
this.logger.info(selNode);
|
||||
});
|
||||
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)
|
||||
).
|
||||
subscribe((action: any) => {
|
||||
@ -74,7 +85,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
this.sendFundError = '';
|
||||
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).
|
||||
pipe(takeUntil(this.unSubs[2])).
|
||||
pipe(takeUntil(this.unSubs[3])).
|
||||
subscribe({
|
||||
next: (data) => {
|
||||
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;
|
||||
if (this.transaction.amount && this.selAmountUnit !== event.value) {
|
||||
this.commonService.convertCurrency(this.transaction.amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
||||
pipe(takeUntil(this.unSubs[3])).
|
||||
pipe(takeUntil(this.unSubs[4])).
|
||||
subscribe({
|
||||
next: (data) => {
|
||||
this.selAmountUnit = event.value;
|
||||
|
@ -6,8 +6,21 @@
|
||||
</div>
|
||||
<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-content class="padding-gap-x-large">
|
||||
<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-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()">
|
||||
<mat-form-field fxLayout="column" fxFlex.gt-sm="55">
|
||||
<mat-label>Bitcoin Address</mat-label>
|
||||
<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 { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
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 { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
|
||||
@ -23,6 +23,8 @@ import { RTLState } from '../../../store/rtl.state';
|
||||
import { isAuthorized, openSnackBar } from '../../../store/rtl.actions';
|
||||
import { setChannelTransaction } from '../../store/lnd.actions';
|
||||
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({
|
||||
selector: 'rtl-on-chain-send-modal',
|
||||
@ -35,6 +37,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
|
||||
@ViewChild('stepper', { static: false }) stepper: MatStepper;
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public faInfoCircle = faInfoCircle;
|
||||
public sweepAll = false;
|
||||
public selNode: Node | null;
|
||||
public appConfig: RTLConfiguration;
|
||||
@ -58,6 +61,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
public sendFundError = '';
|
||||
public flgValidated = false;
|
||||
public flgEditable = true;
|
||||
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
|
||||
public passwordFormLabel = 'Authenticate with your RTL password';
|
||||
public sendFundFormLabel = 'Sweep funds';
|
||||
public confirmFormLabel = 'Confirm sweep';
|
||||
@ -65,12 +69,13 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
passwordFormGroup: UntypedFormGroup;
|
||||
sendFundFormGroup: 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(
|
||||
public dialogRef: MatDialogRef<OnChainSendModalComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: OnChainSendFunds,
|
||||
private logger: LoggerService,
|
||||
private dataService: DataService,
|
||||
private store: Store<RTLState>,
|
||||
private rtlEffects: RTLEffects,
|
||||
private commonService: CommonService,
|
||||
@ -80,6 +85,13 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
private formBuilder: UntypedFormBuilder) { }
|
||||
|
||||
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.passwordFormGroup = this.formBuilder.group({
|
||||
hiddenPassword: ['', [Validators.required]],
|
||||
@ -92,7 +104,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
selTransType: ['1', Validators.required]
|
||||
});
|
||||
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') {
|
||||
this.sendFundFormGroup.controls.transactionBlocks.setValidators([Validators.required]);
|
||||
this.sendFundFormGroup.controls.transactionBlocks.setValue(null);
|
||||
@ -105,16 +117,16 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
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.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.amountUnits = selNode.settings.currencyUnits;
|
||||
this.logger.info(selNode);
|
||||
});
|
||||
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)).
|
||||
subscribe((action: any) => {
|
||||
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) {
|
||||
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({
|
||||
next: (data) => {
|
||||
this.selAmountUnit = CurrencyUnitEnum.SATS;
|
||||
@ -254,7 +266,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
if (this.transactionAmount && this.selAmountUnit !== event.value) {
|
||||
const amount = this.transactionAmount ? this.transactionAmount : 0;
|
||||
this.commonService.convertCurrency(amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
|
||||
pipe(takeUntil(this.unSubs[5])).
|
||||
pipe(takeUntil(this.unSubs[6])).
|
||||
subscribe({
|
||||
next: (data) => {
|
||||
this.selAmountUnit = event.value;
|
||||
|
@ -89,7 +89,7 @@
|
||||
<mat-option (click)="onUTXOClick(utxo)">View Info</mat-option>
|
||||
<mat-option (click)="onLabelUTXO(utxo)">Label</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>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -12,8 +12,8 @@ import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { ChannelRebalanceAlert } from '../../../../shared/models/alertData';
|
||||
import { LoggerService } from '../../../../shared/services/logger.service';
|
||||
import { CommonService } from '../../../../shared/services/common.service';
|
||||
import { Channel, QueryRoutes, ListInvoices } from '../../../../shared/models/lndModels';
|
||||
import { DEFAULT_INVOICE_EXPIRY, FEE_LIMIT_TYPES, LNDActions, PAGE_SIZE, ScreenSizeEnum, UI_MESSAGES } from '../../../../shared/services/consts-enums-functions';
|
||||
import { Channel, QueryRoutes, ListInvoices, SendPayment } from '../../../../shared/models/lndModels';
|
||||
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 { saveNewInvoice, sendPayment } from '../../../store/lnd.actions';
|
||||
@ -239,13 +239,29 @@ export class ChannelRebalanceComponent implements OnInit, OnDestroy {
|
||||
this.flgInvoiceGenerated = true;
|
||||
this.paymentRequest = payReq;
|
||||
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,
|
||||
feeLimitType: 'fixed', feeLimit: Math.ceil((+this.feeFormGroup.controls.feeLimit.value * +this.inputFormGroup.controls.rebalanceAmount.value) / 100),
|
||||
allowSelfPayment: true, lastHopPubkey: this.inputFormGroup.controls.selRebalancePeer.value.remote_pubkey, fromDialog: true } }));
|
||||
const payload: SendPayment = {
|
||||
uiMessage: UI_MESSAGES.NO_SPINNER,
|
||||
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 {
|
||||
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.NO_SPINNER, paymentReq: payReq, outgoingChannel: this.selChannel,
|
||||
feeLimitType: this.feeFormGroup.controls.selFeeLimitType.value.id, feeLimit: this.feeFormGroup.controls.feeLimit.value, allowSelfPayment: true,
|
||||
lastHopPubkey: this.inputFormGroup.controls.selRebalancePeer.value.remote_pubkey, fromDialog: true } }));
|
||||
const payload: SendPayment = {
|
||||
uiMessage: UI_MESSAGES.NO_SPINNER,
|
||||
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 }) => {
|
||||
this.store.dispatch(openSpinner({ payload: action.payload.uiMessage }));
|
||||
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.INITIATED } }));
|
||||
const queryHeaders = {};
|
||||
queryHeaders['paymentReq'] = action.payload.paymentReq;
|
||||
if (action.payload.paymentAmount) {
|
||||
queryHeaders['paymentAmount'] = action.payload.paymentAmount;
|
||||
}
|
||||
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(
|
||||
const queryParams = JSON.parse(JSON.stringify(action.payload));
|
||||
delete queryParams.uiMessage;
|
||||
delete queryParams.fromDialog;
|
||||
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.PAYMENTS_API + '/send', queryParams).pipe(
|
||||
map((sendRes: any) => {
|
||||
this.logger.info(sendRes);
|
||||
this.store.dispatch(closeSpinner({ payload: action.payload.uiMessage }));
|
||||
this.store.dispatch(updateLNDAPICallStatus({ payload: { action: 'SendPayment', status: APICallStatusEnum.COMPLETED } }));
|
||||
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 } }));
|
||||
return {
|
||||
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(fetchChannels());
|
||||
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 } }));
|
||||
} else {
|
||||
let msg = 'Payment Sent Successfully.';
|
||||
@ -751,7 +736,7 @@ export class LNDEffects implements OnDestroy {
|
||||
}),
|
||||
catchError((err: any) => {
|
||||
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.store.dispatch(fetchInvoices({ payload: { num_max_invoices: this.invoicesPageSettings?.recordsPerPage, reversed: true } }));
|
||||
return of({
|
||||
|
@ -195,7 +195,7 @@ export class LightningPaymentsComponent implements OnInit, AfterViewInit, OnDest
|
||||
subscribe((confirmRes) => {
|
||||
if (confirmRes) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
@ -224,7 +224,7 @@ export class LightningPaymentsComponent implements OnInit, AfterViewInit, OnDest
|
||||
pipe(take(1)).
|
||||
subscribe((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();
|
||||
}
|
||||
});
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<span class="page-title">Send Payment</span>
|
||||
</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-content class="padding-gap-x-large">
|
||||
<form #sendPaymentForm="ngForm" fxLayoutAlign="space-between stretch" fxLayout="column">
|
||||
@ -56,6 +56,7 @@
|
||||
</mat-autocomplete>
|
||||
<mat-error *ngIf="selectedChannelCtrl.errors?.notfound">Channel not found in the list.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle fxFlex="25" tabindex="8" color="primary" name="isAmp" [(ngModel)]="isAmp">AMP Payment</mat-slide-toggle>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
<div *ngIf="paymentError !== ''" fxFlex="100" class="alert alert-danger mt-1">
|
||||
@ -63,8 +64,8 @@
|
||||
<span *ngIf="paymentError !== ''">{{paymentError}}</span>
|
||||
</div>
|
||||
<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 mat-button id="sendBtn" color="primary" tabindex="3" (click)="onSendPayment()">Send Payment</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="10" (click)="onSendPayment()">Send Payment</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
|
@ -90,8 +90,8 @@ describe('LightningSendPaymentsComponent', () => {
|
||||
const sendButton = fixture.debugElement.nativeElement.querySelector('#sendBtn');
|
||||
sendButton.click();
|
||||
const expectedSendPaymentPayload: SendPayment = {
|
||||
uiMessage: UI_MESSAGES.SEND_PAYMENT, outgoingChannel: null, feeLimitType: 'none', feeLimit: null, fromDialog: true,
|
||||
paymentReq: 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq' +
|
||||
uiMessage: UI_MESSAGES.SEND_PAYMENT, outgoing_chan_ids: undefined, fee_limit_sat: 1000000, fromDialog: true, amp: false,
|
||||
payment_request: 'lntb4u1psvdzaypp555uks3f6774kl3vdy2dfr00j847pyxtrqelsdnczuxnmtqv99srsdpy23jhxarfdenjqmn8wfuzq3txvejkxarnyq' +
|
||||
'6qcqp2sp5xjzu6pz2sf8x4v8nmr58kjdm6k05etjfq9c96mwkhzl0g9j7sjkqrzjq28vwprzypa40c75myejm8s2aenkeykcnd7flvy9plp2yjq56nvrc8ss5c' +
|
||||
'qqqzgqqqqqqqlgqqqqqqgq9q9qy9qsqpt6u4rwfrck3tmpn54kdxjx3xdch62t5wype2f44mmlar07y749xt9elhfhf6dnlfk2tjwg3qpy8njh6remphfcc0630aq' +
|
||||
'38j0s3hrgpv4eel3'
|
||||
|
@ -9,8 +9,8 @@ import { MatDialogRef } from '@angular/material/dialog';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { Node } from '../../../shared/models/RTLconfig';
|
||||
import { PayRequest, Channel, ChannelsSummary, LightningBalance } from '../../../shared/models/lndModels';
|
||||
import { APICallStatusEnum, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, FEE_LIMIT_TYPES, LNDActions, UI_MESSAGES } from '../../../shared/services/consts-enums-functions';
|
||||
import { PayRequest, Channel, ChannelsSummary, LightningBalance, SendPayment } from '../../../shared/models/lndModels';
|
||||
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 { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { DataService } from '../../../shared/services/data.service';
|
||||
@ -43,6 +43,7 @@ export class LightningSendPaymentsComponent implements OnInit, OnDestroy {
|
||||
public activeChannels: Channel[] = [];
|
||||
public filteredMinAmtActvChannels: Channel[] = [];
|
||||
public selectedChannelCtrl = new UntypedFormControl();
|
||||
public isAmp = false;
|
||||
public feeLimit: number | null = null;
|
||||
public selFeeLimitType = FEE_LIMIT_TYPES[0];
|
||||
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') {
|
||||
this.zeroAmtInvoice = true;
|
||||
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 {
|
||||
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() {
|
||||
this.paymentDecoded = {};
|
||||
this.paymentRequest = '';
|
||||
this.isAmp = false;
|
||||
this.selectedChannelCtrl.setValue(null);
|
||||
this.filteredMinAmtActvChannels = this.activeChannels;
|
||||
if (this.filteredMinAmtActvChannels.length && this.filteredMinAmtActvChannels.length > 0) {
|
||||
|
@ -21,8 +21,8 @@
|
||||
</ng-container>
|
||||
<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()">
|
||||
<mat-icon *ngIf="scrollDirection === 'DOWN'" fxLayoutAlign="center center">arrow_downward</mat-icon>
|
||||
<mat-icon *ngIf="scrollDirection === 'UP'" fxLayoutAlign="center center">arrow_upward</mat-icon>
|
||||
<mat-icon *ngIf="scrollDirection === 'DOWN'" class="arrow-downward" fxLayoutAlign="center center">arrow_downward</mat-icon>
|
||||
<mat-icon *ngIf="scrollDirection === 'UP'" class="arrow-upward" fxLayoutAlign="center center">arrow_upward</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<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 class="foreground-secondary-text" fxLayout="row" fxFlex="100" fxLayoutAlign="start stretch" [ngSwitch]="obj.type">
|
||||
<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 *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>
|
||||
|
@ -552,13 +552,13 @@ export interface FetchPayments {
|
||||
export interface SendPayment {
|
||||
uiMessage: string;
|
||||
fromDialog: boolean;
|
||||
paymentReq: string;
|
||||
paymentAmount?: number;
|
||||
outgoingChannel?: Channel | null;
|
||||
feeLimitType?: string;
|
||||
feeLimit?: number | null;
|
||||
allowSelfPayment?: boolean;
|
||||
lastHopPubkey?: string;
|
||||
payment_request: string;
|
||||
amp: boolean;
|
||||
amt?: number;
|
||||
outgoing_chan_ids?: string[] | [];
|
||||
fee_limit_sat?: number | null;
|
||||
allow_self_payment?: boolean;
|
||||
last_hop_pubkey?: string;
|
||||
}
|
||||
|
||||
export interface GetNewAddress {
|
||||
|
@ -12,10 +12,11 @@ export function getPaginatorLabel(field: string) {
|
||||
}
|
||||
|
||||
export const HOUR_SECONDS = 3600;
|
||||
export const SECS_IN_YEAR = 31536000;
|
||||
|
||||
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';
|
||||
|
||||
@ -69,7 +70,7 @@ export const TRANS_TYPES = [
|
||||
export const FEE_LIMIT_TYPES = [
|
||||
{ id: 'none', name: 'No Fee Limit', placeholder: 'No Limit' },
|
||||
{ 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 = [
|
||||
@ -1396,3 +1397,14 @@ export function getSelectedCurrency(currencyID: string) {
|
||||
}
|
||||
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 {
|
||||
color: $primary-darker;
|
||||
}
|
||||
&.info-icon-text {
|
||||
&.info-icon-text, &.arrow-downward, &.arrow-upward {
|
||||
color: $foreground-text;
|
||||
}
|
||||
}
|
||||
|
@ -323,6 +323,10 @@
|
||||
&.info-icon-text {
|
||||
color: $foreground-secondary-text;
|
||||
}
|
||||
&.arrow-downward, &.arrow-upward {
|
||||
font-size: 150%;
|
||||
color: $background-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user