mirror of
https://github.com/Ride-The-Lightning/RTL.git
synced 2026-08-13 12:33:07 +02:00
parent
ee797481b4
commit
86d7d1ffa8
78 changed files with 3003 additions and 38 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -48,3 +48,4 @@ RTL-Multi-Node-Conf.json
|
|||
RTL.conf
|
||||
RTL-1.conf
|
||||
RTL-Multi-Node-Conf-1.json
|
||||
RTL-Config-for-BTC-Testing.json
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
<a href="https://snyk.io/test/github/Ride-The-Lightning/RTL"><img src="https://snyk.io/test/github/Ride-The-Lightning/RTL/badge.svg" alt="Known Vulnerabilities" data-canonical-src="https://snyk.io/test/github/Ride-The-Lightning/RTL" style="max-width:100%;"></a>
|
||||
[](https://github.com/DAVFoundation/captain-n3m0/blob/master/LICENSE)
|
||||
### Stable Release: v0.6.4
|
||||
### Stable Release: v0.6.8
|
||||
|
||||
**Intro** -- [Application Features](docs/Application_features.md) -- [Road Map](docs/Roadmap.md) -- [LND API Coverage](docs/LNDAPICoverage.md) -- [Application Configurations](docs/Application_configurations) -- [C-lightning](docs/C-Lightning-setup.md)
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
angular/7.75677e99073742b9f2a4.js
Normal file
1
angular/7.75677e99073742b9f2a4.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -12,8 +12,8 @@
|
|||
<link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta name="msapplication-TileColor" content="#da532c">
|
||||
<meta name="theme-color" content="#ffffff">
|
||||
<link rel="stylesheet" href="styles.d944e28a09351c044329.css"></head>
|
||||
<link rel="stylesheet" href="styles.f04e677f00c07a00a61f.css"></head>
|
||||
<body>
|
||||
<rtl-app></rtl-app>
|
||||
<script src="runtime.d324cb9ce055cdfad42a.js" defer></script><script src="polyfills-es5.2ae7ace69949ec0a3f00.js" nomodule defer></script><script src="polyfills.3302e98effc5e50a54c2.js" defer></script><script src="main.6078c58378efda053404.js" defer></script></body>
|
||||
<script src="runtime.f077d9a5a55b365fad3b.js" defer></script><script src="polyfills-es5.2ae7ace69949ec0a3f00.js" nomodule defer></script><script src="polyfills.3302e98effc5e50a54c2.js" defer></script><script src="main.0b4165691223413d29d4.js" defer></script></body>
|
||||
</html>
|
||||
|
|
|
|||
1
angular/main.0b4165691223413d29d4.js
Normal file
1
angular/main.0b4165691223413d29d4.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 +1 @@
|
|||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"7265422b6fd577fc6798",6:"ba6a125e1b449192ae1c",7:"7189929795574f12346d"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"55621ac84bdf6c786e25",6:"83056549d4d88d5a89c1",7:"75677e99073742b9f2a4"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var l=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);
|
||||
File diff suppressed because one or more lines are too long
1
angular/styles.f04e677f00c07a00a61f.css
Normal file
1
angular/styles.f04e677f00c07a00a61f.css
Normal file
File diff suppressed because one or more lines are too long
2
app.js
2
app.js
|
|
@ -26,6 +26,7 @@ const payReqRoutes = require("./routes/lnd/payReq");
|
|||
const paymentsRoutes = require("./routes/lnd/payments");
|
||||
const invoiceRoutes = require("./routes/lnd/invoices");
|
||||
const switchRoutes = require("./routes/lnd/switch");
|
||||
const loopRoutes = require('./routes/lnd/loop');
|
||||
const messageRoutes = require("./routes/lnd/message");
|
||||
|
||||
const infoCLRoutes = require("./routes/c-lightning/getInfo");
|
||||
|
|
@ -75,6 +76,7 @@ app.use(apiLNDRoot + "payreq", payReqRoutes);
|
|||
app.use(apiLNDRoot + "payments", paymentsRoutes);
|
||||
app.use(apiLNDRoot + "invoices", invoiceRoutes);
|
||||
app.use(apiLNDRoot + "switch", switchRoutes);
|
||||
app.use(apiLNDRoot + "loop", loopRoutes);
|
||||
app.use(apiLNDRoot + "message", messageRoutes);
|
||||
|
||||
app.use(apiCLRoot + "getinfo", infoCLRoutes);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,10 @@ common.secret_key = crypto.randomBytes(64).toString('hex');
|
|||
common.nodes = [];
|
||||
common.selectedNode = {};
|
||||
|
||||
common.getSelSwapServerUrl = () => {
|
||||
return common.selectedNode.swap_server_url;
|
||||
};
|
||||
|
||||
common.getSelLNServerUrl = () => {
|
||||
return common.selectedNode.ln_server_url;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ connect.setDefaultConfig = () => {
|
|||
channelBackupPath: channelBackupPath,
|
||||
enableLogging: false,
|
||||
lnServerUrl: "https://localhost:8080/v1",
|
||||
swapServerUrl: "https://localhost:8081/v1",
|
||||
fiatConversion: false
|
||||
}
|
||||
}
|
||||
|
|
@ -152,6 +153,7 @@ connect.validateNodeConfig = (config) => {
|
|||
} else {
|
||||
common.nodes[idx].config_path = '';
|
||||
}
|
||||
common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL ? process.env.SWAP_SERVER_URL : (node.Settings.swapServerUrl) ? node.Settings.swapServerUrl.trim() : '';
|
||||
common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
|
||||
common.nodes[idx].enable_logging = (node.Settings.enableLogging) ? !!node.Settings.enableLogging : false;
|
||||
common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : common.rtl_conf_file_path + common.path_separator + 'backup' + common.path_separator + 'node-' + node.index;
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ exports.getRTLConfig = (req, res, next) => {
|
|||
settings.bitcoindConfigPath = node.bitcoind_config_path;
|
||||
settings.enableLogging = node.enable_logging ? !!node.enable_logging : false;
|
||||
settings.lnServerUrl = node.ln_server_url;
|
||||
settings.swapServerUrl = node.swap_server_url;
|
||||
settings.channelBackupPath = node.channel_backup_path;
|
||||
settings.currencyUnit = node.currency_unit;
|
||||
nodesArr.push({
|
||||
|
|
|
|||
267
controllers/lnd/loop.js
Normal file
267
controllers/lnd/loop.js
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
var request = require('request-promise');
|
||||
var common = require('../../common');
|
||||
var logger = require('../logger');
|
||||
var options = {};
|
||||
var swapServerUrl = '';
|
||||
|
||||
exports.loopOut = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop Out Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/out';
|
||||
let body = {
|
||||
amt: req.body.amount,
|
||||
sweep_conf_target: req.body.targetConf,
|
||||
max_swap_routing_fee: req.body.swapRoutingFee,
|
||||
max_miner_fee: req.body.minerFee,
|
||||
max_prepay_routing_fee: req.body.prepayRoutingFee,
|
||||
max_prepay_amt: req.body.prepayAmt,
|
||||
max_swap_fee: req.body.swapFee,
|
||||
swap_publication_deadline: req.body.swapPublicationDeadline
|
||||
};
|
||||
if (req.body.chanId !== '') { body['loop_out_channel'] = req.body.chanId; }
|
||||
if (req.body.destAddress !== '') { body['dest'] = req.body.destAddress; }
|
||||
options.body = JSON.stringify(body);
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Body: ' + options.body});
|
||||
request.post(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out: ' + JSON.stringify(body)});
|
||||
if(!body || body.error) {
|
||||
res.status(500).json({
|
||||
message: 'Loop Out Failed!',
|
||||
error: (!body) ? 'Error From Server!' : body.error.message
|
||||
});
|
||||
} else {
|
||||
res.status(201).json(body);
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
logger.error({fileName: 'Loop Out', lineNum: 33, msg: 'Loop Out Failed: ' + JSON.stringify(err.error)});
|
||||
return res.status(500).json({
|
||||
message: "Loop Out Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopOutTerms = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop Out Terms Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/out/terms';
|
||||
request(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Terms: ' + JSON.stringify(body)});
|
||||
res.status(200).json(body);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop Out Terms Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopOutQuote = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop Out Quote Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/out/quote/' + req.params.amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Quote URL: ' + options.url});
|
||||
request(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Quote: ' + body});
|
||||
body = JSON.parse(body);
|
||||
body.amount = +req.params.amount;
|
||||
body.swap_payment_dest = body.swap_payment_dest ? Buffer.from(body.swap_payment_dest, 'base64').toString('hex') : '';
|
||||
res.status(200).json(body);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop Out Quote Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopOutTermsAndQuotes = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop Out Terms And Quotes Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/out/terms';
|
||||
request(options).then(function(terms) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Terms: ' + JSON.stringify(terms)});
|
||||
const options1 = {}; const options2 = {};
|
||||
terms = JSON.parse(terms);
|
||||
options1.url = swapServerUrl + '/loop/out/quote/' + terms.min_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
|
||||
options2.url = swapServerUrl + '/loop/out/quote/' + terms.max_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Min Quote Options: ' + JSON.stringify(options1)});
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Max Quote Options: ' + JSON.stringify(options2)});
|
||||
Promise.all([request(options1), request(options2)]).then(function(values) {
|
||||
values[0] = JSON.parse(values[0]);
|
||||
values[1] = JSON.parse(values[1]);
|
||||
values[0].amount = +terms.min_swap_amount;
|
||||
values[1].amount = +terms.max_swap_amount;
|
||||
values[0].swap_payment_dest = values[0].swap_payment_dest ? Buffer.from(values[0].swap_payment_dest, 'base64').toString('hex') : '';
|
||||
values[1].swap_payment_dest = values[1].swap_payment_dest ? Buffer.from(values[1].swap_payment_dest, 'base64').toString('hex') : '';
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Quotes 1: ' + JSON.stringify(values[0])});
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Out Quotes 2: ' + JSON.stringify(values[1])});
|
||||
res.status(200).json(values);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop Out Quotes Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop Out Terms Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopIn = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop In Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/in';
|
||||
options.body = JSON.stringify({
|
||||
amt: req.body.amount,
|
||||
max_swap_fee: req.body.swapFee,
|
||||
max_miner_fee: req.body.minerFee
|
||||
// last_hop: req.body.lastHop,
|
||||
// external_htlc: req.body.externalHtlc
|
||||
});
|
||||
request.post(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In: ' + JSON.stringify(body)});
|
||||
if(!body || body.error) {
|
||||
res.status(500).json({
|
||||
message: 'Loop In Failed!',
|
||||
error: (!body) ? 'Error From Server!' : body.error.message
|
||||
});
|
||||
} else {
|
||||
res.status(201).json(body);
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
logger.error({fileName: 'Loop In', lineNum: 134, msg: 'Loop In Failed: ' + JSON.stringify(err.error)});
|
||||
return res.status(500).json({
|
||||
message: "Loop In Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopInTerms = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop In Terms Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/in/terms';
|
||||
request(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Terms: ' + JSON.stringify(body)});
|
||||
res.status(200).json(body);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop In Terms Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopInQuote = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop In Quote Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/in/quote/' + req.params.amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Quote Options: ' + options.url});
|
||||
request(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Quote: ' + JSON.stringify(body)});
|
||||
body = JSON.parse(body);
|
||||
body.amount = +req.params.amount;
|
||||
body.swap_payment_dest = body.swap_payment_dest ? Buffer.from(body.swap_payment_dest, 'base64').toString('hex') : '';
|
||||
res.status(200).json(body);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop In Quote Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.loopInTermsAndQuotes = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop In Terms And Quotes Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/in/terms';
|
||||
request(options).then(function(terms) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Terms: ' + JSON.stringify(terms)});
|
||||
const options1 = {}; const options2 = {};
|
||||
terms = JSON.parse(terms);
|
||||
options1.url = swapServerUrl + '/loop/in/quote/' + terms.min_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
|
||||
options2.url = swapServerUrl + '/loop/in/quote/' + terms.max_swap_amount + '?conf_target=' + (req.query.targetConf ? req.query.targetConf : '2') + '&swap_publication_deadline=' + req.query.swapPublicationDeadline;
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Min Quote Options: ' + JSON.stringify(options1)});
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Max Quote Options: ' + JSON.stringify(options2)});
|
||||
Promise.all([request(options1), request(options2)]).then(function(values) {
|
||||
values[0] = JSON.parse(values[0]);
|
||||
values[1] = JSON.parse(values[1]);
|
||||
values[0].amount = +terms.min_swap_amount;
|
||||
values[1].amount = +terms.max_swap_amount;
|
||||
values[0].swap_payment_dest = values[0].swap_payment_dest ? Buffer.from(values[0].swap_payment_dest, 'base64').toString('hex') : '';
|
||||
values[1].swap_payment_dest = values[1].swap_payment_dest ? Buffer.from(values[1].swap_payment_dest, 'base64').toString('hex') : '';
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Quotes 1: ' + JSON.stringify(values[0])});
|
||||
logger.info({fileName: 'Loop', msg: 'Loop In Quotes 2: ' + JSON.stringify(values[1])});
|
||||
res.status(200).json(values);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop In Quotes Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop In Terms Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.swaps = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop Out Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/swaps';
|
||||
request(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Swaps: ' + body});
|
||||
body = JSON.parse(body);
|
||||
if (body.swaps && body.swaps.length > 0) {
|
||||
body.swaps.forEach(swap => {
|
||||
swap.initiation_time_str = (!swap.initiation_time) ? '' : common.convertTimestampToDate(Math.round(swap.initiation_time/1000000000));
|
||||
swap.last_update_time_str = (!swap.last_update_time) ? '' : common.convertTimestampToDate(Math.round(swap.last_update_time/1000000000));
|
||||
});
|
||||
body.swaps = common.sortDescByKey(body.swaps, 'initiation_time');
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Swaps after Sort: ' + JSON.stringify(body)});
|
||||
}
|
||||
res.status(200).json(body.swaps);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop Swaps Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
exports.swap = (req, res, next) => {
|
||||
swapServerUrl = common.getSelSwapServerUrl();
|
||||
if(swapServerUrl === '') { return res.status(500).json({message: "Loop Out Failed!",error: { message: 'Loop Server URL is missing in the configuration.'}}); }
|
||||
options.url = swapServerUrl + '/loop/swap/' + req.params.id;
|
||||
request(options).then(function (body) {
|
||||
logger.info({fileName: 'Loop', msg: 'Loop Swap: ' + body});
|
||||
body = JSON.parse(body);
|
||||
body.initiation_time_str = (!body.initiation_time) ? '' : common.convertTimestampToDate(Math.round(body.initiation_time/1000000000));
|
||||
body.last_update_time_str = (!body.last_update_time) ? '' : common.convertTimestampToDate(Math.round(body.last_update_time/1000000000));
|
||||
res.status(200).json(body);
|
||||
})
|
||||
.catch((err) => {
|
||||
return res.status(500).json({
|
||||
message: "Loop Swap Failed!",
|
||||
error: err.error.error ? err.error.error : err.error
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -42,6 +42,7 @@ parameters have `default` values for initial setup and can be updated after RTL
|
|||
PORT (port number for the rtl node server, default 3000, Required)
|
||||
LN_IMPLEMENTATION (LND, CLT. Default 'LND', Required)
|
||||
LN_SERVER_URL (LND server URL for REST APIs, default https://localhost:8080/v1) OR LN_SERVER_URL (LN server URL for LNP REST APIs) (Required)
|
||||
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://localhost:8081/v1) (Optional)
|
||||
CONFIG_PATH (Full path of the lnd.conf file including the file name) OR CONFIG_PATH (Full path of the LNP .conf file including the file name) (Optional)
|
||||
MACAROON_PATH (Path for the folder containing 'admin.macaroon' file, Required)
|
||||
RTL_SSO (1 - single sign on via an external cookie, 0 - stand alone RTL authentication, Optional)
|
||||
|
|
|
|||
2
package-lock.json
generated
2
package-lock.json
generated
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rtl",
|
||||
"version": "0.6.8-beta",
|
||||
"version": "0.7.0-beta",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "rtl",
|
||||
"version": "0.6.8-beta",
|
||||
"version": "0.7.0-beta",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
|
|
|
|||
17
routes/lnd/loop.js
Normal file
17
routes/lnd/loop.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
const LoopController = require("../../controllers/lnd/loop");
|
||||
const express = require("express");
|
||||
const router = express.Router();
|
||||
const authCheck = require("../authCheck");
|
||||
|
||||
router.get("/in/terms", authCheck, LoopController.loopInTerms);
|
||||
router.get("/in/quote/:amount", authCheck, LoopController.loopInQuote);
|
||||
router.get("/in/termsAndQuotes", authCheck, LoopController.loopInTermsAndQuotes);
|
||||
router.post("/in", authCheck, LoopController.loopIn);
|
||||
router.get("/out/terms", authCheck, LoopController.loopOutTerms);
|
||||
router.get("/out/quote/:amount", authCheck, LoopController.loopOutQuote);
|
||||
router.get("/out/termsAndQuotes", authCheck, LoopController.loopOutTermsAndQuotes);
|
||||
router.post("/out", authCheck, LoopController.loopOut);
|
||||
router.get("/swaps", authCheck, LoopController.swaps);
|
||||
router.get("/swap/:id", authCheck, LoopController.swap);
|
||||
|
||||
module.exports = router;
|
||||
|
|
@ -18,6 +18,7 @@ import { AppComponent } from './app.component';
|
|||
import { environment } from '../environments/environment';
|
||||
import { SessionService } from './shared/services/session.service';
|
||||
import { CommonService } from './shared/services/common.service';
|
||||
import { LoopService } from './shared/services/loop.service';
|
||||
import { DataService } from './shared/services/data.service';
|
||||
import { LoggerService, ConsoleLoggerService } from './shared/services/logger.service';
|
||||
import { AuthGuard } from './shared/services/auth.guard';
|
||||
|
|
@ -40,6 +41,7 @@ import { SpinnerDialogComponent } from './shared/components/data-modal/spinner-d
|
|||
import { AlertMessageComponent } from './shared/components/data-modal/alert-message/alert-message.component';
|
||||
import { ConfirmationMessageComponent } from './shared/components/data-modal/confirmation-message/confirmation-message.component';
|
||||
import { ErrorMessageComponent } from './shared/components/data-modal/error-message/error-message.component';
|
||||
import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -67,7 +69,8 @@ import { ErrorMessageComponent } from './shared/components/data-modal/error-mess
|
|||
AlertMessageComponent,
|
||||
ConfirmationMessageComponent,
|
||||
ErrorMessageComponent,
|
||||
CloseChannelComponent
|
||||
CloseChannelComponent,
|
||||
LoopModalComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CLInvoiceInformationComponent,
|
||||
|
|
@ -81,7 +84,8 @@ import { ErrorMessageComponent } from './shared/components/data-modal/error-mess
|
|||
AlertMessageComponent,
|
||||
ConfirmationMessageComponent,
|
||||
ErrorMessageComponent,
|
||||
CloseChannelComponent
|
||||
CloseChannelComponent,
|
||||
LoopModalComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: LoggerService, useClass: ConsoleLoggerService },
|
||||
|
|
@ -89,7 +93,7 @@ import { ErrorMessageComponent } from './shared/components/data-modal/error-mess
|
|||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true, autoFocus: true, disableClose: true, role: 'dialog', width: '55%' } },
|
||||
CommonService, AuthGuard, SessionService, DataService
|
||||
CommonService, AuthGuard, SessionService, DataService, LoopService
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@
|
|||
<span class="dashboard-capacity-header" matTooltip="{{channel.remote_alias || channel.remote_pubkey}}" matTooltipDisabled="{{(channel.remote_alias || channel.remote_pubkey).length < 26}}">{{(channel.remote_alias || channel.remote_pubkey) | slice:0:24}}{{(channel.remote_alias || channel.remote_pubkey).length > 25 ? '...' : ''}}</span>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between stretch" class="w-100">
|
||||
<mat-hint *ngIf="direction === 'In'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.remote_balance || 0 | number}} Sats</mat-hint>
|
||||
<mat-hint fxFlex="100" *ngIf="direction === 'Out'" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.local_balance || 0 | number}} Sats</mat-hint>
|
||||
<mat-hint [fxFlex]="showLoop ? '85' : '100'" *ngIf="direction === 'Out'" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.local_balance || 0 | number}} Sats</mat-hint>
|
||||
<button *ngIf="showLoop && direction === 'Out'" fxLayout="column" fxLayoutAlign="center end" class="button-link-dashboard" color="primary" fxFlex="15" mat-button aria-label="Loop Out" (click)="onLoopOut(channel)">Loop Out</button>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="direction === 'In'" fxFlex="100" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.remote_balance || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
<mat-progress-bar *ngIf="direction === 'Out'" fxFlex="100" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.local_balance || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { SwapTypeEnum } from '../../../shared/services/consts-enums-functions';
|
||||
import { Channel } from '../../../shared/models/lndModels';
|
||||
import { LoopModalComponent } from '../../loop/loop-modal/loop-modal.component';
|
||||
import { LoopService } from '../../../shared/services/loop.service';
|
||||
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
import * as RTLActions from '../../../store/rtl.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-channel-liquidity-info',
|
||||
|
|
@ -16,17 +21,40 @@ export class ChannelLiquidityInfoComponent implements OnInit, OnDestroy {
|
|||
@Input() direction: string;
|
||||
@Input() totalLiquidity: number;
|
||||
@Input() allChannels: Channel[];
|
||||
public showLoop: boolean;
|
||||
private targetConf = 6;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private router: Router, private store: Store<fromRTLReducer.RTLState>) {}
|
||||
constructor(private router: Router, private loopService: LoopService, private store: Store<fromRTLReducer.RTLState>) {}
|
||||
|
||||
ngOnInit() {}
|
||||
ngOnInit() {
|
||||
this.store.select('lnd')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore) => {
|
||||
this.showLoop = (rtlStore.nodeSettings.swapServerUrl && rtlStore.nodeSettings.swapServerUrl.trim() !== '') ? true : false;
|
||||
});
|
||||
}
|
||||
|
||||
goToChannels() {
|
||||
this.router.navigateByUrl('/lnd/peerschannels');
|
||||
}
|
||||
|
||||
onLoopOut(channel: Channel) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Getting Terms and Quotes...'));
|
||||
this.loopService.getLoopOutTermsAndQuotes(this.targetConf)
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe(response => {
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ minHeight: '56rem', data: {
|
||||
channel: channel,
|
||||
minQuote: response[0],
|
||||
maxQuote: response[1],
|
||||
direction: SwapTypeEnum.LOOP_OUT,
|
||||
component: LoopModalComponent
|
||||
}}));
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export class HomeComponent implements OnInit, OnDestroy {
|
|||
public inactiveChannels = 0;
|
||||
public channelBalances = {localBalance: 0, remoteBalance: 0, balancedness: '0'};
|
||||
public selNode: SelNodeChild = {};
|
||||
public showLoop = false;
|
||||
public fees: Fees;
|
||||
public information: GetInfo = {};
|
||||
public balances = { onchain: -1, lightning: -1, total: 0 };
|
||||
|
|
@ -127,6 +128,7 @@ export class HomeComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
});
|
||||
this.selNode = rtlStore.nodeSettings;
|
||||
this.showLoop = (this.selNode.swapServerUrl && this.selNode.swapServerUrl.trim() !== '') ? true : false;
|
||||
this.information = rtlStore.information;
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = ( this.information.identity_pubkey) ? false : true;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@ import { ChannelStatusInfoComponent } from './home/channel-status-info/channel-s
|
|||
import { ChannelCapacityInfoComponent } from './home/channel-capacity-info/channel-capacity-info.component';
|
||||
import { ChannelLiquidityInfoComponent } from './home/channel-liquidity-info/channel-liquidity-info.component';
|
||||
import { NetworkInfoComponent } from './network-info/network-info.component';
|
||||
import { LoopComponent } from './loop/loop.component';
|
||||
import { SwapsComponent } from './loop/swaps/swaps.component';
|
||||
|
||||
import { LNDUnlockedGuard } from '../shared/services/auth.guard';
|
||||
|
||||
|
|
@ -91,7 +93,9 @@ import { LNDUnlockedGuard } from '../shared/services/auth.guard';
|
|||
ChannelStatusInfoComponent,
|
||||
ChannelCapacityInfoComponent,
|
||||
ChannelLiquidityInfoComponent,
|
||||
NetworkInfoComponent
|
||||
NetworkInfoComponent,
|
||||
LoopComponent,
|
||||
SwapsComponent
|
||||
],
|
||||
providers: [
|
||||
LNDUnlockedGuard
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import { LookupsComponent } from './lookups/lookups.component';
|
|||
import { RoutingComponent } from './routing/routing.component';
|
||||
import { OnChainComponent } from './on-chain/on-chain.component';
|
||||
import { NetworkInfoComponent } from './network-info/network-info.component';
|
||||
import { LoopComponent } from './loop/loop.component';
|
||||
import { BackupComponent } from './backup/backup.component';
|
||||
import { SignVerifyMessageComponent } from './sign-verify-message/sign-verify-message.component';
|
||||
import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
|
||||
|
|
@ -29,6 +30,7 @@ export const LndRoutes: Routes = [
|
|||
{ path: 'routing', component: RoutingComponent, canActivate: [LNDUnlockedGuard] },
|
||||
{ path: 'lookups', component: LookupsComponent, canActivate: [LNDUnlockedGuard] },
|
||||
{ path: 'network', component: NetworkInfoComponent, canActivate: [LNDUnlockedGuard] },
|
||||
{ path: 'loop', component: LoopComponent, canActivate: [LNDUnlockedGuard] },
|
||||
{ path: '**', component: NotFoundComponent },
|
||||
{ path: 'rates', redirectTo: 'network' }
|
||||
]}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,5 @@
|
|||
svg {
|
||||
height: 50%;
|
||||
min-height: 50%;
|
||||
max-height: 50%;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoopInInfoGraphicsComponent } from './info-graphics.component';
|
||||
|
||||
describe('LoopInInfoGraphicsComponent', () => {
|
||||
let component: LoopInInfoGraphicsComponent;
|
||||
let fixture: ComponentFixture<LoopInInfoGraphicsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoopInInfoGraphicsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoopInInfoGraphicsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
|
||||
|
||||
import { sliderAnimation } from '../../../shared/animation/slider-animation';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-loop-in-info-graphics',
|
||||
templateUrl: './info-graphics.component.html',
|
||||
styleUrls: ['./info-graphics.component.scss'],
|
||||
animations: [sliderAnimation]
|
||||
})
|
||||
export class LoopInInfoGraphicsComponent implements OnInit {
|
||||
@Input() animationDirection = 'forward';
|
||||
@Input() stepNumber = 1;
|
||||
@Output() stepNumberChange = new EventEmitter();
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onSwipe(event: any) {
|
||||
if(event.direction === 2 && this.stepNumber < 5) {
|
||||
this.stepNumber++;
|
||||
this.animationDirection = 'forward';
|
||||
this.stepNumberChange.emit(this.stepNumber);
|
||||
} else if(event.direction === 4 && this.stepNumber > 1) {
|
||||
this.stepNumber--;
|
||||
this.animationDirection = 'backward';
|
||||
this.stepNumberChange.emit(this.stepNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/app/lnd/loop/loop-modal/loop-modal.component.html
Normal file
136
src/app/lnd/loop/loop-modal/loop-modal.component.html
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" *ngIf="!flgShowInfo" [@opacityAnimation]>
|
||||
<div fxFlex="100" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '83' : '91'" fxLayoutAlign="start start" class="padding-gap-x-large"><span class="page-title">{{channel ? ('Channel ' + loopDirectionCaption) : loopDirectionCaption}}</span></div>
|
||||
<div [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '17' : '9'" fxLayoutAlign="space-between end">
|
||||
<button tabindex="21" class="btn-close-x p-0" (click)="showInfo()" mat-button>?</button>
|
||||
<button tabindex="22" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button>
|
||||
</div>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-5px">
|
||||
<div fxLayout="column">
|
||||
<div *ngIf="channel" class="padding-gap-large" fxLayout="row wrap" fxLayoutAlign="space-between stretch">
|
||||
<p fxFlex="40"><strong>Channel Peer: </strong>{{channel.remote_alias | titlecase}}</p>
|
||||
<p fxFlex="30"><strong>Channel ID: </strong>{{channel.chan_id}}</p>
|
||||
<p fxFlex="30"></p>
|
||||
</div>
|
||||
<mat-vertical-stepper [linear]="true" #stepper (selectionChange)="stepSelectionChanged($event)">
|
||||
<mat-step [stepControl]="inputFormGroup" [editable]="flgEditable">
|
||||
<form [formGroup]="inputFormGroup" fxLayout="column" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between" class="my-1">
|
||||
<ng-template matStepLabel>{{inputFormLabel}}</ng-template>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<rtl-loop-quote [quote]="minQuote" [termCaption]="'min'" [panelExpanded]="false" [showPanel]="true"></rtl-loop-quote>
|
||||
<rtl-loop-quote [quote]="maxQuote" [termCaption]="'max'" [panelExpanded]="false" [showPanel]="true"></rtl-loop-quote>
|
||||
</div>
|
||||
<div fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between center" class="mt-1">
|
||||
<mat-form-field [fxFlex]="direction === swapTypeEnum.LOOP_OUT ? '30' : '48'">
|
||||
<input autoFocus matInput placeholder="Amount" type="number" step="1000" tabindex="1" formControlName="amount" required>
|
||||
<mat-hint>Range: {{minQuote.amount | number}}-{{maxQuote.amount | number}}</mat-hint>
|
||||
<span matSuffix>Sats</span>
|
||||
<mat-error *ngIf="inputFormGroup.controls.amount.errors?.required">Amount is required.</mat-error>
|
||||
<mat-error *ngIf="inputFormGroup.controls.amount.errors?.min">Amount must be greater than or equal to {{minQuote.amount | number}}.</mat-error>
|
||||
<mat-error *ngIf="inputFormGroup.controls.amount.errors?.max">Amount must be less than or equal to {{maxQuote.amount | number}}.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field [fxFlex]="direction === swapTypeEnum.LOOP_OUT ? '20' : '48'">
|
||||
<input matInput placeholder="Sweep Confirmation Target" type="number" step="1" tabindex="2" formControlName="sweepConfTarget" required>
|
||||
<mat-error *ngIf="inputFormGroup.controls.sweepConfTarget.errors?.required">Confirmation target is required.</mat-error>
|
||||
<mat-error *ngIf="inputFormGroup.controls.sweepConfTarget.errors?.min">Confirmation target must be a positive number.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field *ngIf="direction === swapTypeEnum.LOOP_OUT" fxFlex="30">
|
||||
<input matInput placeholder="Max Off-chain Routing Fee (%)" type="number" step="1" tabindex="3" formControlName="routingFeePercent" required>
|
||||
<mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.required">Percentage is required.</mat-error>
|
||||
<mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.min">Percentage must be a positive number.</mat-error>
|
||||
<mat-error *ngIf="inputFormGroup.controls.routingFeePercent.errors?.max">Percentage must be less than or equal to {{maxRoutingFeePercentage}}.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle *ngIf="direction === swapTypeEnum.LOOP_OUT" fxFlex="15" tabindex="4" color="primary" formControlName="fast">Fast</mat-slide-toggle>
|
||||
</div>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
|
||||
<button mat-stroked-button color="primary" tabindex="5" type="button" (click)="onEstimateQuote()">Estimate Quote</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="quoteFormGroup" [editable]="flgEditable">
|
||||
<form [formGroup]="quoteFormGroup" fxLayout="column" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between" class="my-1">
|
||||
<ng-template matStepLabel>{{quoteFormLabel}}</ng-template>
|
||||
<rtl-loop-quote [quote]="quote" [showPanel]="false"></rtl-loop-quote>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
|
||||
<button *ngIf="direction === swapTypeEnum.LOOP_OUT" mat-stroked-button color="primary" tabindex="6" type="button" matStepperNext>Next</button>
|
||||
<button *ngIf="direction === swapTypeEnum.LOOP_IN" mat-stroked-button color="primary" tabindex="7" type="button" (click)="onLoop()">Initiate {{loopDirectionCaption}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step *ngIf="direction === swapTypeEnum.LOOP_OUT" [stepControl]="addressFormGroup" [editable]="flgEditable">
|
||||
<form [formGroup]="addressFormGroup" fxLayout="column" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between" class="my-1">
|
||||
<ng-template matStepLabel>{{addressFormLabel}}</ng-template>
|
||||
<div fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between stretch" class="mt-1">
|
||||
<mat-radio-group color="primary" name="addressType" (change)="onAddressTypeChange($event)" formControlName="addressType" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<mat-radio-button fxFlex="48" tabindex="8" value="local">Node Local Address</mat-radio-button>
|
||||
<mat-radio-button fxFlex="48" tabindex="9" value="external">External Address</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<mat-form-field fxFlex="100" class="mt-1">
|
||||
<input matInput placeholder="Address" tabindex="10" formControlName="address" [required]="addressFormGroup.controls.addressType.value === 'external'">
|
||||
<mat-error *ngIf="addressFormGroup.controls.address.errors?.required">Address is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
|
||||
<button mat-stroked-button color="primary" tabindex="11" type="button" (click)="onLoop()">Initiate {{loopDirectionCaption}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="statusFormGroup">
|
||||
<form [formGroup]="statusFormGroup" fxLayout="column" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between" class="my-1">
|
||||
<ng-template matStepLabel>{{loopDirectionCaption}} Status</ng-template>
|
||||
<div fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<mat-expansion-panel class="flat-expansion-panel" fxFlex="100" [expanded]="loopStatus">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<span fxLayoutAlign="start center" fxFlex="100">{{(!loopStatus) ? ('Waiting for ' + loopDirectionCaption + ' request...') : (loopStatus.id_bytes) ? (loopDirectionCaption + ' request details') : (loopDirectionCaption + ' error details')}}<mat-icon *ngIf="loopStatus" class="ml-1 icon-small">{{(loopStatus && loopStatus?.id_bytes) ? 'check' : 'close'}}</mat-icon></span>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div *ngIf="!loopStatus; else loopStatusBlock"></div>
|
||||
</mat-expansion-panel>
|
||||
<mat-progress-bar fxFlex="100" *ngIf="!loopStatus" color="primary" mode="indeterminate"></mat-progress-bar>
|
||||
</div>
|
||||
<h4 *ngIf="loopStatus" fxLayoutAlign="start" class="font-bold-500 mt-2">{{(loopStatus && loopStatus.error) ? (loopDirectionCaption + ' failed.') : (loopStatus && loopStatus.id_bytes && channel) ? (loopDirectionCaption + ' request placed successfully. Go to loop to check it\'s status.') : (loopDirectionCaption + ' request placed successfully.')}}</h4>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
|
||||
<button *ngIf="loopStatus && loopStatus.id_bytes && channel" mat-flat-button color="primary" tabindex="12" type="button" (click)="goToLoop()">Check Status</button>
|
||||
<button *ngIf="loopStatus && (loopStatus.error || !loopStatus.id_bytes)" mat-flat-button color="primary" tabindex="13" type="button" (click)="stepper.reset()">Start Again</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-vertical-stepper>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end end">
|
||||
<button mat-stroked-button color="primary" tabindex="14" type="button" [mat-dialog-close]="false" default>Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #loopStatusBlock>
|
||||
<rtl-loop-status fxLayout="column" [loopStatus]="loopStatus"></rtl-loop-status>
|
||||
</ng-template>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" *ngIf="flgShowInfo" [@opacityAnimation] class="info-graphics-container">
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxFlex="5" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div fxFlex="95" fxLayoutAlign="start start" class="padding-gap-x-large"><span class="page-title"></span></div>
|
||||
<div fxFlex="8" fxLayoutAlign="end center">
|
||||
<button tabindex="19" class="btn-close-x p-0" (click)="flgShowInfo=false;stepNumber=1;" mat-button>X</button>
|
||||
</div>
|
||||
</mat-card-header>
|
||||
<mat-card-content fxLayout="column" fxFlex="70" fxLayoutAlign="space-between center">
|
||||
<rtl-loop-out-info-graphics *ngIf="direction === swapTypeEnum.LOOP_OUT" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-out-info-graphics>
|
||||
<rtl-loop-in-info-graphics *ngIf="direction === swapTypeEnum.LOOP_IN" [(stepNumber)]="stepNumber" [animationDirection]="animationDirection"></rtl-loop-in-info-graphics>
|
||||
</mat-card-content>
|
||||
<div class="my-4" fxLayout="row" fxFlex="10" fxLayoutAlign="center end">
|
||||
<span *ngFor="let i of [1, 2, 3, 4, 5];" (click) = "onStepChanged(i)" fxLayoutAlign="center center" class="dots-stepper-block">
|
||||
<p class="dot tiny-dot mr-0" [ngClass]="{'dot-primary': stepNumber === i, 'dot-primary-lighter': stepNumber !== i}"></p>
|
||||
</span>
|
||||
</div>
|
||||
<div fxLayout="row" fxFlex="15" fxLayoutAlign="end end" class="mt-2">
|
||||
<button *ngIf="stepNumber === 5" mat-stroked-button class="mr-1" color="primary" tabindex="15" type="button" (click)="onReadMore()">Read More</button>
|
||||
<button *ngIf="stepNumber === 5" mat-flat-button color="primary" tabindex="16" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</button>
|
||||
<button *ngIf="stepNumber < 5" mat-stroked-button class="mr-1" color="primary" tabindex="17" type="button" (click)="flgShowInfo=false;stepNumber=1;">Close</button>
|
||||
<button *ngIf="stepNumber < 5" mat-flat-button color="primary" tabindex="18" type="button" (click)="onStepChanged(stepNumber + 1)">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
3
src/app/lnd/loop/loop-modal/loop-modal.component.scss
Normal file
3
src/app/lnd/loop/loop-modal/loop-modal.component.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.dots-stepper-block {
|
||||
width: 3rem;
|
||||
}
|
||||
25
src/app/lnd/loop/loop-modal/loop-modal.component.spec.ts
Normal file
25
src/app/lnd/loop/loop-modal/loop-modal.component.spec.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoopModalComponent } from './loop-modal.component';
|
||||
|
||||
describe('LoopModalComponent', () => {
|
||||
let component: LoopModalComponent;
|
||||
let fixture: ComponentFixture<LoopModalComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoopModalComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoopModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
263
src/app/lnd/loop/loop-modal/loop-modal.component.ts
Normal file
263
src/app/lnd/loop/loop-modal/loop-modal.component.ts
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
import { Component, OnInit, Inject, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatVerticalStepper } from '@angular/material';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { opacityAnimation } from '../../../shared/animation/opacity-animation';
|
||||
import { ScreenSizeEnum, SwapTypeEnum } from '../../../shared/services/consts-enums-functions';
|
||||
import { LoopQuote, LoopStatus } from '../../../shared/models/loopModels';
|
||||
import { LoopAlert } from '../../../shared/models/alertData';
|
||||
import { LoopService } from '../../../shared/services/loop.service';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { CommonService } from '../../../shared/services/common.service';
|
||||
import { Channel } from '../../../shared/models/lndModels';
|
||||
|
||||
import * as RTLActions from '../../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-loop-modal',
|
||||
templateUrl: './loop-modal.component.html',
|
||||
styleUrls: ['./loop-modal.component.scss'],
|
||||
animations: [opacityAnimation]
|
||||
})
|
||||
export class LoopModalComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ViewChild('stepper', { static: false }) stepper: MatVerticalStepper;
|
||||
public faInfoCircle = faInfoCircle;
|
||||
public quote: LoopQuote;
|
||||
public channel: Channel;
|
||||
public minQuote: LoopQuote;
|
||||
public maxQuote: LoopQuote;
|
||||
public swapTypeEnum = SwapTypeEnum;
|
||||
public direction = SwapTypeEnum.LOOP_OUT;
|
||||
public loopDirectionCaption = 'Loop out';
|
||||
public loopStatus: LoopStatus = null;
|
||||
public inputFormLabel = 'Amount to loop out';
|
||||
public quoteFormLabel = 'Confirm Quote';
|
||||
public addressFormLabel = 'Withdrawal Address';
|
||||
public maxRoutingFeePercentage = 2;
|
||||
public prepayRoutingFee = 36;
|
||||
public flgShowInfo = false;
|
||||
public stepNumber = 1;
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
public animationDirection = 'forward';
|
||||
public flgEditable = true;
|
||||
inputFormGroup: FormGroup;
|
||||
quoteFormGroup: FormGroup;
|
||||
addressFormGroup: FormGroup;
|
||||
statusFormGroup: FormGroup;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<LoopModalComponent>, @Inject(MAT_DIALOG_DATA) public data: LoopAlert, private store: Store<fromRTLReducer.RTLState>, private loopService: LoopService, private formBuilder: FormBuilder, private decimalPipe: DecimalPipe, private logger: LoggerService, private router: Router, private commonService: CommonService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
this.channel = this.data.channel;
|
||||
this.minQuote = this.data.minQuote ? this.data.minQuote : {};
|
||||
this.maxQuote = this.data.maxQuote ? this.data.maxQuote : {};
|
||||
this.direction = this.data.direction;
|
||||
this.loopDirectionCaption = this.direction === SwapTypeEnum.LOOP_IN ? 'Loop in' : 'Loop out';
|
||||
this.inputFormLabel = 'Amount to ' + this.loopDirectionCaption;
|
||||
this.inputFormGroup = this.formBuilder.group({
|
||||
amount: [this.minQuote.amount, [Validators.required, Validators.min(this.minQuote.amount), Validators.max(this.maxQuote.amount)]],
|
||||
sweepConfTarget: [6, [Validators.required, Validators.min(1)]],
|
||||
routingFeePercent: [this.maxRoutingFeePercentage, [Validators.required, Validators.min(0), Validators.max(this.maxRoutingFeePercentage)]],
|
||||
fast: [false, [Validators.required]]
|
||||
});
|
||||
this.quoteFormGroup = this.formBuilder.group({});
|
||||
this.addressFormGroup = this.formBuilder.group({
|
||||
addressType: ['local', [Validators.required]],
|
||||
address: [{value: '', disabled: true}]
|
||||
});
|
||||
this.statusFormGroup = this.formBuilder.group({});
|
||||
this.onFormValueChanges();
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.inputFormGroup.setErrors({'Invalid': true});
|
||||
if (this.direction === SwapTypeEnum.LOOP_OUT) {
|
||||
this.addressFormGroup.setErrors({'Invalid': true});
|
||||
}
|
||||
}
|
||||
|
||||
onFormValueChanges() {
|
||||
this.inputFormGroup.valueChanges.pipe(takeUntil(this.unSubs[4])).subscribe(changedValues => {
|
||||
this.inputFormGroup.setErrors({'Invalid': true});
|
||||
});
|
||||
if (this.direction === SwapTypeEnum.LOOP_OUT) {
|
||||
this.addressFormGroup.valueChanges.pipe(takeUntil(this.unSubs[5])).subscribe(changedValues => {
|
||||
this.addressFormGroup.setErrors({'Invalid': true});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onAddressTypeChange(event: any) {
|
||||
if (event.value === 'external') {
|
||||
this.addressFormGroup.controls.address.setValidators([Validators.required]);
|
||||
this.addressFormGroup.controls.address.markAsTouched();
|
||||
this.addressFormGroup.controls.address.enable();
|
||||
} else {
|
||||
this.addressFormGroup.controls.address.setValidators(null);
|
||||
this.addressFormGroup.controls.address.markAsPristine();
|
||||
this.addressFormGroup.controls.address.disable();
|
||||
this.addressFormGroup.controls.address.setValue('');
|
||||
}
|
||||
this.addressFormGroup.setErrors({'Invalid': true});
|
||||
}
|
||||
|
||||
|
||||
onLoop() {
|
||||
if(!this.inputFormGroup.controls.amount.value || this.inputFormGroup.controls.amount.value < this.minQuote.amount || this.inputFormGroup.controls.amount.value > this.maxQuote.amount || !this.inputFormGroup.controls.sweepConfTarget.value || this.inputFormGroup.controls.sweepConfTarget.value < 2 || (!this.inputFormGroup.controls.routingFeePercent.value || this.inputFormGroup.controls.routingFeePercent.value < 0 || this.inputFormGroup.controls.routingFeePercent.value > this.maxRoutingFeePercentage) || (this.direction === SwapTypeEnum.LOOP_OUT && this.addressFormGroup.controls.addressType.value === 'external' && (!this.addressFormGroup.controls.address.value || this.addressFormGroup.controls.address.value.trim() === ''))) { return true; }
|
||||
this.flgEditable = false;
|
||||
this.stepper.selected.stepControl.setErrors(null);
|
||||
this.stepper.next();
|
||||
if (this.direction === SwapTypeEnum.LOOP_IN) {
|
||||
this.loopService.loopIn(this.inputFormGroup.controls.amount.value, +this.quote.swap_fee, +this.quote.miner_fee, '', true).pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((loopStatus: any) => {
|
||||
this.loopStatus = JSON.parse(loopStatus);
|
||||
this.store.dispatch(new RTLActions.FetchLoopSwaps());
|
||||
this.flgEditable = true;
|
||||
}, (err) => {
|
||||
this.loopStatus = { error: err.error.error ? err.error.error : err.error ? err.error : err };
|
||||
this.flgEditable = true;
|
||||
this.logger.error(err);
|
||||
});
|
||||
} else {
|
||||
let swapRoutingFee = this.inputFormGroup.controls.amount.value * (this.inputFormGroup.controls.routingFeePercent.value / 100);
|
||||
let destAddress = this.addressFormGroup.controls.addressType.value === 'external' ? this.addressFormGroup.controls.address.value : '';
|
||||
let swapPublicationDeadline = this.inputFormGroup.controls.fast.value ? 0 : new Date().getTime() + (30 * 60000);
|
||||
this.loopService.loopOut(this.inputFormGroup.controls.amount.value, (this.channel && this.channel.chan_id ? this.channel.chan_id : ''), this.inputFormGroup.controls.sweepConfTarget.value, swapRoutingFee, +this.quote.miner_fee, this.prepayRoutingFee, +this.quote.prepay_amt, +this.quote.swap_fee, swapPublicationDeadline, destAddress).pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe((loopStatus: any) => {
|
||||
this.loopStatus = JSON.parse(loopStatus);
|
||||
this.store.dispatch(new RTLActions.FetchLoopSwaps());
|
||||
this.flgEditable = true;
|
||||
}, (err) => {
|
||||
this.loopStatus = { error: err.error.error ? err.error.error : err.error ? err.error : err };
|
||||
this.flgEditable = true;
|
||||
this.logger.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onEstimateQuote() {
|
||||
if(!this.inputFormGroup.controls.amount.value || this.inputFormGroup.controls.amount.value < this.minQuote.amount || this.inputFormGroup.controls.amount.value > this.maxQuote.amount || !this.inputFormGroup.controls.sweepConfTarget.value || this.inputFormGroup.controls.sweepConfTarget.value < 2) { return true; }
|
||||
this.stepper.selected.stepControl.setErrors(null);
|
||||
this.stepper.next();
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Getting Quotes...'));
|
||||
let swapPublicationDeadline = this.inputFormGroup.controls.fast.value ? 0 : new Date().getTime() + (30 * 60000);
|
||||
if(this.direction === SwapTypeEnum.LOOP_IN) {
|
||||
this.loopService.getLoopInQuote(this.inputFormGroup.controls.amount.value, this.inputFormGroup.controls.sweepConfTarget.value, swapPublicationDeadline)
|
||||
.pipe(takeUntil(this.unSubs[2]))
|
||||
.subscribe(response => {
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
this.quote = response;
|
||||
this.quote.off_chain_swap_routing_fee_percentage = this.inputFormGroup.controls.routingFeePercent.value ? this.inputFormGroup.controls.routingFeePercent.value : 2;
|
||||
});
|
||||
} else {
|
||||
this.loopService.getLoopOutQuote(this.inputFormGroup.controls.amount.value, this.inputFormGroup.controls.sweepConfTarget.value, swapPublicationDeadline)
|
||||
.pipe(takeUntil(this.unSubs[3]))
|
||||
.subscribe(response => {
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
this.quote = response;
|
||||
this.quote.off_chain_swap_routing_fee_percentage = this.inputFormGroup.controls.routingFeePercent.value ? this.inputFormGroup.controls.routingFeePercent.value : 2;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
stepSelectionChanged(event: any) {
|
||||
switch (event.selectedIndex) {
|
||||
case 0:
|
||||
this.inputFormLabel = 'Amount to ' + this.loopDirectionCaption;
|
||||
this.quoteFormLabel = 'Confirm Quote';
|
||||
this.addressFormLabel = 'Withdrawal Address';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (this.inputFormGroup.controls.amount.value || this.inputFormGroup.controls.sweepConfTarget.value) {
|
||||
if (this.direction === SwapTypeEnum.LOOP_IN) {
|
||||
this.inputFormLabel = this.loopDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats | Target Confirmation: ' + (this.inputFormGroup.controls.sweepConfTarget.value ? this.inputFormGroup.controls.sweepConfTarget.value : 6);
|
||||
} else {
|
||||
this.inputFormLabel = this.loopDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats | Target Confirmation: ' + (this.inputFormGroup.controls.sweepConfTarget.value ? this.inputFormGroup.controls.sweepConfTarget.value : 6) + ' | Percentage: ' + (this.inputFormGroup.controls.routingFeePercent.value ? this.inputFormGroup.controls.routingFeePercent.value : '2') + ' | Fast: ' + (this.inputFormGroup.controls.fast.value ? 'Enabled' : 'Disabled');
|
||||
}
|
||||
} else {
|
||||
this.inputFormLabel = 'Amount to ' + this.loopDirectionCaption;
|
||||
}
|
||||
this.quoteFormLabel = 'Confirm Quote';
|
||||
this.addressFormLabel = 'Withdrawal Address';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (this.inputFormGroup.controls.amount.value || this.inputFormGroup.controls.sweepConfTarget.value) {
|
||||
if (this.direction === SwapTypeEnum.LOOP_IN) {
|
||||
this.inputFormLabel = this.loopDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats | Target Confirmation: ' + (this.inputFormGroup.controls.sweepConfTarget.value ? this.inputFormGroup.controls.sweepConfTarget.value : 6);
|
||||
} else {
|
||||
this.inputFormLabel = this.loopDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats | Target Confirmation: ' + (this.inputFormGroup.controls.sweepConfTarget.value ? this.inputFormGroup.controls.sweepConfTarget.value : 6) + ' | Fast: ' + (this.inputFormGroup.controls.fast.value ? 'Enabled' : 'Disabled');
|
||||
}
|
||||
} else {
|
||||
this.inputFormLabel = 'Amount to ' + this.loopDirectionCaption;
|
||||
}
|
||||
if (this.quote && this.quote.swap_fee && this.quote.miner_fee && this.quote.prepay_amt) {
|
||||
this.quoteFormLabel = 'Quote confirmed | Estimated Fees: ' + this.decimalPipe.transform(+this.quote.swap_fee + +this.quote.miner_fee) + ' Sats';
|
||||
} else {
|
||||
this.quoteFormLabel = 'Quote confirmed';
|
||||
}
|
||||
if (this.addressFormGroup.controls.addressType.value) {
|
||||
this.addressFormLabel = 'Withdrawal Address | Type: ' + this.addressFormGroup.controls.addressType.value;
|
||||
} else {
|
||||
this.addressFormLabel = 'Withdrawal Address';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
this.inputFormLabel = 'Amount to ' + this.loopDirectionCaption;
|
||||
this.quoteFormLabel = 'Confirm Quote';
|
||||
this.addressFormLabel = 'Withdrawal Address';
|
||||
break;
|
||||
}
|
||||
if ((this.direction === SwapTypeEnum.LOOP_OUT && event.selectedIndex !== 1 && event.selectedIndex < event.previouslySelectedIndex)
|
||||
|| (this.direction === SwapTypeEnum.LOOP_IN && event.selectedIndex < event.previouslySelectedIndex)) {
|
||||
event.selectedStep.stepControl.setErrors({'Invalid': true});
|
||||
}
|
||||
}
|
||||
|
||||
goToLoop() {
|
||||
this.dialogRef.close(true);
|
||||
this.router.navigateByUrl('/lnd/loop');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.dialogRef.close(true);
|
||||
}
|
||||
|
||||
showInfo() {
|
||||
this.flgShowInfo = true;
|
||||
}
|
||||
|
||||
onReadMore() {
|
||||
if (this.direction === SwapTypeEnum.LOOP_IN) {
|
||||
window.open('https://blog.lightning.engineering/announcement/2019/06/25/loop-in.html', '_blank');
|
||||
} else {
|
||||
window.open('https://blog.lightning.engineering/technical/posts/2019/04/15/loop-out-in-depth.html', '_blank');
|
||||
}
|
||||
this.onClose();
|
||||
}
|
||||
|
||||
onStepChanged(index: number) {
|
||||
this.animationDirection = index < this.stepNumber ? 'backward' : 'forward';
|
||||
this.stepNumber = index;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,5 @@
|
|||
svg {
|
||||
height: 50%;
|
||||
min-height: 50%;
|
||||
max-height: 50%;
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoopOutInfoGraphicsComponent } from './info-graphics.component';
|
||||
|
||||
describe('LoopOutInfoGraphicsComponent', () => {
|
||||
let component: LoopOutInfoGraphicsComponent;
|
||||
let fixture: ComponentFixture<LoopOutInfoGraphicsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoopOutInfoGraphicsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoopOutInfoGraphicsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
|
||||
|
||||
import { sliderAnimation } from '../../../shared/animation/slider-animation';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-loop-out-info-graphics',
|
||||
templateUrl: './info-graphics.component.html',
|
||||
styleUrls: ['./info-graphics.component.scss'],
|
||||
animations: [sliderAnimation]
|
||||
})
|
||||
export class LoopOutInfoGraphicsComponent implements OnInit {
|
||||
@Input() animationDirection = 'forward';
|
||||
@Input() stepNumber = 1;
|
||||
@Output() stepNumberChange = new EventEmitter();
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onSwipe(event: any) {
|
||||
if(event.direction === 2 && this.stepNumber < 5) {
|
||||
this.stepNumber++;
|
||||
this.animationDirection = 'forward';
|
||||
this.stepNumberChange.emit(this.stepNumber);
|
||||
} else if(event.direction === 4 && this.stepNumber > 1) {
|
||||
this.stepNumber--;
|
||||
this.animationDirection = 'backward';
|
||||
this.stepNumberChange.emit(this.stepNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
59
src/app/lnd/loop/loop-quote/loop-quote.component.html
Normal file
59
src/app/lnd/loop/loop-quote/loop-quote.component.html
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
<ng-container *ngTemplateOutlet="showPanel ? expansionPanelBlock : quoteDetailsBlock"></ng-container>
|
||||
<ng-template #expansionPanelBlock>
|
||||
<ng-container *ngTemplateOutlet="quote?.miner_fee < 0 ? errorBlock : informationBlock"></ng-container>
|
||||
</ng-template>
|
||||
<ng-template #informationBlock>
|
||||
<mat-expansion-panel class="flat-expansion-panel mb-1" fxFlex="100" [expanded]="panelExpanded" [ngClass]="{'h-5':!flgShowPanel}">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<span fxLayoutAlign="start center" fxFlex="100">Quote for {{termCaption}} amount ({{quote.amount | number}} Sats)</span>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<ng-container *ngTemplateOutlet="quoteDetailsBlock"></ng-container>
|
||||
</mat-expansion-panel>
|
||||
</ng-template>
|
||||
<ng-template #quoteDetailsBlock>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="30" matTooltip="The fee that the swap server is charging for the swap">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Swap Fee (Sats)</h4>
|
||||
<span class="foreground-secondary-text">{{quote?.swap_fee | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="30" matTooltip="An estimate of the on-chain fee that needs to be paid to sweep the HTLC">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Miner Fee (Sats)</h4>
|
||||
<span class="foreground-secondary-text">{{quote?.miner_fee | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="40" matTooltip="The part of the swap fee that is requested as a prepayment">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Prepay Amount (Sats)</h4>
|
||||
<span class="foreground-secondary-text">{{quote?.prepay_amt | number}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="w-100 my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="50" matTooltip="Maximum off-chain fee that may be paid for routing the payment amount to the server">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Max Off-chain Swap Routing Fee (Sats)</h4>
|
||||
<span class="foreground-secondary-text">{{(quote?.amount * ((quote?.off_chain_swap_routing_fee_percentage ? quote?.off_chain_swap_routing_fee_percentage : 2) / 100)) | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="50" matTooltip="Maximum off-chain fee that may be paid for routing the pre-payment amount to the server">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Max Off-chain Prepay Routing Fee (Sats)</h4>
|
||||
<span class="foreground-secondary-text">36</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="w-100 my-1" *ngIf="quote?.swap_payment_dest !== ''"></mat-divider>
|
||||
<div fxLayout="row" *ngIf="quote?.swap_payment_dest !== ''">
|
||||
<div fxFlex="100" matTooltip="The node pubkey where the swap payment needs to be paid to">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Swap Server Node Pubkey</h4>
|
||||
<span class="foreground-secondary-text">{{quote?.swap_payment_dest}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #errorBlock>
|
||||
<mat-expansion-panel class="flat-expansion-panel mb-1" fxFlex="100" [disabled]="true" [ngClass]="{'h-5':!flgShowPanel}">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
<span fxLayoutAlign="start center" fxFlex="100">Quote for {{termCaption}} amount ({{quote.amount | number}} Sats): Insufficient balance to estimate quote</span>
|
||||
</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
</mat-expansion-panel>
|
||||
</ng-template>
|
||||
0
src/app/lnd/loop/loop-quote/loop-quote.component.scss
Normal file
0
src/app/lnd/loop/loop-quote/loop-quote.component.scss
Normal file
25
src/app/lnd/loop/loop-quote/loop-quote.component.spec.ts
Normal file
25
src/app/lnd/loop/loop-quote/loop-quote.component.spec.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoopQuoteComponent } from './loop-quote.component';
|
||||
|
||||
describe('LoopQuoteComponent', () => {
|
||||
let component: LoopQuoteComponent;
|
||||
let fixture: ComponentFixture<LoopQuoteComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoopQuoteComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoopQuoteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
22
src/app/lnd/loop/loop-quote/loop-quote.component.ts
Normal file
22
src/app/lnd/loop/loop-quote/loop-quote.component.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { LoopQuote } from '../../../shared/models/loopModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-loop-quote',
|
||||
templateUrl: './loop-quote.component.html',
|
||||
styleUrls: ['./loop-quote.component.scss']
|
||||
})
|
||||
export class LoopQuoteComponent implements OnInit {
|
||||
@Input() quote: LoopQuote = {};
|
||||
@Input() termCaption = '';
|
||||
@Input() showPanel = true;
|
||||
@Input() panelExpanded = false;
|
||||
public flgShowPanel = false;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
setTimeout(() => { this.flgShowPanel = true; }, 1200);
|
||||
}
|
||||
|
||||
}
|
||||
23
src/app/lnd/loop/loop-status/loop-status.component.html
Normal file
23
src/app/lnd/loop/loop-status/loop-status.component.html
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<ng-container *ngTemplateOutlet="loopStatus?.error ? loopFailedBlock : loopSuccessfulBlock"></ng-container>
|
||||
<ng-template #loopFailedBlock>
|
||||
<div fxLayout="column"><span
|
||||
class="foreground-secondary-text">{{'Error: ' + (loopStatus?.error?.error?.error?.error ? loopStatus.error.error.error.error : loopStatus?.error?.error?.error ? loopStatus.error.error.error : loopStatus?.error?.error ? loopStatus.error.error : loopStatus?.error ? loopStatus.error : 'Unknown')}}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #loopSuccessfulBlock>
|
||||
<div fxLayout="column">
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">ID</h4>
|
||||
<span class="foreground-secondary-text">{{loopStatus?.id_bytes}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="w-100 my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">HTLC Address</h4>
|
||||
<span class="foreground-secondary-text">{{loopStatus?.htlc_address}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
0
src/app/lnd/loop/loop-status/loop-status.component.scss
Normal file
0
src/app/lnd/loop/loop-status/loop-status.component.scss
Normal file
25
src/app/lnd/loop/loop-status/loop-status.component.spec.ts
Normal file
25
src/app/lnd/loop/loop-status/loop-status.component.spec.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoopStatusComponent } from './loop-status.component';
|
||||
|
||||
describe('LoopStatusComponent', () => {
|
||||
let component: LoopStatusComponent;
|
||||
let fixture: ComponentFixture<LoopStatusComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoopStatusComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoopStatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
16
src/app/lnd/loop/loop-status/loop-status.component.ts
Normal file
16
src/app/lnd/loop/loop-status/loop-status.component.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { LoopStatus } from '../../../shared/models/loopModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-loop-status',
|
||||
templateUrl: './loop-status.component.html',
|
||||
styleUrls: ['./loop-status.component.scss']
|
||||
})
|
||||
export class LoopStatusComponent implements OnInit {
|
||||
@Input() loopStatus: LoopStatus;
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
}
|
||||
19
src/app/lnd/loop/loop.component.html
Normal file
19
src/app/lnd/loop/loop.component.html
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
|
||||
<fa-icon [icon]="faInfinity" class="page-title-img mr-1"></fa-icon>
|
||||
<span class="page-title">Loop</span>
|
||||
</div>
|
||||
<div fxLayout="column" class="padding-gap-x">
|
||||
<mat-card>
|
||||
<mat-card-content fxLayout="column">
|
||||
<mat-tab-group fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" (selectedIndexChange)="onSelectedIndexChange($event)">
|
||||
<mat-tab label="Loop Out">
|
||||
<button mat-flat-button color="primary" (click)="onLoop(swapTypeEnum.LOOP_OUT)" class="mt-1" type="button" tabindex="1">Start Loop Out</button>
|
||||
</mat-tab>
|
||||
<mat-tab label="Loop In">
|
||||
<button mat-flat-button color="primary" (click)="onLoop(swapTypeEnum.LOOP_IN)" class="mt-1" type="button" tabindex="2">Start Loop In</button>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<rtl-swaps fxLayout="row" fxFlex="100" [selectedSwapType]="selectedSwapType"></rtl-swaps>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
3
src/app/lnd/loop/loop.component.scss
Normal file
3
src/app/lnd/loop/loop.component.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.loop-monitor-logs {
|
||||
min-height: 4rem;
|
||||
}
|
||||
25
src/app/lnd/loop/loop.component.spec.ts
Normal file
25
src/app/lnd/loop/loop.component.spec.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoopComponent } from './loop.component';
|
||||
|
||||
describe('LoopComponent', () => {
|
||||
let component: LoopComponent;
|
||||
let fixture: ComponentFixture<LoopComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoopComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoopComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
77
src/app/lnd/loop/loop.component.ts
Normal file
77
src/app/lnd/loop/loop.component.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { faInfinity } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { SwapTypeEnum } from '../../shared/services/consts-enums-functions';
|
||||
import { LoopModalComponent } from './loop-modal/loop-modal.component';
|
||||
import { LoopQuote } from '../../shared/models/loopModels';
|
||||
import { LoopService } from '../../shared/services/loop.service';
|
||||
|
||||
import * as fromRTLReducer from '../../store/rtl.reducers';
|
||||
import * as RTLActions from '../../store/rtl.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-loop',
|
||||
templateUrl: './loop.component.html',
|
||||
styleUrls: ['./loop.component.scss']
|
||||
})
|
||||
export class LoopComponent implements OnInit {
|
||||
public faInfinity = faInfinity;
|
||||
private targetConf = 2;
|
||||
public inAmount = 250000;
|
||||
public quotes: LoopQuote[] = [];
|
||||
public swapTypeEnum = SwapTypeEnum;
|
||||
public selectedSwapType: SwapTypeEnum = SwapTypeEnum.LOOP_OUT;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private loopService: LoopService, private store: Store<fromRTLReducer.RTLState>) {}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
onSelectedIndexChange(event: any) {
|
||||
if(event === 1) {
|
||||
this.selectedSwapType = SwapTypeEnum.LOOP_IN;
|
||||
} else {
|
||||
this.selectedSwapType = SwapTypeEnum.LOOP_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
onLoop(direction: SwapTypeEnum) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Getting Terms and Quotes...'));
|
||||
if(direction === SwapTypeEnum.LOOP_IN) {
|
||||
this.loopService.getLoopInTermsAndQuotes(this.targetConf)
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe(response => {
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ data: {
|
||||
minQuote: response[0],
|
||||
maxQuote: response[1],
|
||||
direction: direction,
|
||||
component: LoopModalComponent
|
||||
}}));
|
||||
});
|
||||
} else {
|
||||
this.loopService.getLoopOutTermsAndQuotes(this.targetConf)
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe(response => {
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ data: {
|
||||
minQuote: response[0],
|
||||
maxQuote: response[1],
|
||||
direction: direction,
|
||||
component: LoopModalComponent
|
||||
}}));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
84
src/app/lnd/loop/swaps/swaps.component.html
Normal file
84
src/app/lnd/loop/swaps/swaps.component.html
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start start" class="card-content-gap">
|
||||
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch" fxFlex="100" class="page-sub-title-container w-100">
|
||||
<div fxFlex="70">
|
||||
<fa-icon [icon]="faHistory" class="page-title-img mr-1"></fa-icon>
|
||||
<span class="page-title">{{swapCaption}} History</span>
|
||||
</div>
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" class="w-100">
|
||||
<div perfectScrollbar class="table-container" fxFlex="100">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="listSwaps" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
|
||||
<ng-container matColumnDef="initiation_time_str">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Initiation Time </th>
|
||||
<td mat-cell *matCellDef="let swap">{{swap.initiation_time_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="last_update_time_str">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Last Update Time </th>
|
||||
<td mat-cell *matCellDef="let swap">{{swap.last_update_time_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
|
||||
<td mat-cell *matCellDef="let swap">{{swap.id}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="id_bytes">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID (Bytes) </th>
|
||||
<td mat-cell *matCellDef="let swap">{{swap.id_bytes}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="state">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> State </th>
|
||||
<td mat-cell *matCellDef="let swap">{{swapStateEnum[swap.state]}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="htlc_address">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> HTLC Address </th>
|
||||
<td mat-cell *matCellDef="let swap">{{swap.htlc_address}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amt">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Amount (Sats) </th>
|
||||
<td mat-cell *matCellDef="let swap">
|
||||
<span fxLayoutAlign="end center">{{swap.amt | number}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="cost_server">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Cost Server (Sats) </th>
|
||||
<td mat-cell *matCellDef="let swap"><span fxLayoutAlign="end center">{{swap.cost_server | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="cost_offchain">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Cost Offchain (Sats) </th>
|
||||
<td mat-cell *matCellDef="let swap"><span fxLayoutAlign="end center">{{swap.cost_offchain | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="cost_onchain">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Cost Onchain (Sats) </th>
|
||||
<td mat-cell *matCellDef="let swap"><span fxLayoutAlign="end center">
|
||||
{{swap?.cost_onchain | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th mat-header-cell *matHeaderCellDef class="px-3">
|
||||
<div class="bordered-box table-actions-select">
|
||||
<mat-select placeholder="Actions" tabindex="1" class="mr-0">
|
||||
<mat-select-trigger></mat-select-trigger>
|
||||
<mat-option (click)="onDownloadCSV()">Download CSV</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let swap" class="pl-3" fxLayoutAlign="end center">
|
||||
<button mat-stroked-button color="primary" type="button" tabindex="4"
|
||||
(click)="onSwapClick(swap, $event)">View Info</button>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="no_swap">
|
||||
<td mat-footer-cell *matFooterCellDef colspan="4">
|
||||
<p *ngIf="!listSwaps.data || listSwaps.data.length<1">No {{swapCaption | lowercase}} swaps available.</p>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-footer-row *matFooterRowDef="['no_swap']" [ngClass]="{'display-none': listSwaps.data && listSwaps.data.length>0}"></tr>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
<mat-paginator [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" [showFirstLastButtons]="screenSize === screenSizeEnum.XS ? false : true" class="mb-4"></mat-paginator>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
4
src/app/lnd/loop/swaps/swaps.component.scss
Normal file
4
src/app/lnd/loop/swaps/swaps.component.scss
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.mat-column-actions {
|
||||
min-height: 4.8rem;
|
||||
}
|
||||
|
||||
25
src/app/lnd/loop/swaps/swaps.component.spec.ts
Normal file
25
src/app/lnd/loop/swaps/swaps.component.spec.ts
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SwapsComponent } from './swaps.component';
|
||||
|
||||
describe('SwapsComponent', () => {
|
||||
let component: SwapsComponent;
|
||||
let fixture: ComponentFixture<SwapsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SwapsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SwapsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
140
src/app/lnd/loop/swaps/swaps.component.ts
Normal file
140
src/app/lnd/loop/swaps/swaps.component.ts
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
import { Component, OnInit, OnChanges, OnDestroy, ViewChild, Input } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
import { faHistory } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { MatTableDataSource, MatSort, MatPaginator, MatPaginatorIntl } from '@angular/material';
|
||||
import { SwapStatus } from '../../../shared/models/lndModels';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, SwapTypeEnum, SwapStateEnum } from '../../../shared/services/consts-enums-functions';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { CommonService } from '../../../shared/services/common.service';
|
||||
import { LoopService } from '../../../shared/services/loop.service';
|
||||
|
||||
import * as RTLActions from '../../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-swaps',
|
||||
templateUrl: './swaps.component.html',
|
||||
styleUrls: ['./swaps.component.scss'],
|
||||
providers: [
|
||||
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Swaps') }
|
||||
]
|
||||
})
|
||||
export class SwapsComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() selectedSwapType: SwapTypeEnum = SwapTypeEnum.LOOP_OUT;
|
||||
@ViewChild(MatSort, { static: true }) sort: MatSort;
|
||||
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
|
||||
public swapStateEnum = SwapStateEnum;
|
||||
public faHistory = faHistory;
|
||||
public swapCaption = 'Loop Out';
|
||||
public displayedColumns = [];
|
||||
public listSwaps: any;
|
||||
public storedSwaps: SwapStatus[] = [];
|
||||
public filteredSwaps: SwapStatus[] = [];
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
public flgSticky = false;
|
||||
public pageSize = PAGE_SIZE;
|
||||
public pageSizeOptions = PAGE_SIZE_OPTIONS;
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions, private loopService: LoopService) {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
if(this.screenSize === ScreenSizeEnum.XS) {
|
||||
this.flgSticky = false;
|
||||
this.displayedColumns = ['state', 'amt', 'actions'];
|
||||
} else if(this.screenSize === ScreenSizeEnum.SM) {
|
||||
this.flgSticky = false;
|
||||
this.displayedColumns = ['state', 'amt', 'actions'];
|
||||
} else if(this.screenSize === ScreenSizeEnum.MD) {
|
||||
this.flgSticky = false;
|
||||
this.displayedColumns = ['state', 'initiation_time_str', 'amt', 'actions'];
|
||||
} else {
|
||||
this.flgSticky = true;
|
||||
this.displayedColumns = ['state', 'initiation_time_str', 'amt', 'cost_server', 'cost_offchain', 'cost_onchain', 'actions'];
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new RTLActions.FetchLoopSwaps());
|
||||
this.actions$.pipe(takeUntil(this.unSubs[0]), filter((action) => action.type === RTLActions.RESET_LND_STORE)).subscribe((resetLndStore: RTLActions.ResetLNDStore) => {
|
||||
this.store.dispatch(new RTLActions.FetchLoopSwaps());
|
||||
});
|
||||
|
||||
this.store.select('lnd')
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe((rtlStore) => {
|
||||
rtlStore.effectErrorsLnd.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchSwaps') { this.flgLoading[0] = 'error'; }
|
||||
});
|
||||
if (rtlStore.loopSwaps) {
|
||||
this.storedSwaps = rtlStore.loopSwaps;
|
||||
this.filteredSwaps = this.storedSwaps.filter(swap => swap.type === this.selectedSwapType);
|
||||
this.loadSwapsTable(this.filteredSwaps);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = ( rtlStore.transactions) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
this.swapCaption = (this.selectedSwapType === SwapTypeEnum.LOOP_IN) ? 'Loop In' : 'Loop Out'
|
||||
this.filteredSwaps = this.storedSwaps.filter(swap => swap.type === this.selectedSwapType);
|
||||
this.loadSwapsTable(this.filteredSwaps);
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.listSwaps.filter = selFilter;
|
||||
}
|
||||
|
||||
onSwapClick(selSwap: SwapStatus, event: any) {
|
||||
this.loopService.getSwap(selSwap.id_bytes.replace(/\//g, '_').replace(/\+/g, '-')).pipe(takeUntil(this.unSubs[2]))
|
||||
.subscribe((fetchedSwap: SwapStatus) => {
|
||||
const reorderedSwap = [
|
||||
[{key: 'state', value: SwapStateEnum[fetchedSwap.state], title: 'Status', width: 50, type: DataTypeEnum.STRING},
|
||||
{key: 'amt', value: fetchedSwap.amt, title: 'Amount (Sats)', width: 50, type: DataTypeEnum.NUMBER}],
|
||||
[{key: 'initiation_time_str', value: fetchedSwap.initiation_time_str, title: 'Initiation Time', width: 50, type: DataTypeEnum.DATE_TIME},
|
||||
{key: 'last_update_time_str', value: fetchedSwap.last_update_time_str, title: 'Last Update Time', width: 50, type: DataTypeEnum.DATE_TIME}],
|
||||
[{key: 'cost_server', value: fetchedSwap.cost_server, title: 'Server Cost (Sats)', width: 33, type: DataTypeEnum.NUMBER},
|
||||
{key: 'cost_offchain', value: fetchedSwap.cost_offchain, title: 'Offchain Cost (Sats)', width: 33, type: DataTypeEnum.NUMBER},
|
||||
{key: 'cost_onchain', value: fetchedSwap.cost_onchain, title: 'Onchain Cost (Sats)', width: 34, type: DataTypeEnum.NUMBER}],
|
||||
[{key: 'id_bytes', value: fetchedSwap.id_bytes, title: 'ID', width: 100, type: DataTypeEnum.STRING}],
|
||||
[{key: 'htlc_address', value: fetchedSwap.htlc_address, title: 'HTLC Address', width: 100, type: DataTypeEnum.STRING}]
|
||||
];
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ data: {
|
||||
type: AlertTypeEnum.INFORMATION,
|
||||
alertTitle: this.swapCaption + ' Status',
|
||||
message: reorderedSwap,
|
||||
openedBy: 'SWAP'
|
||||
}}));
|
||||
});
|
||||
}
|
||||
|
||||
loadSwapsTable(swaps) {
|
||||
this.listSwaps = new MatTableDataSource<SwapStatus>([...swaps]);
|
||||
this.listSwaps.sort = this.sort;
|
||||
this.listSwaps.paginator = this.paginator;
|
||||
this.logger.info(this.listSwaps);
|
||||
}
|
||||
|
||||
onDownloadCSV() {
|
||||
if(this.listSwaps.data && this.listSwaps.data.length > 0) {
|
||||
this.commonService.downloadCSV(this.listSwaps.data, (this.selectedSwapType === SwapTypeEnum.LOOP_IN) ? 'Loop in' : 'Loop out');
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@
|
|||
<mat-option (click)="onViewRemotePolicy(channel)">View Remote Fee </mat-option>
|
||||
<mat-option (click)="onChannelUpdate(channel)">Update Fee Policy</mat-option>
|
||||
<mat-option (click)="onCircularRebalance(channel)" *ngIf="+versionsArr[0] > 0 || +versionsArr[1] >= 9">Circular Rebalance</mat-option>
|
||||
<mat-option (click)="onLoopOut(channel)" *ngIf="selNode.swapServerUrl">Loop Out</mat-option>
|
||||
<mat-option (click)="onChannelClose(channel)">Close Channel</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,11 +8,13 @@ import { MatTableDataSource, MatSort, MatPaginator, MatPaginatorIntl } from '@an
|
|||
|
||||
import { SelNodeChild } from '../../../../../shared/models/RTLconfig';
|
||||
import { Channel, GetInfo } from '../../../../../shared/models/lndModels';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, UserPersonaEnum } from '../../../../../shared/services/consts-enums-functions';
|
||||
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, UserPersonaEnum, SwapTypeEnum } from '../../../../../shared/services/consts-enums-functions';
|
||||
import { LoggerService } from '../../../../../shared/services/logger.service';
|
||||
import { LoopService } from '../../../../../shared/services/loop.service';
|
||||
import { CommonService } from '../../../../../shared/services/common.service';
|
||||
import { ChannelRebalanceComponent } from '../../channel-rebalance-modal/channel-rebalance.component';
|
||||
import { CloseChannelComponent } from '../../close-channel-modal/close-channel.component';
|
||||
import { LoopModalComponent } from '../../../../loop/loop-modal/loop-modal.component';
|
||||
|
||||
import { LNDEffects } from '../../../../store/lnd.effects';
|
||||
import { RTLEffects } from '../../../../../store/rtl.effects';
|
||||
|
|
@ -51,7 +53,7 @@ export class ChannelOpenTableComponent implements OnInit, OnDestroy {
|
|||
private targetConf = 6;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private lndEffects: LNDEffects, private commonService: CommonService, private decimalPipe: DecimalPipe) {
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private lndEffects: LNDEffects, private commonService: CommonService, private loopService: LoopService, private decimalPipe: DecimalPipe) {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
if(this.screenSize === ScreenSizeEnum.XS) {
|
||||
this.flgSticky = false;
|
||||
|
|
@ -310,6 +312,22 @@ export class ChannelOpenTableComponent implements OnInit, OnDestroy {
|
|||
return channels;
|
||||
}
|
||||
|
||||
onLoopOut(selChannel: Channel) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Getting Terms and Quotes...'));
|
||||
this.loopService.getLoopOutTermsAndQuotes(this.targetConf)
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe(response => {
|
||||
this.store.dispatch(new RTLActions.CloseSpinner());
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ minHeight: '56rem', data: {
|
||||
channel: selChannel,
|
||||
minQuote: response[0],
|
||||
maxQuote: response[1],
|
||||
direction: SwapTypeEnum.LOOP_OUT,
|
||||
component: LoopModalComponent
|
||||
}}));
|
||||
});
|
||||
}
|
||||
|
||||
onDownloadCSV() {
|
||||
if(this.channels.data && this.channels.data.length > 0) {
|
||||
this.commonService.downloadCSV(this.channels.data, 'Open-channels');
|
||||
|
|
|
|||
|
|
@ -1104,7 +1104,29 @@ export class LNDEffects implements OnDestroy {
|
|||
})
|
||||
);
|
||||
|
||||
initializeRemainingData(info: any, landingPage: string) {
|
||||
@Effect()
|
||||
getLoopSwaps = this.actions$.pipe(
|
||||
ofType(RTLActions.FETCH_LOOP_SWAPS),
|
||||
mergeMap((action: RTLActions.FetchLoopSwaps) => {
|
||||
this.store.dispatch(new RTLActions.ClearEffectErrorLnd('LoopSwaps'));
|
||||
return this.httpClient.get(this.CHILD_API_URL + environment.LOOP_API + '/swaps')
|
||||
.pipe(
|
||||
map((swaps: any) => {
|
||||
this.logger.info(swaps);
|
||||
return {
|
||||
type: RTLActions.SET_LOOP_SWAPS,
|
||||
payload: swaps
|
||||
};
|
||||
}),
|
||||
catchError((err: any) => {
|
||||
this.handleErrorWithoutAlert('LoopSwaps', err);
|
||||
return of({type: RTLActions.VOID});
|
||||
})
|
||||
);
|
||||
}
|
||||
));
|
||||
|
||||
initializeRemainingData(info: any, landingPage: string) {
|
||||
this.sessionService.setItem('lndUnlocked', 'true');
|
||||
if (info.chains) {
|
||||
if (typeof info.chains[0] === 'string') {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { SelNodeChild } from '../../shared/models/RTLconfig';
|
|||
import { ErrorPayload } from '../../shared/models/errorPayload';
|
||||
import {
|
||||
GetInfo, Peer, Fees, NetworkInfo, Balance, Channel, Payment, ListInvoices,
|
||||
PendingChannels, ClosedChannel, Transaction, SwitchRes, PendingChannelsGroup
|
||||
PendingChannels, ClosedChannel, Transaction, SwitchRes, PendingChannelsGroup, SwapStatus
|
||||
} from '../../shared/models/lndModels';
|
||||
import * as RTLActions from '../../store/rtl.actions';
|
||||
import { UserPersonaEnum } from '../../shared/services/consts-enums-functions';
|
||||
|
|
@ -31,11 +31,12 @@ export interface LNDState {
|
|||
payments: Payment[];
|
||||
invoices: ListInvoices;
|
||||
forwardingHistory: SwitchRes;
|
||||
loopSwaps: SwapStatus[];
|
||||
}
|
||||
|
||||
export const initLNDState: LNDState = {
|
||||
effectErrorsLnd: [],
|
||||
nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, fiatConversion: false, channelBackupPath: '', currencyUnits: [], selCurrencyUnit: '', lnImplementation: '' },
|
||||
nodeSettings: { userPersona: UserPersonaEnum.OPERATOR, fiatConversion: false, channelBackupPath: '', currencyUnits: [], selCurrencyUnit: '', lnImplementation: '', swapServerUrl: '' },
|
||||
information: {},
|
||||
peers: [],
|
||||
fees: {},
|
||||
|
|
@ -57,6 +58,7 @@ export const initLNDState: LNDState = {
|
|||
payments: [],
|
||||
invoices: {invoices: []},
|
||||
forwardingHistory: {},
|
||||
loopSwaps: []
|
||||
}
|
||||
|
||||
export function LNDReducer(state = initLNDState, action: RTLActions.RTLActions) {
|
||||
|
|
@ -245,6 +247,11 @@ export function LNDReducer(state = initLNDState, action: RTLActions.RTLActions)
|
|||
...state,
|
||||
forwardingHistory: action.payload
|
||||
};
|
||||
case RTLActions.SET_LOOP_SWAPS:
|
||||
return {
|
||||
...state,
|
||||
loopSwaps: action.payload
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,13 @@
|
|||
<ng-container *ngSwitchCase="dataTypeEnum.ARRAY"><span *ngFor="let arrayObj of obj.value" class="display-block w-100">{{arrayObj}}</span></ng-container>
|
||||
<ng-container *ngSwitchCase="dataTypeEnum.NUMBER">{{obj.value | number:'1.0-3'}}</ng-container>
|
||||
<ng-container *ngSwitchCase="dataTypeEnum.BOOLEAN">{{obj.value ? 'True' : 'False'}}</ng-container>
|
||||
<ng-container *ngSwitchDefault>{{obj.value}}</ng-container>
|
||||
<ng-container *ngSwitchDefault>
|
||||
<p fxLayout="row" *ngIf="data.openedBy === 'SWAP' && obj.key === 'state'; else noStyleBlock" [ngClass]="{'failed-status': obj.value === swapStateEnum.FAILED}">
|
||||
{{obj.value}}
|
||||
<mat-icon *ngIf="obj.value === swapStateEnum.FAILED" fxLayoutAlign="end end" class="icon-failed-status">info</mat-icon>
|
||||
</p>
|
||||
<ng-template #noStyleBlock>{{obj.value}}</ng-template>
|
||||
</ng-container>
|
||||
</span>
|
||||
</span>
|
||||
<ng-template #emptyField>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material';
|
|||
import { CommonService } from '../../../services/common.service';
|
||||
import { LoggerService } from '../../../services/logger.service';
|
||||
import { AlertData } from '../../../models/alertData';
|
||||
import { AlertTypeEnum, DataTypeEnum, ScreenSizeEnum } from '../../../services/consts-enums-functions';
|
||||
import { AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, SwapStateEnum } from '../../../services/consts-enums-functions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-alert-message',
|
||||
|
|
@ -12,6 +12,7 @@ import { AlertTypeEnum, DataTypeEnum, ScreenSizeEnum } from '../../../services/c
|
|||
styleUrls: ['./alert-message.component.scss']
|
||||
})
|
||||
export class AlertMessageComponent implements OnInit {
|
||||
public swapStateEnum = SwapStateEnum;
|
||||
public showQRField = '';
|
||||
public showQRName = '';
|
||||
public showCopyName = '';
|
||||
|
|
|
|||
|
|
@ -153,6 +153,12 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
|
|||
let clonedMenu = [];
|
||||
clonedMenu = JSON.parse(JSON.stringify(MENU_DATA.LNDChildren));
|
||||
this.navMenus.data = clonedMenu.filter(navMenuData => {
|
||||
if(navMenuData.children && navMenuData.children.length) {
|
||||
navMenuData.children = navMenuData.children.filter(navMenuChild => {
|
||||
return ((navMenuChild.userPersona === UserPersonaEnum.ALL || navMenuChild.userPersona === this.settings.userPersona) && navMenuChild.link !== '/lnd/loop')
|
||||
|| (navMenuChild.link === '/lnd/loop' && this.settings.swapServerUrl && this.settings.swapServerUrl.trim() !== '');
|
||||
});
|
||||
}
|
||||
return navMenuData.userPersona === UserPersonaEnum.ALL || navMenuData.userPersona === this.settings.userPersona;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
|
|||
|
||||
onCurrencyChange(event: any) {
|
||||
this.selNode.settings.currencyUnits = [...CURRENCY_UNITS, event.value];
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: event.value, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl}));
|
||||
}
|
||||
|
||||
toggleSettings(toggleField: string, event?: any) {
|
||||
|
|
@ -90,8 +90,8 @@ export class AppSettingsComponent implements OnInit, OnDestroy {
|
|||
this.logger.info(this.selNode.settings);
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Updating Settings...'));
|
||||
this.store.dispatch(new RTLActions.SaveSettings({settings: this.selNode.settings, defaultNodeIndex: defaultNodeIndex}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettings({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl}));
|
||||
this.store.dispatch(new RTLActions.SetChildNodeSettingsCL({userPersona: this.selNode.settings.userPersona, channelBackupPath: this.selNode.settings.channelBackupPath, selCurrencyUnit: this.selNode.settings.currencyUnit, currencyUnits: this.selNode.settings.currencyUnits, fiatConversion: this.selNode.settings.fiatConversion, lnImplementation: this.selNode.lnImplementation, swapServerUrl: this.selNode.settings.swapServerUrl}));
|
||||
this.done.emit();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export class Settings {
|
|||
public bitcoindConfigPath?: string,
|
||||
public enableLogging?: boolean,
|
||||
public lnServerUrl?: string,
|
||||
public swapServerUrl?: string,
|
||||
public channelBackupPath?: string,
|
||||
public currencyUnit?: string
|
||||
) { }
|
||||
|
|
@ -66,6 +67,7 @@ export interface SelNodeChild {
|
|||
currencyUnits?: string[];
|
||||
fiatConversion?: boolean;
|
||||
lnImplementation?: string;
|
||||
swapServerUrl?: string;
|
||||
}
|
||||
|
||||
export class HelpTopic {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { DataTypeEnum } from '../services/consts-enums-functions';
|
||||
import { DataTypeEnum, SwapTypeEnum } from '../services/consts-enums-functions';
|
||||
import { GetInfoRoot } from './RTLconfig';
|
||||
import { GetInfo, Invoice, Channel } from './lndModels';
|
||||
import { InvoiceCL, GetInfoCL } from './clModels';
|
||||
import { LoopQuote } from './loopModels';
|
||||
|
||||
export interface MessageErrorField {
|
||||
code: number;
|
||||
|
|
@ -72,6 +73,14 @@ export interface ShowPubkeyData {
|
|||
component?: any;
|
||||
}
|
||||
|
||||
export interface LoopAlert {
|
||||
channel: Channel;
|
||||
minQuote: LoopQuote;
|
||||
maxQuote: LoopQuote;
|
||||
direction?: SwapTypeEnum;
|
||||
component?: any;
|
||||
}
|
||||
|
||||
export interface AlertData {
|
||||
type: string; // INFORMATION/WARNING/SUCCESS/ERROR
|
||||
alertTitle?: string;
|
||||
|
|
@ -108,5 +117,5 @@ export interface ErrorData {
|
|||
export interface DialogConfig {
|
||||
width?: string;
|
||||
minHeight?: string;
|
||||
data: AlertData | ConfirmationData | ErrorData | OpenChannelAlert | CLOpenChannelAlert | InvoiceInformation | CLInvoiceInformation | ChannelInformation | OnChainAddressInformation | ShowPubkeyData;
|
||||
data: AlertData | ConfirmationData | ErrorData | OpenChannelAlert | CLOpenChannelAlert | InvoiceInformation | CLInvoiceInformation | ChannelInformation | OnChainAddressInformation | ShowPubkeyData | LoopAlert;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { SwapStateEnum, SwapTypeEnum } from '../services/consts-enums-functions';
|
||||
|
||||
export interface ChannelStatus {
|
||||
channels?: number;
|
||||
capacity?:number;
|
||||
|
|
@ -417,3 +419,20 @@ export interface PendingChannelsData {
|
|||
num_channels: number;
|
||||
limbo_balance: number;
|
||||
}
|
||||
|
||||
export interface SwapStatus {
|
||||
type?: SwapTypeEnum;
|
||||
cost_server?: string;
|
||||
cost_offchain?: string;
|
||||
htlc_address?: string;
|
||||
state?: SwapStateEnum;
|
||||
amt?: string;
|
||||
cost_onchain?: string;
|
||||
initiation_time?: string;
|
||||
initiation_time_str?: string;
|
||||
id_bytes?: string;
|
||||
last_update_time?: string;
|
||||
last_update_time_str?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
|
|
|
|||
21
src/app/shared/models/loopModels.ts
Normal file
21
src/app/shared/models/loopModels.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
export interface LoopTerms {
|
||||
min_swap_amount?: string;
|
||||
max_swap_amount?: string;
|
||||
}
|
||||
|
||||
export interface LoopQuote {
|
||||
amount?: number;
|
||||
swap_fee?: string;
|
||||
miner_fee?: string;
|
||||
prepay_amt?: string;
|
||||
cltv_delta?: number;
|
||||
swap_payment_dest?: string;
|
||||
off_chain_swap_routing_fee_percentage?: number;
|
||||
}
|
||||
|
||||
export interface LoopStatus {
|
||||
htlc_address?: string;
|
||||
id_bytes?: string;
|
||||
id?: string;
|
||||
error?: any;
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { faTachometerAlt, faLink, faBolt, faExchangeAlt, faUsers, faMapSigns, faQuestion, faSearch, faTools, faProjectDiagram, faDownload, faServer, faPercentage, faUserCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faTachometerAlt, faLink, faBolt, faExchangeAlt, faUsers, faMapSigns, faQuestion, faSearch, faTools, faProjectDiagram, faDownload, faServer, faPercentage, faInfinity, faUserCheck } from '@fortawesome/free-solid-svg-icons';
|
||||
import { UserPersonaEnum } from '../services/consts-enums-functions';
|
||||
|
||||
export const MENU_DATA: MenuRootNode = {
|
||||
|
|
@ -11,6 +11,7 @@ export const MENU_DATA: MenuRootNode = {
|
|||
{id: 33, parentId: 3, name: 'Routing', iconType: 'FA', icon: faMapSigns, link: '/lnd/routing', userPersona: UserPersonaEnum.ALL},
|
||||
{id: 34, parentId: 3, name: 'Graph Lookup', iconType: 'FA', icon: faSearch, link: '/lnd/lookups', userPersona: UserPersonaEnum.ALL},
|
||||
{id: 35, parentId: 3, name: 'Sign/Verify', iconType: 'FA', icon: faUserCheck, link: '/lnd/signverify', userPersona: UserPersonaEnum.ALL},
|
||||
{id: 36, parentId: 3, name: 'Loop', iconType: 'FA', icon: faInfinity, link: '/lnd/loop', userPersona: UserPersonaEnum.ALL},
|
||||
{id: 37, parentId: 3, name: 'Backup', iconType: 'FA', icon: faDownload, link: '/lnd/backup', userPersona: UserPersonaEnum.ALL}
|
||||
]},
|
||||
{id: 5, parentId: 0, name: 'Network', iconType: 'FA', icon: faProjectDiagram, link: '/lnd/network', userPersona: UserPersonaEnum.OPERATOR},
|
||||
|
|
|
|||
|
|
@ -118,3 +118,17 @@ export const CHANNEL_CLOSURE_TYPE = {
|
|||
FUNDING_CANCELED: { name: 'Funding Canceled', tooltip: 'Channel never fully opened' },
|
||||
ABANDONED: { name: 'Abandoned', tooltip: 'Channel abandoned by the local node' }
|
||||
}
|
||||
|
||||
export enum SwapStateEnum {
|
||||
INITIATED = 'Initiated',
|
||||
PREIMAGE_REVEALED = 'Preimage Revealed',
|
||||
HTLC_PUBLISHED = 'HTLC Published',
|
||||
SUCCESS = 'Successful',
|
||||
FAILED = 'Failed',
|
||||
INVOICE_SETTLED = 'Invoice Settled'
|
||||
}
|
||||
|
||||
export enum SwapTypeEnum {
|
||||
LOOP_OUT = 'LOOP_OUT',
|
||||
LOOP_IN = 'LOOP_IN'
|
||||
}
|
||||
|
|
|
|||
138
src/app/shared/services/loop.service.ts
Normal file
138
src/app/shared/services/loop.service.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient, HttpParams } from '@angular/common/http';
|
||||
import { throwError, of } from 'rxjs';
|
||||
import { catchError } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { environment, API_URL } from '../../../environments/environment';
|
||||
import { ErrorMessageComponent } from '../../shared/components/data-modal/error-message/error-message.component';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
import { AlertTypeEnum } from '../../shared/services/consts-enums-functions';
|
||||
import * as RTLActions from '../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../store/rtl.reducers';
|
||||
|
||||
@Injectable()
|
||||
export class LoopService {
|
||||
private CHILD_API_URL = API_URL + '/lnd';
|
||||
private loopUrl = '';
|
||||
|
||||
constructor(private httpClient: HttpClient, private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>) {}
|
||||
|
||||
loopOut(amount: number, chanId: string, targetConf: number, swapRoutingFee: number, minerFee: number, prepayRoutingFee: number, prepayAmt: number, swapFee: number, swapPublicationDeadline: number, destAddress: string) {
|
||||
let requestBody = { amount: amount, targetConf: targetConf, swapRoutingFee: swapRoutingFee, minerFee: minerFee, prepayRoutingFee: prepayRoutingFee, prepayAmt: prepayAmt, swapFee: swapFee, swapPublicationDeadline: swapPublicationDeadline, destAddress: destAddress };
|
||||
if (chanId !== '') { requestBody['chanId'] = chanId; }
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/out';
|
||||
return this.httpClient.post(this.loopUrl, requestBody).pipe(catchError(err => this.handleErrorWithoutAlert('Loop Out for Channel: ' + chanId, err)));
|
||||
}
|
||||
|
||||
getLoopOutTerms() {
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/out/terms';
|
||||
return this.httpClient.get(this.loopUrl).pipe(catchError(err => this.handleErrorWithoutAlert('Loop Out Terms', err)));
|
||||
}
|
||||
|
||||
getLoopOutQuote(amount: number, targetConf: number, swapPublicationDeadline: number) {
|
||||
let params = new HttpParams();
|
||||
params = params.append('targetConf', targetConf.toString());
|
||||
params = params.append('swapPublicationDeadline', swapPublicationDeadline.toString());
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/out/quote/' + amount;
|
||||
return this.httpClient.get(this.loopUrl, { params: params }).pipe(catchError(err => this.handleErrorWithoutAlert('Loop Out Quote', err)));
|
||||
}
|
||||
|
||||
getLoopOutTermsAndQuotes(targetConf: number) {
|
||||
let params = new HttpParams();
|
||||
params = params.append('targetConf', targetConf.toString());
|
||||
params = params.append('swapPublicationDeadline', (new Date().getTime() + (30 * 60000)).toString());
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/out/termsAndQuotes';
|
||||
return this.httpClient.get(this.loopUrl, { params: params }).pipe(catchError(err => {
|
||||
return this.handleErrorWithAlert('Loop Out Terms and Quotes', err);
|
||||
}));
|
||||
}
|
||||
|
||||
loopIn(amount: number, swapFee: number, minerFee: number, lastHop: string, externalHtlc: boolean) {
|
||||
const requestBody = { amount: amount, swapFee: swapFee, minerFee: minerFee, lastHop: lastHop, externalHtlc: externalHtlc };
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/in';
|
||||
return this.httpClient.post(this.loopUrl, requestBody).pipe(catchError(err => this.handleErrorWithoutAlert('Loop In', err)));
|
||||
}
|
||||
|
||||
getLoopInTerms() {
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/in/terms';
|
||||
return this.httpClient.get(this.loopUrl).pipe(catchError(err => this.handleErrorWithoutAlert('Loop In Terms', err)));
|
||||
}
|
||||
|
||||
getLoopInQuote(amount: number, targetConf: string, swapPublicationDeadline: number) {
|
||||
let params = new HttpParams();
|
||||
params = params.append('targetConf', targetConf.toString());
|
||||
params = params.append('swapPublicationDeadline', swapPublicationDeadline.toString());
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/in/quote/' + amount;
|
||||
return this.httpClient.get(this.loopUrl, { params: params }).pipe(catchError(err => this.handleErrorWithoutAlert('Loop In Qoute', err)));
|
||||
}
|
||||
|
||||
getLoopInTermsAndQuotes(targetConf: number) {
|
||||
let params = new HttpParams();
|
||||
params = params.append('targetConf', targetConf.toString());
|
||||
params = params.append('swapPublicationDeadline', (new Date().getTime() + (30 * 60000)).toString());
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/in/termsAndQuotes';
|
||||
return this.httpClient.get(this.loopUrl, { params: params }).pipe(catchError(err => {
|
||||
return this.handleErrorWithAlert('Loop In Terms and Quotes', err);
|
||||
}));
|
||||
}
|
||||
|
||||
getSwap(id: string) {
|
||||
this.loopUrl = this.CHILD_API_URL + environment.LOOP_API + '/swap/' + id;
|
||||
return this.httpClient.get(this.loopUrl).pipe(catchError(err => this.handleErrorWithoutAlert('Loop Get Swap for ID: ' + id, err)));
|
||||
}
|
||||
|
||||
handleErrorWithoutAlert(actionName: string, err: { status: number, error: any }) {
|
||||
this.logger.error('ERROR IN: ' + actionName + '\n' + JSON.stringify(err));
|
||||
this.store.dispatch(new RTLActions.CloseSpinner())
|
||||
if (err.status === 401) {
|
||||
this.logger.info('Redirecting to Login');
|
||||
this.store.dispatch(new RTLActions.Logout());
|
||||
} else if (err.error.errno === 'ECONNREFUSED' || err.error.error.errno === 'ECONNREFUSED') {
|
||||
this.store.dispatch(new RTLActions.OpenAlert({
|
||||
data: {
|
||||
type: 'ERROR',
|
||||
alertTitle: 'Loop Not Connected',
|
||||
message: { code: 'ECONNREFUSED', message: 'Unable to Connect to Loop Server', URL: actionName },
|
||||
component: ErrorMessageComponent
|
||||
}
|
||||
}));
|
||||
}
|
||||
return throwError(err);
|
||||
}
|
||||
|
||||
handleErrorWithAlert(errURL: string, err: any) {
|
||||
if (typeof err.error.error === 'string') {
|
||||
try {
|
||||
err = JSON.parse(err.error.error);
|
||||
} catch(err) {}
|
||||
} else {
|
||||
err = err.error.error.error ? err.error.error.error : err.error.error ? err.error.error : err.error ? err.error : { code : 500, message: 'Unknown Error' };
|
||||
}
|
||||
this.logger.error(err);
|
||||
this.store.dispatch(new RTLActions.CloseSpinner())
|
||||
if (err.status === 401) {
|
||||
this.logger.info('Redirecting to Login');
|
||||
this.store.dispatch(new RTLActions.Logout());
|
||||
} else if (err.errno === 'ECONNREFUSED') {
|
||||
this.store.dispatch(new RTLActions.OpenAlert({
|
||||
data: {
|
||||
type: 'ERROR',
|
||||
alertTitle: 'Loop Not Connected',
|
||||
message: { code: 'ECONNREFUSED', message: 'Unable to Connect to Loop Server', URL: errURL },
|
||||
component: ErrorMessageComponent
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
this.store.dispatch(new RTLActions.OpenAlert({data: {
|
||||
type: AlertTypeEnum.ERROR,
|
||||
alertTitle: 'ERROR',
|
||||
message: { code: err.code ? err.code : err.status, message: err.message ? err.message : 'Unknown Error', URL: errURL },
|
||||
component: ErrorMessageComponent
|
||||
}
|
||||
}));
|
||||
}
|
||||
return throwError(err);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ import { ServerConfigComponent } from './components/settings/server-config/serve
|
|||
import { ErrorComponent } from './components/error/error.component';
|
||||
import { CurrencyUnitConverterComponent } from './components/currency-unit-converter/currency-unit-converter.component';
|
||||
import { AuthSettingsComponent } from './components/settings/auth-settings/auth-settings.component';
|
||||
import { LoopQuoteComponent } from '../lnd/loop/loop-quote/loop-quote.component';
|
||||
import { ClipboardDirective } from './directive/clipboard.directive';
|
||||
import { AutoFocusDirective } from './directive/auto-focus.directive';
|
||||
import { MaxValidator } from './directive/max-amount.directive';
|
||||
|
|
@ -42,6 +43,9 @@ import { MinValidator } from './directive/min-amount.directive';
|
|||
import { RemoveLeadingZerosPipe } from './pipes/app.pipe';
|
||||
|
||||
import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.service';
|
||||
import { LoopStatusComponent } from '../lnd/loop/loop-status/loop-status.component';
|
||||
import { LoopOutInfoGraphicsComponent } from '../lnd/loop/loop-out-info-graphics/info-graphics.component';
|
||||
import { LoopInInfoGraphicsComponent } from '../lnd/loop/loop-in-info-graphics/info-graphics.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
|
@ -135,7 +139,11 @@ import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.s
|
|||
MinValidator,
|
||||
QRCodeModule,
|
||||
RemoveLeadingZerosPipe,
|
||||
PerfectScrollbarModule
|
||||
PerfectScrollbarModule,
|
||||
LoopQuoteComponent,
|
||||
LoopStatusComponent,
|
||||
LoopOutInfoGraphicsComponent,
|
||||
LoopInInfoGraphicsComponent
|
||||
],
|
||||
declarations: [
|
||||
AppSettingsComponent,
|
||||
|
|
@ -153,7 +161,11 @@ import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.s
|
|||
MaxValidator,
|
||||
MinValidator,
|
||||
RemoveLeadingZerosPipe,
|
||||
AuthSettingsComponent
|
||||
AuthSettingsComponent,
|
||||
LoopQuoteComponent,
|
||||
LoopStatusComponent,
|
||||
LoopOutInfoGraphicsComponent,
|
||||
LoopInInfoGraphicsComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: LoggerService, useClass: ConsoleLoggerService },
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ body {
|
|||
|
||||
.mat-sidenav-container .mat-sidenav-content {
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.sidenav {
|
||||
|
|
|
|||
|
|
@ -388,4 +388,7 @@
|
|||
fill: $primary-lighter;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header[aria-disabled='true'] {
|
||||
color: $foreground-text;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { RTLConfiguration, Settings, ConfigSettingsNode, GetInfoRoot, SelNodeChi
|
|||
import { GetInfoCL, FeesCL, PeerCL, PaymentCL, PayRequestCL, QueryRoutesCL, ChannelCL, FeeRatesCL, ForwardingHistoryResCL, InvoiceCL, ListInvoicesCL, OnChainCL } from '../shared/models/clModels';
|
||||
import {
|
||||
GetInfo, Peer, Balance, NetworkInfo, Fees, Channel, Invoice, ListInvoices, Payment, GraphNode,
|
||||
PayRequest, ChannelsTransaction, PendingChannels, ClosedChannel, Transaction, SwitchReq, SwitchRes, QueryRoutes, PendingChannelsGroup
|
||||
PayRequest, ChannelsTransaction, PendingChannels, ClosedChannel, Transaction, SwitchReq, SwitchRes, QueryRoutes, PendingChannelsGroup, SwapStatus
|
||||
} from '../shared/models/lndModels';
|
||||
|
||||
export const VOID = 'VOID';
|
||||
|
|
@ -105,6 +105,8 @@ export const GET_FORWARDING_HISTORY = 'GET_FORWARDING_HISTORY';
|
|||
export const SET_FORWARDING_HISTORY = 'SET_FORWARDING_HISTORY';
|
||||
export const GET_QUERY_ROUTES = 'GET_QUERY_ROUTES';
|
||||
export const SET_QUERY_ROUTES = 'SET_QUERY_ROUTES';
|
||||
export const FETCH_LOOP_SWAPS = 'FETCH_LOOP_SWAPS';
|
||||
export const SET_LOOP_SWAPS = 'SET_LOOP_SWAPS';
|
||||
|
||||
export const RESET_CL_STORE = 'RESET_CL_STORE';
|
||||
export const CLEAR_EFFECT_ERROR_CL = 'CLEAR_EFFECT_ERROR_CL';
|
||||
|
|
@ -596,6 +598,16 @@ export class SetQueryRoutes implements Action {
|
|||
constructor(public payload: QueryRoutes) {}
|
||||
}
|
||||
|
||||
export class FetchLoopSwaps implements Action {
|
||||
readonly type = FETCH_LOOP_SWAPS;
|
||||
constructor() {}
|
||||
}
|
||||
|
||||
export class SetLoopSwaps implements Action {
|
||||
readonly type = SET_LOOP_SWAPS;
|
||||
constructor(public payload: SwapStatus[]) {}
|
||||
}
|
||||
|
||||
export class IsAuthorized implements Action {
|
||||
readonly type = IS_AUTHORIZED;
|
||||
constructor(public payload: string) {} // payload = password
|
||||
|
|
@ -863,7 +875,7 @@ export type RTLActions =
|
|||
GetNewAddress | SetNewAddress | SetChannelTransaction |
|
||||
GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet |
|
||||
FetchConfig | ShowConfig | PeerLookup | ChannelLookup | InvoiceLookup | SetLookup |
|
||||
IsAuthorized | IsAuthorizedRes | Login | Logout | ResetPassword |
|
||||
FetchLoopSwaps | SetLoopSwaps | IsAuthorized | IsAuthorizedRes | Login | Logout | ResetPassword |
|
||||
SetChildNodeSettingsCL | FetchInfoCL | SetInfoCL | FetchFeesCL | SetFeesCL | FetchFeeRatesCL | SetFeeRatesCL |
|
||||
FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL |
|
||||
GetNewAddressCL | SetNewAddressCL |
|
||||
|
|
|
|||
|
|
@ -378,9 +378,9 @@ export class RTLEffects implements OnDestroy {
|
|||
const landingPage = isInitialSetup ? '' : 'HOME';
|
||||
let selNode = {};
|
||||
if(node.settings.fiatConversion && node.settings.currencyUnit) {
|
||||
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: [...CURRENCY_UNITS, node.settings.currencyUnit], fiatConversion: node.settings.fiatConversion, lnImplementation: node.lnImplementation };
|
||||
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: [...CURRENCY_UNITS, node.settings.currencyUnit], fiatConversion: node.settings.fiatConversion, lnImplementation: node.lnImplementation, swapServerUrl: node.settings.swapServerUrl };
|
||||
} else {
|
||||
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: CURRENCY_UNITS, fiatConversion: node.settings.fiatConversion, lnImplementation: node.lnImplementation };
|
||||
selNode = { userPersona: node.settings.userPersona, channelBackupPath: node.settings.channelBackupPath, selCurrencyUnit: node.settings.currencyUnit, currencyUnits: CURRENCY_UNITS, fiatConversion: node.settings.fiatConversion, lnImplementation: node.lnImplementation, swapServerUrl: node.settings.swapServerUrl };
|
||||
}
|
||||
this.store.dispatch(new RTLActions.ResetRootStore(node));
|
||||
this.store.dispatch(new RTLActions.ResetLNDStore(selNode));
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const environment = {
|
|||
INVOICES_API: '/invoices',
|
||||
SWITCH_API: '/switch',
|
||||
ON_CHAIN_API: '/onchain',
|
||||
LOOP_API: '/loop',
|
||||
MESSAGE_API: '/message',
|
||||
VERSION: VERSION
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ export const environment = {
|
|||
INVOICES_API: '/invoices',
|
||||
SWITCH_API: '/switch',
|
||||
ON_CHAIN_API: '/onchain',
|
||||
LOOP_API: '/loop',
|
||||
MESSAGE_API: '/message',
|
||||
VERSION: VERSION
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
export const VERSION = '0.6.8-beta';
|
||||
export const VERSION = '0.7.0-beta';
|
||||
Loading…
Add table
Add a link
Reference in a new issue