Release 0.9.3 (#532)
ECL - Channel Force Close Bug Fix #523 ECL - Routing Filter Bug Fix #525 ECL - Adding Routing Peers Tab #527 LND - Open Channel Bump Fee #529pull/563/head v0.9.3
parent
58c46ebf71
commit
80516e1ffe
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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:"07193c762dd7469c63c8",6:"d5f36502e36ce33775a8",7:"e955a16c6af1870f3a25",8:"d2aae145b0c672e1b7ff"}[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:"60ddf8569c2860edb59c",6:"d5f36502e36ce33775a8",7:"a355238233f02a06308f",8:"29ee25c8adea70ea041a"}[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()}([]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,86 @@
|
||||
<div fxLayout="column" fxLayoutAlign="start stretch" class="padding-gap-large">
|
||||
<div fxLayout="column" fxLayout.gt-md="row" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x page-sub-title-container">
|
||||
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start start">
|
||||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container w-100" [ngClass]="{'mt-2': screenSize === screenSizeEnum.XS, 'mt-1': screenSize === screenSizeEnum.SM}">
|
||||
<div fxFlex="70">Incoming</div>
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyIncomingFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start start" fxFlex="100" class="table-container w-100">
|
||||
<table mat-table #tableIn [dataSource]="RoutingPeersIncoming" matSort fxFlex="100" class="overflow-auto incoming-table">
|
||||
<ng-container matColumnDef="channelId">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Channel ID</th>
|
||||
<td mat-cell *matCellDef="let rPeer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '5rem' : '10rem'}">{{rPeer.channelId}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="alias">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Peer Alias</th>
|
||||
<td mat-cell *matCellDef="let rPeer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '5rem' : '10rem'}">{{rPeer.alias}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="events">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Events</th>
|
||||
<td mat-cell *matCellDef="let rPeer"><span fxLayoutAlign="end center">{{rPeer.events | number}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="totalAmount">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount (Sats)</th>
|
||||
<td mat-cell *matCellDef="let rPeer"><span fxLayoutAlign="end center">{{rPeer.totalAmount | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="totalFee">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee (Sats)</th>
|
||||
<td mat-cell *matCellDef="let rPeer"><span fxLayoutAlign="end center">{{rPeer.totalFee | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="no_incoming_event">
|
||||
<td mat-footer-cell *matFooterCellDef colspan="4">
|
||||
<p *ngIf="!RoutingPeersIncoming?.data || RoutingPeersIncoming?.data?.length<1">No incoming routing peer available.</p>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-footer-row *matFooterRowDef="['no_incoming_event']" [ngClass]="{'display-none': RoutingPeersIncoming?.data && RoutingPeersIncoming?.data?.length>0}"></tr>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start start">
|
||||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container w-100" [ngClass]="{'mt-2': screenSize !== screenSizeEnum.LG}">
|
||||
<div fxFlex="70">Outgoing</div>
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyOutgoingFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container w-100">
|
||||
<table mat-table #tableOut [dataSource]="RoutingPeersOutgoing" matSort fxFlex="100" class="overflow-auto outgoing-table">
|
||||
<ng-container matColumnDef="channelId">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Channel ID</th>
|
||||
<td mat-cell *matCellDef="let rPeer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '5rem' : '10rem'}">{{rPeer.channelId}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="alias">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Peer Alias</th>
|
||||
<td mat-cell *matCellDef="let rPeer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '5rem' : '10rem'}">{{rPeer.alias}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="events">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Events</th>
|
||||
<td mat-cell *matCellDef="let rPeer"><span fxLayoutAlign="end center">{{rPeer.events | number}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="totalAmount">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount (Sats)</th>
|
||||
<td mat-cell *matCellDef="let rPeer"><span fxLayoutAlign="end center">{{rPeer.totalAmount | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="totalFee">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee (Sats)</th>
|
||||
<td mat-cell *matCellDef="let rPeer"><span fxLayoutAlign="end center">{{rPeer.totalFee | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="no_outgoing_event">
|
||||
<td mat-footer-cell *matFooterCellDef colspan="4">
|
||||
<p *ngIf="!RoutingPeersOutgoing?.data || RoutingPeersOutgoing?.data?.length<1">No outgoing routing peer available.</p>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr mat-footer-row *matFooterRowDef="['no_outgoing_event']" [ngClass]="{'display-none': RoutingPeersOutgoing?.data && RoutingPeersOutgoing?.data?.length>0}"></tr>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,6 @@
|
||||
.mat-column-channelId, .mat-column-alias {
|
||||
flex: 1 1 10%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ECLRoutingPeersComponent } from './routing-peers.component';
|
||||
|
||||
describe('ECLRoutingPeersComponent', () => {
|
||||
let component: ECLRoutingPeersComponent;
|
||||
let fixture: ComponentFixture<ECLRoutingPeersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ECLRoutingPeersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ECLRoutingPeersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,101 @@
|
||||
import { Component, OnInit, OnChanges, ViewChild, Input } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
import { PaymentRelayed, RoutingPeers } from '../../../shared/models/eclModels';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { CommonService } from '../../../shared/services/common.service';
|
||||
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
import { ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-ecl-routing-peers',
|
||||
templateUrl: './routing-peers.component.html',
|
||||
styleUrls: ['./routing-peers.component.scss']
|
||||
})
|
||||
export class ECLRoutingPeersComponent implements OnInit, OnChanges {
|
||||
@ViewChild(MatSort, { static: true }) sortIn: MatSort;
|
||||
@ViewChild('tableOut', {read: MatSort, static: true}) sortOut: MatSort;
|
||||
@Input() routingPeersData: any;
|
||||
public displayedColumns = [];
|
||||
public RoutingPeersIncoming: any;
|
||||
public RoutingPeersOutgoing: any;
|
||||
public flgSticky = false;
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
|
||||
constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
if(this.screenSize === ScreenSizeEnum.XS) {
|
||||
this.flgSticky = false;
|
||||
this.displayedColumns = ['alias', 'totalFee'];
|
||||
} else if(this.screenSize === ScreenSizeEnum.SM) {
|
||||
this.flgSticky = false;
|
||||
this.displayedColumns = ['alias', 'events', 'totalFee'];
|
||||
} else if(this.screenSize === ScreenSizeEnum.MD) {
|
||||
this.flgSticky = false;
|
||||
this.displayedColumns = ['alias', 'events', 'totalAmount', 'totalFee'];
|
||||
} else {
|
||||
this.flgSticky = true;
|
||||
this.displayedColumns = ['channelId', 'alias', 'events', 'totalAmount', 'totalFee'];
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {}
|
||||
|
||||
ngOnChanges() {
|
||||
this.loadRoutingPeersTable(this.routingPeersData);
|
||||
}
|
||||
|
||||
loadRoutingPeersTable(forwardingEvents: PaymentRelayed[]) {
|
||||
if (forwardingEvents.length > 0) {
|
||||
const results = this.groupRoutingPeers(forwardingEvents);
|
||||
this.RoutingPeersIncoming = new MatTableDataSource<RoutingPeers>(results[0]);
|
||||
this.RoutingPeersIncoming.sort = this.sortIn;
|
||||
this.logger.info(this.RoutingPeersIncoming);
|
||||
this.RoutingPeersOutgoing = new MatTableDataSource<RoutingPeers>(results[1]);
|
||||
this.RoutingPeersOutgoing.sort = this.sortOut;
|
||||
this.logger.info(this.RoutingPeersOutgoing);
|
||||
} else {
|
||||
// To reset table after other Forwarding history calls
|
||||
this.RoutingPeersIncoming = new MatTableDataSource<RoutingPeers>([]);
|
||||
this.RoutingPeersOutgoing = new MatTableDataSource<RoutingPeers>([]);
|
||||
}
|
||||
}
|
||||
|
||||
groupRoutingPeers(forwardingEvents: PaymentRelayed[]) {
|
||||
const incomingResults: RoutingPeers[] = [];
|
||||
const outgoingResults: RoutingPeers[] = [];
|
||||
forwardingEvents.forEach(event => {
|
||||
const incoming = incomingResults.find(result => result.channelId === event.fromChannelId);
|
||||
const outgoing = outgoingResults.find(result => result.channelId === event.toChannelId);
|
||||
if (!incoming) {
|
||||
incomingResults.push({channelId: event.fromChannelId, alias: event.fromChannelAlias, events: 1, totalAmount: +event.amountIn, totalFee: (event.amountIn - event.amountOut)});
|
||||
} else {
|
||||
incoming.events++;
|
||||
incoming.totalAmount = +incoming.totalAmount + +event.amountIn;
|
||||
incoming.totalFee = +incoming.totalFee + (event.amountIn - event.amountOut);
|
||||
}
|
||||
if (!outgoing) {
|
||||
outgoingResults.push({channelId: event.toChannelId, alias: event.toChannelAlias, events: 1, totalAmount: +event.amountOut, totalFee: (event.amountIn - event.amountOut)});
|
||||
} else {
|
||||
outgoing.events++;
|
||||
outgoing.totalAmount = +outgoing.totalAmount + +event.amountOut;
|
||||
outgoing.totalFee = +outgoing.totalFee + (event.amountIn - event.amountOut);
|
||||
}
|
||||
});
|
||||
return [this.commonService.sortDescByKey(incomingResults, 'totalFee'), this.commonService.sortDescByKey(outgoingResults, 'totalFee')];
|
||||
}
|
||||
|
||||
applyIncomingFilter(selFilter: string) {
|
||||
this.RoutingPeersIncoming.filter = selFilter;
|
||||
}
|
||||
|
||||
applyOutgoingFilter(selFilter: string) {
|
||||
this.RoutingPeersOutgoing.filter = selFilter;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<span class="page-title">Bump Fee</span>
|
||||
</div>
|
||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-5px">
|
||||
<form fxLayout="column">
|
||||
<div fxLayout="column" class="bordered-box my-2 p-2">
|
||||
<p fxLayoutAlign="start center" class="pb-1 word-break">Bump fee for channel point: {{bumpFeeChannel?.channel?.channel_point}}</p>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<div fxFlex="100" class="alert alert-info">
|
||||
<fa-icon [icon]="faInfoCircle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span fxLayout="column" fxFlex="100">Bumping fee on pending open channels is an advanced feature, attempt it only if you are familiar with the functionality of Bitcoin transactions.
|
||||
<div>Before attempting fee bump ensure the following:</div>
|
||||
<div class="pl-1">1: Use a Bitcoin block explorer to ensure that channel opening transaction is not confirmed.</div>
|
||||
<div class="pl-1">2: The channel opening transaction must have a sizable change output, which can be spent further. The fee cannot be bumped without the change output.</div>
|
||||
<div class="pl-1">3: Find the index value of the change output via a block explorer.</div>
|
||||
<div class="pl-1">4: Enter the index value of the change output in the form below and the desired fee rate.</div>
|
||||
<div class="pl-1">5: Upon successful fee bump, use your block explorer to track the child transaction in the mempool, which should be linked with the change output transaction.</div>
|
||||
</span>
|
||||
</div>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxFlex.gt-sm="32" fxLayoutAlign="start end">
|
||||
<input matInput [(ngModel)]="outputIndex" placeholder="Index for Change Output" type="number" step="1" min="0" tabindex="1" required name="outputIdx" #outputIdx="ngModel">
|
||||
<mat-error *ngIf="outputIdx.errors?.required">Index for change output is required.</mat-error>
|
||||
<mat-error *ngIf="outputIdx.errors?.pendingChannelOutputIndex">Invalid index value.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex.gt-sm="32">
|
||||
<mat-select [(value)]="selTransType" (selectionChange)="blocks = null;fees = null;" tabindex="2">
|
||||
<mat-option *ngFor="let transType of transTypes" [value]="transType.id">
|
||||
{{transType.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex.gt-sm="32" fxLayoutAlign="start end" *ngIf="selTransType=='1'">
|
||||
<input matInput [(ngModel)]="blocks" placeholder="Number of Blocks" type="number" name="blocks" step="1"
|
||||
min="0" required tabindex="3" #blcks="ngModel">
|
||||
<mat-error *ngIf="!blocks">Number of blocks is required.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex.gt-sm="32" fxLayoutAlign="start end" *ngIf="selTransType=='2'">
|
||||
<input matInput [(ngModel)]="fees" placeholder="Fees (Sats/Byte)"
|
||||
type="number" name="fees" step="1" min="0" required tabindex="4" #fee="ngModel">
|
||||
<mat-error *ngIf="!fees">Fees is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="bumpFeeError !== ''">
|
||||
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span>{{bumpFeeError}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutAlign="end center">
|
||||
<button mat-stroked-button color="primary" type="reset" class="mr-1" (click)="resetData()" tabindex="5" default>Clear</button>
|
||||
<button mat-flat-button color="primary" type="submit" tabindex="6" (click)="onBumpFee()">{{bumpFeeError !== '' ? 'Retry Bump Fee' : 'Bump Fee'}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { BumpFeeComponent } from './bump-fee.component';
|
||||
|
||||
describe('BumpFeeComponent', () => {
|
||||
let component: BumpFeeComponent;
|
||||
let fixture: ComponentFixture<BumpFeeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ BumpFeeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(BumpFeeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,78 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild, Inject } from '@angular/core';
|
||||
import { NgModel } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { faInfoCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { PendingOpenChannel } from '../../../../shared/models/lndModels';
|
||||
import { PendingOpenChannelInformation } from '../../../../shared/models/alertData';
|
||||
import { TRANS_TYPES } from '../../../../shared/services/consts-enums-functions';
|
||||
import { DataService } from '../../../../shared/services/data.service';
|
||||
|
||||
import * as fromRTLReducer from '../../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-bump-fee',
|
||||
templateUrl: './bump-fee.component.html',
|
||||
styleUrls: ['./bump-fee.component.scss']
|
||||
})
|
||||
export class BumpFeeComponent implements OnInit, OnDestroy {
|
||||
private outputIdx: NgModel;
|
||||
@ViewChild('outputIdx') set payReq(outIdx: NgModel) {if(outIdx) { this.outputIdx = outIdx; }}
|
||||
public bumpFeeChannel: PendingOpenChannel;
|
||||
public transTypes = [...TRANS_TYPES];
|
||||
public selTransType = '1';
|
||||
public blocks = null;
|
||||
public fees = null;
|
||||
public outputIndex = null;
|
||||
public faInfoCircle = faInfoCircle;
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public bumpFeeError = '';
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<BumpFeeComponent>, @Inject(MAT_DIALOG_DATA) public data: PendingOpenChannelInformation, private store: Store<fromRTLReducer.RTLState>, private dataService: DataService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.transTypes = this.transTypes.splice(1);
|
||||
this.bumpFeeChannel = this.data.pendingChannel;
|
||||
const channelPointArr = this.bumpFeeChannel.channel && this.bumpFeeChannel.channel.channel_point ? this.bumpFeeChannel.channel.channel_point.split(':') : [];
|
||||
this.bumpFeeChannel.channel.txid_str = channelPointArr[0] ? channelPointArr[0] : (this.bumpFeeChannel.channel && this.bumpFeeChannel.channel.channel_point ? this.bumpFeeChannel.channel.channel_point : '');
|
||||
this.bumpFeeChannel.channel.output_index = channelPointArr[1] ? +channelPointArr[1] : null;
|
||||
}
|
||||
|
||||
onBumpFee() {
|
||||
if (this.outputIndex === this.bumpFeeChannel.channel.output_index) {
|
||||
this.outputIdx.control.setErrors({'pendingChannelOutputIndex': true});
|
||||
return true;
|
||||
}
|
||||
if ((!this.outputIndex && this.outputIndex !== 0) || (this.selTransType === '1' && (!this.blocks || this.blocks === 0)) || (this.selTransType === '2' && (!this.fees || this.fees === 0))) { return true; }
|
||||
this.dataService.bumpFee(this.bumpFeeChannel.channel.txid_str, this.outputIndex, this.blocks, this.fees).pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe(res => {
|
||||
this.dialogRef.close(false);
|
||||
}, (err) => {
|
||||
console.error(err);
|
||||
this.bumpFeeError = err.message ? err.message : err;
|
||||
});
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.bumpFeeError = '';
|
||||
this.selTransType = '1';
|
||||
this.blocks = null;
|
||||
this.fees = null;
|
||||
this.outputIdx.control.setErrors(null);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
export const VERSION = '0.9.2-beta';
|
||||
export const VERSION = '0.9.3-beta';
|
Loading…
Reference in New Issue