Add Fee Rate Information on Send Funds Modal

Closes #1410
This commit is contained in:
ShahanaFarooqui 2024-11-06 20:31:50 -08:00
parent e289c65d5a
commit 47baebb4f3
19 changed files with 117 additions and 50 deletions

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

File diff suppressed because one or more lines are too long

View File

@ -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,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,f]=e[n],s=!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):(s=!1,f<a&&(a=f));if(s){e.splice(n--,1);var d=i();void 0!==d&&(o=d)}}return o}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,i,f]},r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((o,t)=>(r.f[t](e,o),o),[])),r.u=e=>e+"."+{17:"6fa7154eb6e447e2",190:"8009411a512731e5",193:"0e1a81316bbc29da",853:"50b06a24091d386f"}[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,s;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||(s=!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 c=(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(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:o=>o},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={121:0};r.f.j=(i,f)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)f.push(n[2]);else if(121!=i){var a=new Promise((u,c)=>n=e[i]=[u,c]);f.push(n[2]=a);var s=r.p+r.u(i),l=new Error;r.l(s,u=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",l.name="ChunkLoadError",l.type=c,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,s]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(s)var c=s(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(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();

View File

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

View File

@ -6,7 +6,20 @@
</div>
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
</mat-card-header>
<mat-card-content class="padding-gap-x-large">
<mat-card-content class="padding-gap-x-large" fxLayout="column">
<div *ngIf="recommendedFee.minimumFee" fxFlex="100" class="alert alert-info mb-2">
<fa-icon class="mr-1 alert-icon" [icon]="faInfoCircle" />
<span fxLayout="column" fxFlex="100">
<div>Fee rates recommended by mempool (sat/vByte):</div>
<span class="pr-2" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
<span>- High: {{recommendedFee.fastestFee || 'Unknown'}}</span>
<span>- Medium: {{recommendedFee.halfHourFee || 'Unknown'}}</span>
<span>- Low: {{recommendedFee.hourFee || 'Unknown'}}</span>
<span>- Economy: {{recommendedFee.economyFee || 'Unknown'}}</span>
<span>- Minimum: {{recommendedFee.minimumFee || 'Unknown'}}</span>
</span>
</span>
</div>
<form *ngIf="!sweepAll; else sweepAllBlock;" #form="ngForm" fxLayout="row wrap"fxLayoutAlign="space-between start" fxFlex="100" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()">
<mat-form-field fxLayout="column" fxFlex="55">
<mat-label>Bitcoin Address</mat-label>

View File

@ -8,7 +8,7 @@ import { Actions } from '@ngrx/effects';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MatStepper } from '@angular/material/stepper';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import * as sha256 from 'sha256';
import { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
@ -25,6 +25,8 @@ import { setChannelTransaction } from '../../store/cln.actions';
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
import { clnNodeInformation, utxoBalances } from '../../store/cln.selector';
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
import { DataService } from 'src/app/shared/services/data.service';
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
@Component({
selector: 'rtl-cln-on-chain-send-modal',
@ -37,6 +39,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
@ViewChild('stepper', { static: false }) stepper: MatStepper;
public faExclamationTriangle = faExclamationTriangle;
public faInfoCircle = faInfoCircle;
public sweepAll = false;
public selNode: Node | null;
public appConfig: RTLConfiguration;
@ -65,6 +68,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
public advancedTitle = 'Advanced Options';
public flgValidated = false;
public flgEditable = true;
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
public passwordFormLabel = 'Authenticate with your RTL password';
public sendFundFormLabel = 'Sweep funds';
public confirmFormLabel = 'Confirm sweep';
@ -74,12 +78,13 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
confirmFormGroup: UntypedFormGroup;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(
public dialogRef: MatDialogRef<CLNOnChainSendModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: CLNOnChainSendFunds,
private logger: LoggerService,
private dataService: DataService,
private store: Store<RTLState>,
private commonService: CommonService,
private decimalPipe: DecimalPipe,
@ -91,6 +96,13 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
}
ngOnInit() {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
this.sweepAll = this.data.sweepAll;
this.passwordFormGroup = this.formBuilder.group({
hiddenPassword: ['', [Validators.required]],
@ -104,7 +116,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
minConfValue: [{ value: null, disabled: true }]
});
this.confirmFormGroup = this.formBuilder.group({});
this.sendFundFormGroup.controls.flgMinConf.valueChanges.pipe(takeUntil(this.unSubs[0])).subscribe((flg) => {
this.sendFundFormGroup.controls.flgMinConf.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((flg) => {
if (flg) {
this.sendFundFormGroup.controls.selFeeRate.disable();
this.sendFundFormGroup.controls.selFeeRate.setValue(null);
@ -121,7 +133,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
this.sendFundFormGroup.controls.minConfValue.setErrors(null);
}
});
this.sendFundFormGroup.controls.selFeeRate.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((feeRate) => {
this.sendFundFormGroup.controls.selFeeRate.valueChanges.pipe(takeUntil(this.unSubs[2])).subscribe((feeRate) => {
this.sendFundFormGroup.controls.customFeeRate.setValue(null);
this.sendFundFormGroup.controls.customFeeRate.reset();
if (feeRate === 'customperkb' && !this.sendFundFormGroup.controls.flgMinConf.value) {
@ -130,23 +142,23 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
this.sendFundFormGroup.controls.customFeeRate.setValidators(null);
}
});
combineLatest([this.store.select(rootSelectedNode), this.store.select(rootAppConfig)]).pipe(takeUntil(this.unSubs[1])).
combineLatest([this.store.select(rootSelectedNode), this.store.select(rootAppConfig)]).pipe(takeUntil(this.unSubs[3])).
subscribe(([selNode, appConfig]) => {
this.fiatConversion = selNode.settings.fiatConversion;
this.amountUnits = selNode.settings.currencyUnits;
this.appConfig = appConfig;
});
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[2])).
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[4])).
subscribe((nodeInfo: GetInfo) => {
this.information = nodeInfo;
});
this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[3])).
this.store.select(utxoBalances).pipe(takeUntil(this.unSubs[5])).
subscribe((utxoBalancesSeletor: { utxos: UTXO[], balance: Balance, localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }) => {
this.utxos = this.commonService.sortAscByKey(utxoBalancesSeletor.utxos?.filter((utxo) => utxo.status === 'confirmed'), 'value');
this.logger.info(utxoBalancesSeletor);
});
this.actions.pipe(
takeUntil(this.unSubs[4]),
takeUntil(this.unSubs[6]),
filter((action) => action.type === CLNActions.UPDATE_API_CALL_STATUS_CLN || action.type === CLNActions.SET_CHANNEL_TRANSACTION_RES_CLN)).
subscribe((action: any) => {
if (action.type === CLNActions.SET_CHANNEL_TRANSACTION_RES_CLN) {
@ -219,7 +231,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
}
if (this.transaction.satoshi && this.transaction.satoshi !== 'all' && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
this.commonService.convertCurrency(+this.transaction.satoshi, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[5])).
pipe(takeUntil(this.unSubs[7])).
subscribe({
next: (data) => {
this.transaction.satoshi = data[CurrencyUnitEnum.SATS];
@ -307,7 +319,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
let currSelectedUnit = event.value === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : event.value;
if (this.transaction.satoshi && this.selAmountUnit !== event.value) {
this.commonService.convertCurrency(+this.transaction.satoshi, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[6])).
pipe(takeUntil(this.unSubs[8])).
subscribe({
next: (data) => {
this.selAmountUnit = event.value;

View File

@ -58,7 +58,7 @@
<div fxFlex="64" fxLayout="row" fxLayoutAlign="space-between center">
<mat-form-field fxLayout="column" fxLayoutAlign="start center" [fxFlex]="selFeeRate === 'customperkb' && !flgMinConf ? '40' : '100'">
<mat-label>Fee Rate</mat-label>
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate" (selectionChange)="onSelFeeRateChanged($event)">
<mat-select tabindex="4" [disabled]="flgMinConf" [(value)]="selFeeRate">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>

View File

@ -168,6 +168,13 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
}
} else {
this.advancedTitle = 'Advanced Options';
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
}
}
@ -209,19 +216,6 @@ export class CLNOpenChannelComponent implements OnInit, OnDestroy {
this.store.dispatch(saveNewChannel({ payload: newChannel }));
}
onSelFeeRateChanged(event) {
this.customFeeRate = null;
if (event.value === 'customperkb') {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[3])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
}
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(<any>null);

View File

@ -608,10 +608,9 @@ export class CLNEffects implements OnDestroy {
ofType(CLNActions.DELETE_EXPIRED_INVOICE_CLN),
mergeMap((action: { type: string, payload: number }) => {
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.DELETE_INVOICE }));
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.INVOICES_API + '/delete', { 'subsystem': 'expiredinvoices', 'age': SECS_IN_YEAR }).
return this.httpClient.post(this.CHILD_API_URL + API_END_POINTS.INVOICES_API + '/delete', { subsystem: 'expiredinvoices', age: SECS_IN_YEAR }).
pipe(
map((postRes: any) => {
console.warn(postRes);
this.logger.info(postRes);
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.DELETE_INVOICE }));
this.store.dispatch(openSnackBar({ payload: postRes.status }));

View File

@ -6,7 +6,20 @@
</div>
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
</mat-card-header>
<mat-card-content class="padding-gap-x-large">
<mat-card-content class="padding-gap-x-large" fxLayout="column">
<div *ngIf="recommendedFee.minimumFee" fxFlex="100" class="alert alert-info mb-2">
<fa-icon class="mr-1 alert-icon" [icon]="faInfoCircle" />
<span fxLayout="column" fxFlex="100">
<div>Fee rates recommended by mempool (sat/vByte):</div>
<span class="pr-2" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
<span>- High: {{recommendedFee.fastestFee || 'Unknown'}}</span>
<span>- Medium: {{recommendedFee.halfHourFee || 'Unknown'}}</span>
<span>- Low: {{recommendedFee.hourFee || 'Unknown'}}</span>
<span>- Economy: {{recommendedFee.economyFee || 'Unknown'}}</span>
<span>- Minimum: {{recommendedFee.minimumFee || 'Unknown'}}</span>
</span>
</span>
</div>
<form #form="ngForm" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()">
<mat-form-field fxLayout="column" fxFlex="55">
<mat-label>Bitcoin Address</mat-label>

View File

@ -5,7 +5,7 @@ import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatDialogRef } from '@angular/material/dialog';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { Node } from '../../../shared/models/RTLconfig';
import { GetInfo, OnChainBalance, SendPaymentOnChain } from '../../../shared/models/eclModels';
@ -17,6 +17,8 @@ import { RTLState } from '../../../store/rtl.state';
import { openSnackBar } from '../../../store/rtl.actions';
import { sendOnchainFunds } from '../../store/ecl.actions';
import { rootSelectedNode } from '../../../store/rtl.selector';
import { DataService } from 'src/app/shared/services/data.service';
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
@Component({
selector: 'rtl-ecl-on-chain-send-modal',
@ -27,6 +29,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: true }) form: any;
public faExclamationTriangle = faExclamationTriangle;
public faInfoCircle = faInfoCircle;
public selNode: Node | null;
public addressTypes = [];
public selectedAddress = ADDRESS_TYPES[1];
@ -41,19 +44,27 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
public currConvertorRate = {};
public unitConversionValue = 0;
public currencyUnitFormats = CURRENCY_UNIT_FORMATS;
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
public amountError = 'Amount is Required.';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<ECLOnChainSendModalComponent>, private logger: LoggerService, private store: Store<RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions: Actions) { }
constructor(public dialogRef: MatDialogRef<ECLOnChainSendModalComponent>, private logger: LoggerService, private dataService: DataService, private store: Store<RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions: Actions) { }
ngOnInit() {
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[0])).subscribe((selNode) => {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[1])).subscribe((selNode) => {
this.fiatConversion = selNode.settings.fiatConversion;
this.amountUnits = selNode.settings.currencyUnits;
this.logger.info(selNode);
});
this.actions.pipe(
takeUntil(this.unSubs[1]),
takeUntil(this.unSubs[2]),
filter((action) => action.type === ECLActions.UPDATE_API_CALL_STATUS_ECL || action.type === ECLActions.SEND_ONCHAIN_FUNDS_RES_ECL)
).
subscribe((action: any) => {
@ -74,7 +85,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
this.sendFundError = '';
if (this.transaction.amount && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
this.commonService.convertCurrency(this.transaction.amount, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[2])).
pipe(takeUntil(this.unSubs[3])).
subscribe({
next: (data) => {
this.transaction.amount = parseInt(data[CurrencyUnitEnum.SATS]);
@ -107,7 +118,7 @@ export class ECLOnChainSendModalComponent implements OnInit, OnDestroy {
let currSelectedUnit = event.value === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : event.value;
if (this.transaction.amount && this.selAmountUnit !== event.value) {
this.commonService.convertCurrency(this.transaction.amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[3])).
pipe(takeUntil(this.unSubs[4])).
subscribe({
next: (data) => {
this.selAmountUnit = event.value;

View File

@ -6,7 +6,20 @@
</div>
<button tabindex="8" fxFlex="5" fxLayoutAlign="center center" class="btn-close-x p-0" default mat-button [mat-dialog-close]="false">X</button>
</mat-card-header>
<mat-card-content class="padding-gap-x-large">
<mat-card-content class="padding-gap-x-large" fxLayout="column">
<div *ngIf="recommendedFee.minimumFee" fxFlex="100" class="alert alert-info mb-2">
<fa-icon class="mr-1 alert-icon" [icon]="faInfoCircle" />
<span fxLayout="column" fxFlex="100">
<div>Fee rates recommended by mempool (sat/vByte):</div>
<span class="pr-2" fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
<span>- High: {{recommendedFee.fastestFee || 'Unknown'}}</span>
<span>- Medium: {{recommendedFee.halfHourFee || 'Unknown'}}</span>
<span>- Low: {{recommendedFee.hourFee || 'Unknown'}}</span>
<span>- Economy: {{recommendedFee.economyFee || 'Unknown'}}</span>
<span>- Minimum: {{recommendedFee.minimumFee || 'Unknown'}}</span>
</span>
</span>
</div>
<form *ngIf="!sweepAll; else sweepAllBlock;" #form="ngForm" fxLayout="row wrap"fxLayoutAlign="space-between start" fxFlex="100" class="overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()">
<mat-form-field fxLayout="column" fxFlex.gt-sm="55">
<mat-label>Bitcoin Address</mat-label>

View File

@ -8,7 +8,7 @@ import { Actions } from '@ngrx/effects';
import { MatSnackBar } from '@angular/material/snack-bar';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MatStepper } from '@angular/material/stepper';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { faExclamationTriangle, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { OnChainSendFunds } from '../../../shared/models/alertData';
import { Node, RTLConfiguration } from '../../../shared/models/RTLconfig';
@ -23,6 +23,8 @@ import { RTLState } from '../../../store/rtl.state';
import { isAuthorized, openSnackBar } from '../../../store/rtl.actions';
import { setChannelTransaction } from '../../store/lnd.actions';
import { rootAppConfig, rootSelectedNode } from '../../../store/rtl.selector';
import { DataService } from 'src/app/shared/services/data.service';
import { RecommendedFeeRates } from 'src/app/shared/models/rtlModels';
@Component({
selector: 'rtl-on-chain-send-modal',
@ -35,6 +37,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
@ViewChild('stepper', { static: false }) stepper: MatStepper;
public faExclamationTriangle = faExclamationTriangle;
public faInfoCircle = faInfoCircle;
public sweepAll = false;
public selNode: Node | null;
public appConfig: RTLConfiguration;
@ -58,6 +61,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
public sendFundError = '';
public flgValidated = false;
public flgEditable = true;
public recommendedFee: RecommendedFeeRates = { fastestFee: 0, halfHourFee: 0, hourFee: 0 };
public passwordFormLabel = 'Authenticate with your RTL password';
public sendFundFormLabel = 'Sweep funds';
public confirmFormLabel = 'Confirm sweep';
@ -65,12 +69,13 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
passwordFormGroup: UntypedFormGroup;
sendFundFormGroup: UntypedFormGroup;
confirmFormGroup: UntypedFormGroup;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(
public dialogRef: MatDialogRef<OnChainSendModalComponent>,
@Inject(MAT_DIALOG_DATA) public data: OnChainSendFunds,
private logger: LoggerService,
private dataService: DataService,
private store: Store<RTLState>,
private rtlEffects: RTLEffects,
private commonService: CommonService,
@ -80,6 +85,13 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
private formBuilder: UntypedFormBuilder) { }
ngOnInit() {
this.dataService.getRecommendedFeeRates().pipe(takeUntil(this.unSubs[0])).subscribe({
next: (rfRes: RecommendedFeeRates) => {
this.recommendedFee = rfRes;
}, error: (err) => {
this.logger.error(err);
}
});
this.sweepAll = this.data.sweepAll;
this.passwordFormGroup = this.formBuilder.group({
hiddenPassword: ['', [Validators.required]],
@ -92,7 +104,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
selTransType: ['1', Validators.required]
});
this.confirmFormGroup = this.formBuilder.group({});
this.sendFundFormGroup.controls.selTransType.valueChanges.pipe(takeUntil(this.unSubs[0])).subscribe((transType) => {
this.sendFundFormGroup.controls.selTransType.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((transType) => {
if (transType === '1') {
this.sendFundFormGroup.controls.transactionBlocks.setValidators([Validators.required]);
this.sendFundFormGroup.controls.transactionBlocks.setValue(null);
@ -105,16 +117,16 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
this.sendFundFormGroup.controls.transactionFees.setValue(null);
}
});
this.store.select(rootAppConfig).pipe(takeUntil(this.unSubs[1])).subscribe((appConfig) => {
this.store.select(rootAppConfig).pipe(takeUntil(this.unSubs[2])).subscribe((appConfig) => {
this.appConfig = appConfig;
});
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[2])).subscribe((selNode) => {
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[3])).subscribe((selNode) => {
this.fiatConversion = selNode.settings.fiatConversion;
this.amountUnits = selNode.settings.currencyUnits;
this.logger.info(selNode);
});
this.actions.pipe(
takeUntil(this.unSubs[3]),
takeUntil(this.unSubs[4]),
filter((action) => action.type === LNDActions.UPDATE_API_CALL_STATUS_LND || action.type === LNDActions.SET_CHANNEL_TRANSACTION_RES_LND)).
subscribe((action: any) => {
if (action.type === LNDActions.SET_CHANNEL_TRANSACTION_RES_LND) {
@ -173,7 +185,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
}
if (this.transactionAmount && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
this.commonService.convertCurrency(this.transactionAmount, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, CurrencyUnitEnum.SATS, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[4])).
pipe(takeUntil(this.unSubs[5])).
subscribe({
next: (data) => {
this.selAmountUnit = CurrencyUnitEnum.SATS;
@ -254,7 +266,7 @@ export class OnChainSendModalComponent implements OnInit, OnDestroy {
if (this.transactionAmount && this.selAmountUnit !== event.value) {
const amount = this.transactionAmount ? this.transactionAmount : 0;
this.commonService.convertCurrency(amount, prevSelectedUnit, currSelectedUnit, this.amountUnits[2], this.fiatConversion).
pipe(takeUntil(this.unSubs[5])).
pipe(takeUntil(this.unSubs[6])).
subscribe({
next: (data) => {
this.selAmountUnit = event.value;