lnd multi part payment #445 (#446)

lnd multi part payment #445
pull/448/head
ShahanaFarooqui 4 years ago committed by GitHub
parent 4da3b66bc7
commit 25bdec8a70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -15,5 +15,5 @@
<link rel="stylesheet" href="styles.1268069d095d5cf8e257.css"></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.1cfb7a3d5c7630696d04.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.bac9de7c65f61812af23.js" defer></script></body>
<script src="runtime.9e7465a615254b205f19.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.3d79a2643378c8aeda8b.js" defer></script></body>
</html>

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,d=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&d.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);d.length;)d.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:"9bb271dd8dffd2d994a5",6:"02d8d1e879c0be336222",7:"4a00e92294df28ac9ca1",8:"cbebc1d46c4bfbda8693"}[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],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:"9bb271dd8dffd2d994a5",6:"02d8d1e879c0be336222",7:"4a00e92294df28ac9ca1",8:"57420fc1ef9c4c3b9030"}[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()}([]);

@ -3,16 +3,15 @@ var common = require('../../common');
var logger = require('../logger');
var options = {};
getAliasFromPubkey = (hop) => {
getAliasFromPubkey = (pubkey) => {
return new Promise(function(resolve, reject) {
options.url = common.getSelLNServerUrl() + '/v1/graph/node/' + hop.pub_key;
options.url = common.getSelLNServerUrl() + '/v1/graph/node/' + pubkey;
request(options)
.then(function(aliasBody) {
logger.info({fileName: 'Graph', msg: 'Alias: ' + JSON.stringify(aliasBody.node.alias)});
hop.pubkey_alias = aliasBody.node.alias;
resolve(hop);
.then(function(res) {
logger.info({fileName: 'Graph', msg: 'Alias: ' + JSON.stringify(res.node.alias)});
resolve(res.node.alias);
})
.catch(err => resolve(''));
.catch(err => resolve(pubkey.substring(0, 17) + '...'));
});
}
@ -169,35 +168,33 @@ exports.getQueryRoutes = (req, res, next) => {
error: (!body) ? 'Error From Server!' : body.error
});
}
if (body.routes && body.routes.length > 0) {
body.routes.forEach(route => {
if ( route.hops) {
Promise.all(
route.hops.map((hop, i) => {
hop.hop_sequence = i + 1;
return getAliasFromPubkey(hop);
})
)
.then(function(values) {
logger.info({fileName: 'Graph', msg: 'Hops with Alias: ' + JSON.stringify(body)});
res.status(200).json(body);
})
.catch(errRes => {
let err = JSON.parse(JSON.stringify(errRes));
if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
delete err.options.headers['Grpc-Metadata-macaroon'];
}
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 187, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Fetching Query Routes Failed!",
error: err.error
});
});
if(body.routes && body.routes.length && body.routes.length > 0 && body.routes[0].hops && body.routes[0].hops.length && body.routes[0].hops.length > 0) {
Promise.all(body.routes[0].hops.map(hop => {return getAliasFromPubkey(hop.pub_key)}))
.then(function(values) {
body.routes[0].hops.map((hop, i) => {
hop.hop_sequence = i + 1;
hop.pubkey_alias = values[i];
return hop;
});
logger.info({fileName: 'Graph', msg: 'Hops with Alias: ' + JSON.stringify(body)});
res.status(200).json(body);
})
.catch(errRes => {
let err = JSON.parse(JSON.stringify(errRes));
if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
delete err.options.headers['Grpc-Metadata-macaroon'];
}
});
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 196, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Fetching Query Routes Failed!",
error: err.error
});
});
} else {
res.status(200).json(body);
}
})
.catch(errRes => {
@ -208,7 +205,7 @@ exports.getQueryRoutes = (req, res, next) => {
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 204, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
logger.error({fileName: 'Graph', lineNum: 214, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Fetching Query Routes Failed!",
error: err.error
@ -262,3 +259,30 @@ exports.getRemoteFeePolicy = (req, res, next) => {
});
});
};
exports.getAliasesForPubkeys = (req, res, next) => {
options = common.getOptions();
if (req.params.pubKeys.length && req.params.pubKeys.length > 0) {
Promise.all(req.params.pubKeys.map(pubkey => {return getAliasFromPubkey(pubkey)}))
.then(function(values) {
logger.info({fileName: 'Graph', msg: 'Node Alias: ' + JSON.stringify(values)});
res.status(200).json(values);
})
.catch(errRes => {
let err = JSON.parse(JSON.stringify(errRes));
if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
delete err.options.headers['Grpc-Metadata-macaroon'];
}
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 279, msg: 'Get Aliases for Pubkeys Failed: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Getting Aliases for Pubkeys Failed!",
error: err.error
});
});
} else {
res.status(200).json([]);
}
};

