Mempool openchannel minfee (#1388)

Open channel model block if min fee is higher
pull/1391/head
ShahanaFarooqui 3 weeks ago committed by GitHub
parent 40f6c4d933
commit a96422f9c4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

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

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

@ -0,0 +1 @@
(()=>{"use strict";var e,v={},m={};function r(e){var o=m[e];if(void 0!==o)return o.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(o,t,i,d)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,d]=e[n],c=!0,f=0;f<t.length;f++)(!1&d||a>=d)&&Object.keys(r.O).every(b=>r.O[b](t[f]))?t.splice(f--,1):(c=!1,d<a&&(a=d));if(c){e.splice(n--,1);var u=i();void 0!==u&&(o=u)}}return o}d=d||0;for(var n=e.length;n>0&&e[n-1][2]>d;n--)e[n]=e[n-1];e[n]=[t,i,d]},r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((o,t)=>(r.f[t](e,o),o),[])),r.u=e=>e+"."+{125:"c9e382333be6e677",456:"b73706bd7985d63a",570:"58fb22012be84615",758:"b6dcd2f2b36dacf0"}[e]+".js",r.miniCssF=e=>{},r.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={},o="RTLApp:";r.l=(t,i,d,n)=>{if(e[t])e[t].push(i);else{var a,c;if(void 0!==d)for(var f=document.getElementsByTagName("script"),u=0;u<f.length;u++){var l=f[u];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==o+d){a=l;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",o+d),a.src=r.tu(t)),e[t]=[i];var s=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:o=>o},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,d)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)d.push(n[2]);else if(666!=i){var a=new Promise((l,s)=>n=e[i]=[l,s]);d.push(n[2]=a);var c=r.p+r.u(i),f=new Error;r.l(c,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var s=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;f.message="Loading chunk "+i+" failed.\n("+s+": "+p+")",f.name="ChunkLoadError",f.type=s,f.request=p,n[1](f)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var o=(i,d)=>{var f,u,[n,a,c]=d,l=0;if(n.some(p=>0!==e[p])){for(f in a)r.o(a,f)&&(r.m[f]=a[f]);if(c)var s=c(r)}for(i&&i(d);l<n.length;l++)r.o(e,u=n[l])&&e[u]&&e[u][0](),e[u]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();

@ -1 +0,0 @@
(()=>{"use strict";var e,v={},m={};function r(e){var o=m[e];if(void 0!==o)return o.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(o,t,i,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,f]=e[n],c=!0,l=0;l<t.length;l++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(c=!1,f<a&&(a=f));if(c){e.splice(n--,1);var d=i();void 0!==d&&(o=d)}}return o}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,i,f]},r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((o,t)=>(r.f[t](e,o),o),[])),r.u=e=>e+"."+{125:"2d8b0d451f9e6528",456:"a7433b9c5b34e0df",570:"a47a5e74ba9177e8",758:"b6dcd2f2b36dacf0"}[e]+".js",r.miniCssF=e=>{},r.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={},o="RTLApp:";r.l=(t,i,f,n)=>{if(e[t])e[t].push(i);else{var a,c;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==o+f){a=u;break}}a||(c=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",o+f),a.src=r.tu(t)),e[t]=[i];var s=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:o=>o},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,f)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=i){var a=new Promise((u,s)=>n=e[i]=[u,s]);f.push(n[2]=a);var c=r.p+r.u(i),l=new Error;r.l(c,u=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var s=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+i+" failed.\n("+s+": "+p+")",l.name="ChunkLoadError",l.type=s,l.request=p,n[1](l)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var o=(i,f)=>{var l,d,[n,a,c]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(c)var s=c(r)}for(i&&i(f);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[d]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();

@ -43,13 +43,14 @@ export class CLNBumpFeeComponent implements OnInit, OnDestroy {
public bumpFeeError = '';
public flgShowDustWarning = false;
public dustOutputValue = 0;
public recommendedFee = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private actions: Actions, public dialogRef: MatDialogRef<CLNBumpFeeComponent>, @Inject(MAT_DIALOG_DATA) public data: CLNChannelInformation, private store: Store<RTLState>, private logger: LoggerService, private dataService: DataService, private snackBar: MatSnackBar) { }
ngOnInit() {
this.bumpFeeChannel = this.data.channel;
this.logger.info(this.bumpFeeChannel);
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
@ -81,7 +82,7 @@ export class CLNBumpFeeComponent implements OnInit, OnDestroy {
payload: {
destination: action.payload,
satoshi: 'all',
feerate: (+(this.fees || 0) * 1000).toString(),
feerate: (+(this.fees || 0) * 1000).toString() + 'perkb',
utxos: [this.bumpFeeChannel.funding_txid + ':' + (this.outputIndex || '').toString()]
}
}));

@ -42,22 +42,24 @@
</mat-expansion-panel-header>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayoutAlign.gt-sm="space-between center" fxLayout.gt-sm="row wrap">
<div fxFlex="54" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '48' : '100'">
<div fxFlex="64" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '40' : '100'">
<mat-label>Fee Rate</mat-label>
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate" (selectionChange)="customFeeRate=null">
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate" (selectionChange)="onSelFeeRateChanged($event)">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field *ngIf="selFeeRate === 'customperkb' && !flgMinConf" fxLayout="column" fxFlex="48" fxLayoutAlign="end center">
<mat-form-field *ngIf="selFeeRate === 'customperkb' && !flgMinConf" fxLayout="column" fxFlex="58" fxLayoutAlign="end center">
<mat-label>Fee Rate (Sats/vByte)</mat-label>
<input #custFeeRate="ngModel" matInput type="number" name="custFeeRate" tabindex="4" [step]="0.1" [min]="0" [required]="selFeeRate === 'customperkb' && !flgMinConf" [(ngModel)]="customFeeRate">
<input #custFeeRate="ngModel" matInput type="number" name="custFeeRate" tabindex="4" [step]="1" [min]="recommendedFee.minimumFee" [required]="selFeeRate === 'customperkb' && !flgMinConf" [(ngModel)]="customFeeRate">
<mat-hint>Mempool Min: {{recommendedFee.minimumFee}} (Sats/vByte)</mat-hint>
<mat-error *ngIf="selFeeRate === 'customperkb' && !flgMinConf && !customFeeRate">Fee Rate is required.</mat-error>
<mat-error *ngIf="selFeeRate === 'customperkb' && !flgMinConf && customFeeRate && customFeeRate < recommendedFee.minimumFee">Lower than min feerate {{recommendedFee.minimumFee}} in the mempool.</mat-error>
</mat-form-field>
</div>
<div fxFlex="42" fxLayout="row" fxLayoutAlign="start center">
<div fxFlex="32" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="7" tabindex="5" color="primary" name="flgMinConf" fxLayoutAlign="stretch start" [ngClass]="{'mr-6': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM, 'mr-2': screenSize === screenSizeEnum.MD || screenSize === screenSizeEnum.LG || screenSize === screenSizeEnum.XL}" [(ngModel)]="flgMinConf" (change)="flgMinConf ? selFeeRate=null : minConfValue=null" />
<mat-form-field fxLayout="column" fxFlex="93">
<mat-label>Min Confirmation Blocks</mat-label>

@ -8,11 +8,14 @@ import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { LoggerService } from '../../../../shared/services/logger.service';
import { DataService } from '../../../../shared/services/data.service';
import { CommonService } from '../../../../shared/services/common.service';
import { Peer, GetInfo, UTXO } from '../../../../shared/models/clnModels';
import { Peer, GetInfo, UTXO, SaveChannel } from '../../../../shared/models/clnModels';
import { CLNOpenChannelAlert } from '../../../../shared/models/alertData';
import { APICallStatusEnum, CLNActions, FEE_RATE_TYPES, ScreenSizeEnum } from '../../../../shared/services/consts-enums-functions';
import { RecommendedFeeRates } from '../../../../shared/models/rtlModels';
import { RTLState } from '../../../../store/rtl.state';
import { saveNewChannel } from '../../../store/cln.actions';
import { rootSelectedNode } from '../../../../store/rtl.selector';
@ -52,9 +55,13 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
public minConfValue = null;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<CLNOpenChannelComponent>, @Inject(MAT_DIALOG_DATA) public data: CLNOpenChannelAlert, private store: Store<RTLState>, private actions: Actions, private decimalPipe: DecimalPipe, private commonService: CommonService) {
constructor(private logger: LoggerService, public dialogRef: MatDialogRef<CLNOpenChannelComponent>,
@Inject(MAT_DIALOG_DATA) public data: CLNOpenChannelAlert, private store: Store<RTLState>,
private actions: Actions, private decimalPipe: DecimalPipe, private commonService: CommonService,
private dataService: DataService) {
this.screenSize = this.commonService.getScreenSize();
}
@ -185,11 +192,15 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
}
onOpenChannel(): boolean | void {
if ((!this.peer && !this.selectedPubkey) || (!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0) || (this.flgMinConf && !this.minConfValue)) || (this.selFeeRate === 'customperkb' && !this.flgMinConf && !this.customFeeRate)) {
if ((!this.peer && !this.selectedPubkey) ||
(!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0) ||
(this.flgMinConf && !this.minConfValue)) ||
(this.selFeeRate === 'customperkb' && !this.flgMinConf && !this.customFeeRate) ||
(this.selFeeRate === 'customperkb' && this.recommendedFee.minimumFee > this.customFeeRate)) {
return true;
}
const newChannel = { peerId: ((!this.peer || !this.peer.id) ? this.selectedPubkey : this.peer.id), amount: (this.flgUseAllBalance) ? 'all' : this.fundingAmount.toString(), announce: !this.isPrivate, minconf: this.flgMinConf ? this.minConfValue : null };
newChannel['feerate'] = (this.selFeeRate === 'customperkb' && !this.flgMinConf && this.customFeeRate) ? (this.customFeeRate * 1000) + 'perkb' : this.selFeeRate;
const newChannel: SaveChannel = { peerId: ((!this.peer || !this.peer.id) ? this.selectedPubkey : this.peer.id), amount: (this.flgUseAllBalance) ? 'all' : this.fundingAmount.toString(), announce: !this.isPrivate, minconf: this.flgMinConf ? this.minConfValue : null };
newChannel.feeRate = (this.selFeeRate === 'customperkb' && !this.flgMinConf && this.customFeeRate) ? (this.customFeeRate * 1000) + 'perkb' : this.selFeeRate;
if (this.selUTXOs.length && this.selUTXOs.length > 0) {
newChannel['utxos'] = [];
this.selUTXOs.forEach((utxo: UTXO) => newChannel['utxos'].push(utxo.txid + ':' + utxo.output));
@ -197,6 +208,19 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
this.store.dispatch(saveNewChannel({ payload: newChannel }));
}
onSelFeeRateChanged(event) {
this.customFeeRate = null;
if (event.value === 'customperkb') {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
}
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(<any>null);

@ -45,7 +45,9 @@
<div fxFlex="100" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field fxLayout="column" fxFlex="49">
<mat-label>Fee (Sats/vByte)</mat-label>
<input #fee="ngModel" matInput type="number" name="fee" tabindex="7" [step]="1" [min]="0" [(ngModel)]="feeRate">
<input #fee="ngModel" matInput type="number" name="fee" tabindex="7" [step]="1" [min]="recommendedFee.minimumFee" [(ngModel)]="feeRate">
<mat-hint>Mempool Min: {{recommendedFee.minimumFee}} (Sats/vByte)</mat-hint>
<mat-error *ngIf="feeRate && feeRate < recommendedFee.minimumFee">Lower than min feerate {{recommendedFee.minimumFee}} in the mempool.</mat-error>
</mat-form-field>
</div>
</div>

@ -3,13 +3,14 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { EffectsModule } from '@ngrx/effects';
import { StoreModule } from '@ngrx/store';
import { mockCLEffects, mockECLEffects, mockLNDEffects, mockMatDialogRef, mockRTLEffects } from '../../../../shared/test-helpers/mock-services';
import { SharedModule } from '../../../../shared/shared.module';
import { DataService } from '../../../../shared/services/data.service';
import { RootReducer } from '../../../../store/rtl.reducers';
import { LNDReducer } from '../../../../lnd/store/lnd.reducers';
import { CLNReducer } from '../../../../cln/store/cln.reducers';
import { ECLReducer } from '../../../../eclair/store/ecl.reducers';
import { mockCLEffects, mockDataService, mockECLEffects, mockLNDEffects, mockMatDialogRef, mockRTLEffects } from '../../../../shared/test-helpers/mock-services';
import { ECLOpenChannelComponent } from './open-channel.component';
describe('ECLOpenChannelComponent', () => {
@ -26,6 +27,7 @@ describe('ECLOpenChannelComponent', () => {
EffectsModule.forRoot([mockRTLEffects, mockLNDEffects, mockCLEffects, mockECLEffects])
],
providers: [
{ provide: DataService, useClass: mockDataService },
{ provide: MatDialogRef, useClass: mockMatDialogRef },
{ provide: MAT_DIALOG_DATA, useValue: { message: {} } }
]

@ -15,6 +15,9 @@ import { RTLState } from '../../../../store/rtl.state';
import { rootSelectedNode } from '../../../../store/rtl.selector';
import { saveNewChannel } from '../../../store/ecl.actions';
import { Node } from '../../../../shared/models/RTLconfig';
import { RecommendedFeeRates } from '../../../../shared/models/rtlModels';
import { LoggerService } from '../../../../shared/services/logger.service';
import { DataService } from '../../../../shared/services/data.service';
@Component({
selector: 'rtl-ecl-open-channel',
@ -40,9 +43,12 @@ export class ECLOpenChannelComponent implements OnInit, OnDestroy {
public selectedPubkey = '';
public isPrivate = false;
public feeRate: number | null = null;
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<ECLOpenChannelComponent>, @Inject(MAT_DIALOG_DATA) public data: ECLOpenChannelAlert, private store: Store<RTLState>, private actions: Actions) { }
constructor(private logger: LoggerService, public dialogRef: MatDialogRef<ECLOpenChannelComponent>,
@Inject(MAT_DIALOG_DATA) public data: ECLOpenChannelAlert, private store: Store<RTLState>,
private actions: Actions, private dataService: DataService) { }
ngOnInit() {
if (this.data.message) {
@ -131,11 +137,23 @@ export class ECLOpenChannelComponent implements OnInit, OnDestroy {
if (this.feeRate && this.feeRate > 0) {
this.advancedTitle = this.advancedTitle + ' | Fee (Sats/vByte): ' + this.feeRate;
}
} else {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
}
}
onOpenChannel(): boolean | void {
if ((!this.peer && !this.selectedPubkey) || (!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0))) {
if (
(!this.peer && !this.selectedPubkey) ||
(!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0)) ||
(this.feeRate && this.recommendedFee.minimumFee > this.feeRate)
) {
return true;
}
const saveChannelPayload: SaveChannel = { nodeId: ((!this.peer || !this.peer.nodeId) ? this.selectedPubkey : this.peer.nodeId), amount: this.fundingAmount, private: this.isPrivate };
@ -143,6 +161,7 @@ export class ECLOpenChannelComponent implements OnInit, OnDestroy {
this.store.dispatch(saveNewChannel({ payload: saveChannelPayload }));
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(<any>null);

@ -38,7 +38,7 @@ export class BumpFeeComponent implements OnInit, OnDestroy {
public bumpFeeError = '';
public flgShowDustWarning = false;
public dustOutputValue = 0;
public recommendedFee = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<BumpFeeComponent>, @Inject(MAT_DIALOG_DATA) public data: PendingOpenChannelInformation, private logger: LoggerService, private dataService: DataService, private snackBar: MatSnackBar) { }

@ -27,8 +27,8 @@
<input #amt="ngModel" matInput type="number" tabindex="1" required name="amnt" [step]="1000" [min]="1" [max]="totalBalance" [(ngModel)]="fundingAmount">
<mat-hint>(Remaining: {{totalBalance - ((fundingAmount) ? fundingAmount : 0) | number}})</mat-hint>
<span matSuffix> Sats </span>
<mat-error *ngIf="amount.errors?.required">Amount is required.</mat-error>
<mat-error *ngIf="amount.errors?.max">Amount must be less than or equal to {{totalBalance}}.</mat-error>
<mat-error *ngIf="amt.errors?.required">Amount is required.</mat-error>
<mat-error *ngIf="amt.errors?.max">Amount must be less than or equal to {{totalBalance}}.</mat-error>
</mat-form-field>
<div fxFlex="35" fxLayoutAlign="start center">
<mat-slide-toggle tabindex="2" color="primary" name="isPrivate" [(ngModel)]="isPrivate">Private Channel</mat-slide-toggle>
@ -43,7 +43,7 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxLayout="column" fxFlex="49">
<mat-select tabindex="3" [(value)]="selTransType">
<mat-select tabindex="3" [(value)]="selTransType" (selectionChange)="onSelTransTypeChanged($event)">
<mat-option *ngFor="let transType of transTypes" [value]="transType.id">
{{transType.name}}
</mat-option>
@ -51,11 +51,14 @@
</mat-form-field>
<mat-form-field fxLayout="column" fxFlex="49">
<mat-label>{{selTransType==='0' ? 'Default' : selTransType==='1' ? 'Target Confirmation Blocks' : 'Fee (Sats/vByte)'}}</mat-label>
<input #transTypeVal="ngModel" matInput type="number" tabindex="4" name="transTpValue" [required]="selTransType!=='0'" [disabled]="selTransType==='0'" [step]="1" [min]="0" [(ngModel)]="transTypeValue">
<mat-error *ngIf="selTransType!=='0' && !transTypeValue">{{selTransType === '1' ? 'Target Confirmation Blocks' : 'Fee'}} is required.</mat-error>
<input #transTypeVal="ngModel" matInput type="number" tabindex="4" name="transTpValue" [required]="selTransType!=='0'" [disabled]="selTransType==='0'" [step]="1" [min]="selTransType === '2' ? recommendedFee.minimumFee : 0" [(ngModel)]="transTypeValue">
<mat-hint *ngIf="selTransType === '2'">Mempool Min: {{recommendedFee.minimumFee}} (Sats/vByte)</mat-hint>
<mat-error *ngIf="selTransType === '1' && !transTypeValue">Target Confirmation Blocks is required.</mat-error>
<mat-error *ngIf="selTransType === '2' && !transTypeValue">Fee is required.</mat-error>
<mat-error *ngIf="selTransType === '2' && transTypeValue && +transTypeValue < recommendedFee.minimumFee">Lower than min feerate {{recommendedFee.minimumFee}} in the mempool.</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center" class="mt-2">
<div *ngIf="isTaprootAvailable" fxFlex="50" fxLayoutAlign="start center">
<mat-slide-toggle tabindex="6" color="primary" name="taprootChannel" [(ngModel)]="taprootChannel">Taproot Channel</mat-slide-toggle>
</div>

@ -11,11 +11,14 @@ import { Peer, GetInfo } from '../../../../shared/models/lndModels';
import { OpenChannelAlert } from '../../../../shared/models/alertData';
import { APICallStatusEnum, LNDActions, TRANS_TYPES } from '../../../../shared/services/consts-enums-functions';
import { RecommendedFeeRates } from '../../../../shared/models/rtlModels';
import { RTLState } from '../../../../store/rtl.state';
import { rootSelectedNode } from '../../../../store/rtl.selector';
import { saveNewChannel } from '../../../store/lnd.actions';
import { Node } from '../../../../shared/models/RTLconfig';
import { CommonService } from '../../../../shared/services/common.service';
import { DataService } from '../../../../shared/services/data.service';
import { LoggerService } from '../../../../shared/services/logger.service';
@Component({
selector: 'rtl-open-channel',
@ -47,9 +50,12 @@ export class OpenChannelComponent implements OnInit, OnDestroy {
public spendUnconfirmed = false;
public transTypeValue = '';
public transTypes = TRANS_TYPES;
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<OpenChannelComponent>, @Inject(MAT_DIALOG_DATA) public data: OpenChannelAlert, private store: Store<RTLState>, private actions: Actions, private commonService: CommonService) { }
constructor(private logger: LoggerService, public dialogRef: MatDialogRef<OpenChannelComponent>,
@Inject(MAT_DIALOG_DATA) public data: OpenChannelAlert, private store: Store<RTLState>,
private actions: Actions, private commonService: CommonService, private dataService: DataService) { }
ngOnInit() {
if (this.data.message) {
@ -138,7 +144,12 @@ export class OpenChannelComponent implements OnInit, OnDestroy {
}
onOpenChannel(): boolean | void {
if ((!this.peer && !this.selectedPubkey) || (!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0) || ((this.selTransType === '1' || this.selTransType === '2') && !this.transTypeValue))) {
if (
(!this.peer && !this.selectedPubkey) ||
(!this.fundingAmount ||
((this.totalBalance - this.fundingAmount) < 0) || ((this.selTransType === '1' || this.selTransType === '2') && !this.transTypeValue)) ||
(this.selTransType === '2' && this.recommendedFee.minimumFee > +this.transTypeValue)
) {
return true;
}
// Taproot channel's commitment type is 5
@ -160,6 +171,19 @@ export class OpenChannelComponent implements OnInit, OnDestroy {
}
}
onSelTransTypeChanged(event) {
this.transTypeValue = '';
if (event.value === this.transTypes[2].id) {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
}
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(<any>null);

Loading…
Cancel
Save