@ -9,7 +9,7 @@ exports.getPayments = (req, res, next) => {
request(options).then((body) => {
const body_str = (!body) ? '' : JSON.stringify(body);
const search_idx = (!body) ? -1 : body_str.search('Not Found');
logger.info({fileName: 'Payments', msg: 'Payment Decoded Received: ' + body_str});
logger.info({fileName: 'Payments', msg: 'Payment List Received: ' + body_str});
if(!body || search_idx > -1 || body.error) {
logger.error({fileName: 'Payments', lineNum: 14, msg: 'List Payments Error: ' + ((!body || !body.error) ? 'Error From Server!' : JSON.stringify(body.error))});
res.status(500).json({
@ -20,9 +20,14 @@ exports.getPayments = (req, res, next) => {
if ( body.payments && body.payments.length > 0) {
body.payments.forEach(payment => {
payment.creation_date_str = (!payment.creation_date) ? '' : common.convertTimestampToDate(payment.creation_date);
payment.htlcs.forEach(htlc => {
htlc.attempt_time_str = (!htlc.attempt_time_ns) ? '' : common.convertTimestampToDate(Math.round(htlc.attempt_time_ns/1000000000));
htlc.resolve_time_str = (!htlc.resolve_time_ns) ? '' : common.convertTimestampToDate(Math.round(htlc.resolve_time_ns/1000000000));
});
});
body.payments = common.sortDescByKey(body.payments, 'creation_date');
}
logger.info({fileName: 'Payments', msg: 'Payment List After Dates: ' + JSON.stringify(body.payments)});
res.status(200).json(body.payments);
}
})

@ -5,6 +5,7 @@ const authCheck = require("../authCheck");
router.get("/", authCheck, graphController.getDescribeGraph);
router.get("/info", authCheck, graphController.getGraphInfo);
router.get("/nodes/:pubKeys", authCheck, graphController.getAliasesForPubkeys);
router.get("/node/:pubKey", authCheck, graphController.getGraphNode);
router.get("/edge/:chanid", authCheck, graphController.getGraphEdge);
router.get("/edge/:chanid/:localPubkey", authCheck, graphController.getRemoteFeePolicy);

@ -28,26 +28,30 @@
<table mat-table #table fxFlex="100" [dataSource]="payments" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="creation_date">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Creation Date</th>
<td mat-cell *matCellDef="let payment">{{payment?.creation_date_str}}</td>
<td mat-cell *matCellDef="let payment">
<span *ngIf="payment.status === 'SUCCEEDED'" class="dot green" matTooltip="Succeeded" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="payment.status !== 'SUCCEEDED'" class="dot red" matTooltip="Failed" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
{{payment?.creation_date_str}}
</td>
</ng-container>
<ng-container matColumnDef="payment_hash">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Hash</th>
<td mat-cell *matCellDef="let payment" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '35rem'}">{{payment?.payment_hash}}</td>
</ng-container>
<ng-container matColumnDef="fee">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee</th>
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee (Sats)</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.fee | number}}</span></td>
</ng-container>
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value (Sats)</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value | number}}</span></td>
</ng-container>
<ng-container matColumnDef="path">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Path (Hops)</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.path?.length || 0}}</span></td>
</ng-container>
<ng-container matColumnDef="hops">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">#Hops</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.htlcs[0]?.route?.hops?.length || 0}}</span></td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="pl-4 pr-3">
<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>
@ -55,15 +59,88 @@
</mat-select>
</div>
</th>
<td mat-cell *matCellDef="let payment" class="pl-4" fxLayoutAlign="end center">
<button mat-stroked-button color="primary" type="button" tabindex="4" (click)="onPaymentClick(payment,$event)">View Info</button>
<td mat-cell *matCellDef="let payment" class="px-3" fxLayoutAlign="end center">
<button mat-stroked-button color="primary" type="button" tabindex="4" (click)="onPaymentClick(payment)">View Info</button>
</td>
</ng-container>
<ng-container matColumnDef="no_payment">
<td mat-footer-cell *matFooterCellDef colspan="4">
<p *ngIf="!payments.data || payments.data.length<1">No payments available.</p>
<p *ngIf="!payments?.data || payments?.data?.length<1">No payments available.</p>
</td>
</ng-container>
<!-- Payment Group Row Start -->
<ng-container matColumnDef="groupTotal">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="start center" class="htlc-row-span">
<span *ngIf="payment.status === 'SUCCEEDED'" class="dot green" matTooltip="Succeeded" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="payment.status !== 'SUCCEEDED'" class="dot red" matTooltip="Failed" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
Total Attempts: {{payment?.htlcs?.length}}
</span>
<ng-container *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="start center" class="htlc-row-span">
<span *ngIf="htlc.status === 'SUCCEEDED'" class="dot green" matTooltip="Succeeded" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="htlc.status !== 'SUCCEEDED'" class="dot red" matTooltip="Failed" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
{{htlc.attempt_time_str}}
</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="groupHash">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="start center" class="htlc-row-span">{{payment?.payment_hash}}</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs; index as i" fxLayoutAlign="start center" class="htlc-row-span">
HTLC {{i + 1}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupFee">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="end center" class="htlc-row-span">{{payment?.fee | number:'1.0-0'}}</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="end center" class="htlc-row-span">
{{htlc.route?.total_fees | number:'1.0-0'}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupValue">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="end center" class="htlc-row-span">{{payment?.value | number:'1.0-0'}}</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="end center" class="htlc-row-span">
{{htlc.route?.total_amt | number:'1.0-0'}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupHops">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="end center" class="htlc-row-span">-</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="end center" class="htlc-row-span">
{{(htlc.route?.hops?.length || 0) | number:'1.0-0'}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupAction">
<td mat-cell *matCellDef="let payment" class="px-3">
<span fxLayoutAlign="end center">
<button mat-flat-button class="btn-htlc-expand" color="primary" type="button" tabindex="5" (click)="payment.is_expanded = !payment.is_expanded">{{payment.is_expanded ? 'Hide' : 'Show'}}</button>
</span>
<div *ngIf="payment.is_expanded">
<div *ngFor="let htlc of payment?.htlcs; index as i" fxLayoutAlign="end center">
<button mat-stroked-button class="btn-htlc-info" color="primary" type="button" tabindex="6" (click)="onHTLCClick(htlc, payment)">View {{i + 1}}</button>
</div>
</div>
</td>
</ng-container>
<tr mat-row *matRowDef="let row; columns: htlcColumns; when: is_group;" [@newlyAddedRowAnimation]="(row.payment_hash === newlyAddedPayment && flgAnimate) ? 'added' : 'notAdded'"></tr>
<!-- Payment Group Row End -->
<tr mat-footer-row *matFooterRowDef="['no_payment']" [ngClass]="{'display-none': payments.data && payments.data.length>0}"></tr>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.payment_hash === newlyAddedPayment && flgAnimate) ? 'added' : 'notAdded'"></tr>

@ -7,4 +7,21 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.mat-column-groupAction {
min-height: 4.8rem;
& .btn-htlc-expand {
width: 9rem;
}
& .btn-htlc-info {
margin-top: 0.5rem;
width: 9rem;
}
}
.htlc-row-span {
min-height: 4.2rem;
}

@ -1,7 +1,7 @@
import { Component, OnInit, OnDestroy, ViewChild, Input } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { Subject, forkJoin } from 'rxjs';
import { takeUntil, take, filter } from 'rxjs/operators';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faHistory } from '@fortawesome/free-solid-svg-icons';
@ -9,7 +9,7 @@ import { faHistory } from '@fortawesome/free-solid-svg-icons';
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table';
import { GetInfo, Payment, PayRequest, Channel } from '../../../shared/models/lndModels';
import { GetInfo, Payment, PayRequest, Channel, HTLC, Peer, Hop } from '../../../shared/models/lndModels';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, FEE_LIMIT_TYPES } from '../../../shared/services/consts-enums-functions';
import { LoggerService } from '../../../shared/services/logger.service';
import { CommonService } from '../../../shared/services/common.service';
@ -44,9 +44,11 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {};
public flgLoading: Array<Boolean | 'error'> = [true];
public information: GetInfo = {};
public peers: Peer[] = [];
public payments: any;
public paymentJSONArr: Payment[] = [];
public displayedColumns = [];
public htlcColumns = [];
public paymentDecoded: PayRequest = {};
public paymentRequest = '';
public paymentDecodedHint = '';
@ -68,15 +70,19 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
if(this.screenSize === ScreenSizeEnum.XS) {
this.flgSticky = false;
this.displayedColumns = ['creation_date', 'actions'];
this.htlcColumns = ['groupTotal', 'groupAction'];
} else if(this.screenSize === ScreenSizeEnum.SM) {
this.flgSticky = false;
this.displayedColumns = ['creation_date', 'value', 'actions'];
this.htlcColumns = ['groupTotal', 'groupValue', 'groupAction'];
} else if(this.screenSize === ScreenSizeEnum.MD) {
this.flgSticky = false;
this.displayedColumns = ['creation_date', 'fee', 'value', 'actions'];
this.htlcColumns = ['groupTotal', 'groupFee', 'groupValue', 'groupAction'];
} else {
this.flgSticky = true;
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'path', 'actions'];
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'hops', 'actions'];
this.htlcColumns = ['groupTotal', 'groupHash', 'groupFee', 'groupValue', 'groupHops', 'groupAction'];
}
}
@ -91,6 +97,7 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
});
this.information = rtlStore.information;
this.selNode = rtlStore.nodeSettings;
this.peers = rtlStore.peers;
this.activeChannels = rtlStore.allChannels.filter(channel => channel.active);
this.paymentJSONArr = (rtlStore.payments && rtlStore.payments.length > 0) ? rtlStore.payments : [];
this.payments = (rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<Payment>([...this.paymentJSONArr]);
@ -240,6 +247,10 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
this.feeLimit = null;
this.selFeeLimitType = FEE_LIMIT_TYPES[0];
}
}
is_group(index: number, payment: Payment) {
return payment.htlcs && payment.htlcs.length > 1;
}
resetData() {
@ -251,31 +262,53 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
this.form.resetForm();
}
onPaymentClick(selPayment: Payment, event: any) {
let pathAliases = '';
if (selPayment.path && selPayment.path.length > 0) {
forkJoin(this.dataService.getAliasesFromPubkeys(selPayment.path))
.pipe(takeUntil(this.unSubs[3]))
.subscribe((nodes: any) => {
nodes.forEach(node => {
pathAliases = pathAliases === '' ? node.node.alias : pathAliases + '\n' + node.node.alias;
getHopDetails(hops: Hop[]) {
let self = this;
return hops.reduce(function (accumulator, currentHop) {
let peerFound = self.peers.find(peer => peer.pub_key === currentHop.pub_key);
if (peerFound && peerFound.alias) {
accumulator.push('<pre>Channel: ' + peerFound.alias.padEnd(20) + '&Tab;&Tab;&Tab;Amount (Sats): ' + self.decimalPipe.transform(currentHop.amt_to_forward) + '</pre>');
} else {
self.dataService.getAliasFromPubkey(currentHop.pub_key)
.pipe(takeUntil(self.unSubs[1]))
.subscribe((res: any) => {
accumulator.push('<pre>Channel: ' + (res.node && res.node.alias ? res.node.alias.padEnd(20) : (currentHop.pub_key.substring(0, 17) + '...')) + '&Tab;&Tab;&Tab;Amount (Sats): ' + self.decimalPipe.transform(currentHop.amt_to_forward) + '</pre>');
});
this.openPaymentInModal(selPayment, pathAliases);
});
} else {
this.openPaymentInModal(selPayment, pathAliases);
}
}
return accumulator;
}, []);
}
openPaymentInModal(selPayment: Payment, pathAliases: string) {
onHTLCClick(selHtlc: HTLC, selPayment: Payment) {
const reorderedHTLC = [
[{key: 'payment_hash', value: selPayment.payment_hash, title: 'Payment Hash', width: 100, type: DataTypeEnum.STRING}],
[{key: 'payment_request', value: selPayment.payment_request, title: 'Payment Request', width: 100, type: DataTypeEnum.STRING}],
[{key: 'preimage', value: selHtlc.preimage, title: 'Preimage', width: 100, type: DataTypeEnum.STRING}],
[{key: 'status', value: selHtlc.status, title: 'Status', width: 33, type: DataTypeEnum.STRING},
{key: 'attempt_time_str', value: selHtlc.attempt_time_str, title: 'Attempt Time', width: 33, type: DataTypeEnum.DATE_TIME},
{key: 'resolve_time_str', value: selHtlc.resolve_time_str, title: 'Resolve Time', width: 34, type: DataTypeEnum.DATE_TIME}],
[{key: 'total_amt', value: selHtlc.route.total_amt, title: 'Amount (Sats)', width: 33, type: DataTypeEnum.NUMBER},
{key: 'total_fees', value: selHtlc.route.total_fees, title: 'Fee (Sats)', width: 33, type: DataTypeEnum.NUMBER},
{key: 'total_time_lock', value: selHtlc.route.total_time_lock, title: 'Total Time Lock', width: 34, type: DataTypeEnum.NUMBER}],
[{key: 'hops', value: this.getHopDetails(selHtlc.route.hops), title: 'Hops', width: 100, type: DataTypeEnum.ARRAY}]
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'HTLC Information',
message: reorderedHTLC,
scrollable: selHtlc.route && selHtlc.route.hops && selHtlc.route.hops.length > 1
}}));
}
onPaymentClick(selPayment: Payment) {
const reorderedPayment = [
[{key: 'payment_hash', value: selPayment.payment_hash, title: 'Payment Hash', width: 100, type: DataTypeEnum.STRING}],
[{key: 'payment_preimage', value: selPayment.payment_preimage, title: 'Payment Preimage', width: 100, type: DataTypeEnum.STRING}],
[{key: 'path', value: pathAliases, title: 'Path', width: 100, type: DataTypeEnum.STRING}],
[{key: 'creation_date_str', value: selPayment.creation_date_str, title: 'Creation Date', width: 50, type: DataTypeEnum.DATE_TIME},
{key: 'fee', value: selPayment.fee, title: 'Fee', width: 50, type: DataTypeEnum.NUMBER}],
[{key: 'payment_request', value: selPayment.payment_request, title: 'Payment Request', width: 100, type: DataTypeEnum.STRING}],
[{key: 'status', value: selPayment.status, title: 'Status', width: 50, type: DataTypeEnum.STRING},
{key: 'creation_date_str', value: selPayment.creation_date_str, title: 'Creation Date', width: 50, type: DataTypeEnum.DATE_TIME}],
[{key: 'value_msat', value: selPayment.value_msat, title: 'Value (mSats)', width: 50, type: DataTypeEnum.NUMBER},
{key: 'value_sat', value: selPayment.value, title: 'Value (Sats)', width: 50, type: DataTypeEnum.NUMBER}]
{key: 'fee_msat', value: selPayment.fee_msat, title: 'Fee (mSats)', width: 50, type: DataTypeEnum.NUMBER}]
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
@ -290,7 +323,15 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
onDownloadCSV() {
if(this.payments.data && this.payments.data.length > 0) {
this.commonService.downloadFile(this.payments.data, 'Payments');
let paymentsDataCopy = JSON.parse(JSON.stringify(this.payments.data));
let flattenedPayments = paymentsDataCopy.reduce((acc, curr) => {
if (curr.htlcs) {
return acc.concat(curr.htlcs);
} else {
return acc.concat(curr);
}
}, []);
this.commonService.downloadFile(flattenedPayments, 'Payments');
}
}

@ -49,9 +49,7 @@
<span *ngIf="obj && (!!obj.value || obj.value === 0); else emptyField">
<span [ngSwitch]="obj.type" class="foreground-secondary-text" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<ng-container *ngSwitchCase="dataTypeEnum.ARRAY">
<span *ngFor="let arrayObj of obj.value" class="display-block w-100">
{{arrayObj | json}}
</span>
<span *ngFor="let arrayObj of obj.value" class="display-block w-100" [innerHTML]="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>

@ -13,7 +13,10 @@ import { AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, SwapStateEnum } from '../.
styleUrls: ['./alert-message.component.scss']
})
export class AlertMessageComponent implements OnInit, AfterViewChecked {
@ViewChild('scrollContainer', { static: true }) scrollContainer: ElementRef;
private scrollContainer: ElementRef;
@ViewChild('scrollContainer') set container(containerContent: ElementRef) {
if(containerContent) { this.scrollContainer = containerContent; }
}
public swapStateEnum = SwapStateEnum;
public showQRField = '';
public showQRName = '';
@ -45,7 +48,9 @@ export class AlertMessageComponent implements OnInit, AfterViewChecked {
}
ngAfterViewChecked() {
this.shouldScroll = this.scrollContainer && this.scrollContainer.nativeElement ? this.scrollContainer.nativeElement.classList.value.includes('ps--active-y') : false;
setTimeout(() => {
this.shouldScroll = this.scrollContainer && this.scrollContainer.nativeElement ? this.scrollContainer.nativeElement.classList.value.includes('ps--active-y') : false;
});
}
onScrollDown() {

@ -246,10 +246,14 @@ export interface HopHint {
}
export interface HTLC {
incoming?: boolean;
amount?: number;
hash_lock?: string;
expiration_height?: number;
status?: string;
route?: Route;
attempt_time_ns?: string;
resolve_time_ns?: string;
failure?: any;
preimage?: string;
attempt_time_str?: string;
resolve_time_str?: string;
}
export interface Invoice {
@ -320,12 +324,21 @@ export interface Payment {
creation_date?: number;
creation_date_str?: string;
payment_hash?: string;
payment_request?: string;
status?: string;
path?: string[];
fee?: number;
fee_sat?: number;
fee_msat?: number;
value_msat?: number;
value_sat?: number;
value?: number;
payment_preimage?: string;
creation_time_ns?: string;
payment_index?: string;
failure_reason?: string;
htlcs: HTLC[];
is_expanded?: boolean;
}
export interface PayRequest {
@ -372,6 +385,9 @@ export interface Hop {
amt_to_forward_msat?: string;
fee_msat?: string;
pub_key?: string;
tlv_payload?: boolean;
mpp_record?: { payment_addr?: string; total_amt_msat?: number; }
custom_records?: any;
}
export interface Route {

@ -45,16 +45,12 @@ export class DataService implements OnInit, OnDestroy {
return this.httpClient.get(environment.CONF_API + '/rates');
}
getAliasesFromPubkeys(pubkeys: any) {
let nodes$: Array<Observable<any>> = [];
pubkeys.forEach(pubkey => {
nodes$.push(
this.httpClient.get(this.childAPIUrl + environment.NETWORK_API + '/node/' + pubkey)
.pipe(takeUntil(this.unSubs[0]),
catchError(err => of({node: {alias: pubkey}})))
);
});
return nodes$;
getAliasesFromPubkeys(pubkeys: string[]) {
return this.httpClient.get(this.childAPIUrl + environment.NETWORK_API + '/nodes/' + pubkeys)
}
getAliasFromPubkey(pubkey: string) {
return this.httpClient.get(this.childAPIUrl + environment.NETWORK_API + '/node/' + pubkey)
}
signMessage(msg: string) {

Loading…
Cancel
Save