parent
24b006519f
commit
0b383e95a1
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 @@
|
||||
(()=>{"use strict";var e,r,t,a={},o={};function n(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return a[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=a,e=[],n.O=(r,t,a,o)=>{if(!t){var l=1/0;for(s=0;s<e.length;s++){for(var[t,a,o]=e[s],d=!0,i=0;i<t.length;i++)(!1&o||l>=o)&&Object.keys(n.O).every(e=>n.O[e](t[i]))?t.splice(i--,1):(d=!1,o<l&&(l=o));d&&(e.splice(s--,1),r=a())}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,a,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((r,t)=>(n.f[t](e,r),r),[])),n.u=e=>e+"."+{432:"a6d92c45cf6d18d2ceaa",891:"f50ed5ee955e9ad8b4cf",918:"f9485c36a5f074dfe7ba",958:"a55d8156c48e68ddc825"}[e]+".js",n.miniCssF=e=>"styles.50804d64c130486c55c1.css",n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="rtl:",n.l=(e,a,o,l)=>{if(r[e])r[e].push(a);else{var d,i;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var c=s[u];if(c.getAttribute("src")==e||c.getAttribute("data-webpack")==t+o){d=c;break}}d||(i=!0,(d=document.createElement("script")).charset="utf-8",d.timeout=120,n.nc&&d.setAttribute("nonce",n.nc),d.setAttribute("data-webpack",t+o),d.src=e),r[e]=[a];var f=(t,a)=>{d.onerror=d.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],d.parentNode&&d.parentNode.removeChild(d),o&&o.forEach(e=>e(a)),t)return t(a)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:d}),12e4);d.onerror=f.bind(null,d.onerror),d.onload=f.bind(null,d.onload),i&&document.head.appendChild(d)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.p="",(()=>{var e={666:0};n.f.j=(r,t)=>{var a=n.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else if(666!=r){var o=new Promise((t,o)=>a=e[r]=[t,o]);t.push(a[2]=o);var l=n.p+n.u(r),d=new Error;n.l(l,t=>{if(n.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var o=t&&("load"===t.type?"missing":t.type),l=t&&t.target&&t.target.src;d.message="Loading chunk "+r+" failed.\n("+o+": "+l+")",d.name="ChunkLoadError",d.type=o,d.request=l,a[1](d)}},"chunk-"+r,r)}else e[r]=0},n.O.j=r=>0===e[r];var r=(r,t)=>{var a,o,[l,d,i]=t,s=0;for(a in d)n.o(d,a)&&(n.m[a]=d[a]);if(i)var u=i(n);for(r&&r(t);s<l.length;s++)n.o(e,o=l[s])&&e[o]&&e[o][0](),e[l[s]]=0;return n.O(u)},t=self.webpackChunkrtl=self.webpackChunkrtl||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})()})();
|
||||
(()=>{"use strict";var e,r,t,a={},o={};function n(e){var r=o[e];if(void 0!==r)return r.exports;var t=o[e]={id:e,loaded:!1,exports:{}};return a[e].call(t.exports,t,t.exports,n),t.loaded=!0,t.exports}n.m=a,e=[],n.O=(r,t,a,o)=>{if(!t){var l=1/0;for(s=0;s<e.length;s++){for(var[t,a,o]=e[s],i=!0,d=0;d<t.length;d++)(!1&o||l>=o)&&Object.keys(n.O).every(e=>n.O[e](t[d]))?t.splice(d--,1):(i=!1,o<l&&(l=o));i&&(e.splice(s--,1),r=a())}return r}o=o||0;for(var s=e.length;s>0&&e[s-1][2]>o;s--)e[s]=e[s-1];e[s]=[t,a,o]},n.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return n.d(r,{a:r}),r},n.d=(e,r)=>{for(var t in r)n.o(r,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},n.f={},n.e=e=>Promise.all(Object.keys(n.f).reduce((r,t)=>(n.f[t](e,r),r),[])),n.u=e=>e+"."+{145:"ece30118af5d019172a0",432:"cc664c98c4074918e24a",891:"0b4a7fadb75c2e9cf113",958:"88b5fa8d3b93e7a0e7b3"}[e]+".js",n.miniCssF=e=>"styles.50804d64c130486c55c1.css",n.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),r={},t="rtl:",n.l=(e,a,o,l)=>{if(r[e])r[e].push(a);else{var i,d;if(void 0!==o)for(var s=document.getElementsByTagName("script"),u=0;u<s.length;u++){var c=s[u];if(c.getAttribute("src")==e||c.getAttribute("data-webpack")==t+o){i=c;break}}i||(d=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,n.nc&&i.setAttribute("nonce",n.nc),i.setAttribute("data-webpack",t+o),i.src=e),r[e]=[a];var f=(t,a)=>{i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(e=>e(a)),t)return t(a)},p=setTimeout(f.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=f.bind(null,i.onerror),i.onload=f.bind(null,i.onload),d&&document.head.appendChild(i)}},n.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),n.p="",(()=>{var e={666:0};n.f.j=(r,t)=>{var a=n.o(e,r)?e[r]:void 0;if(0!==a)if(a)t.push(a[2]);else if(666!=r){var o=new Promise((t,o)=>a=e[r]=[t,o]);t.push(a[2]=o);var l=n.p+n.u(r),i=new Error;n.l(l,t=>{if(n.o(e,r)&&(0!==(a=e[r])&&(e[r]=void 0),a)){var o=t&&("load"===t.type?"missing":t.type),l=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+o+": "+l+")",i.name="ChunkLoadError",i.type=o,i.request=l,a[1](i)}},"chunk-"+r,r)}else e[r]=0},n.O.j=r=>0===e[r];var r=(r,t)=>{var a,o,[l,i,d]=t,s=0;for(a in i)n.o(i,a)&&(n.m[a]=i[a]);if(d)var u=d(n);for(r&&r(t);s<l.length;s++)n.o(e,o=l[s])&&e[o]&&e[o][0](),e[l[s]]=0;return n.O(u)},t=self.webpackChunkrtl=self.webpackChunkrtl||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})()})();
|
@ -0,0 +1,53 @@
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100">
|
||||
<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="padding-gap-x-large">
|
||||
<form fxLayout="column">
|
||||
<div fxLayout="column" class="bordered-box mb-1 p-2">
|
||||
<p fxLayoutAlign="start center" class="pb-1 word-break">Bump fee for channel point: {{bumpFeeChannel?.funding_txid}}
|
||||
<fa-icon class="ml-1" [icon]="faCopy" matSuffix rtlClipboard [payload]="bumpFeeChannel?.funding_txid" (copied)="onCopyID($event)" matTooltip="Copy transaction ID"></fa-icon>
|
||||
</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="49">
|
||||
<input autoFocus matInput [(ngModel)]="outputIndex" placeholder="Output Index" type="number" [step]="1" [min]="0" tabindex="1" required name="outputIdx" #outputIdx="ngModel">
|
||||
<mat-error *ngIf="outputIdx.errors?.required">Output Index required.</mat-error>
|
||||
<mat-error *ngIf="outputIdx.errors?.pendingChannelOutputIndex">Invalid index value.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="49">
|
||||
<input matInput [(ngModel)]="fees" placeholder="Fees (Sats/vByte)"
|
||||
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,51 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
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 { DataService } from '../../../../shared/services/data.service';
|
||||
import { LoggerService } from '../../../../shared/services/logger.service';
|
||||
import { SharedModule } from '../../../../shared/shared.module';
|
||||
import { mockCLEffects, mockDataService, mockECLEffects, mockLNDEffects, mockLoggerService, mockMatDialogRef, mockRTLEffects } from '../../../../shared/test-helpers/mock-services';
|
||||
|
||||
import { RTLReducer } from '../../../../store/rtl.reducers';
|
||||
import { CLBumpFeeComponent } from './bump-fee.component';
|
||||
|
||||
describe('CLBumpFeeComponent', () => {
|
||||
let component: CLBumpFeeComponent;
|
||||
let fixture: ComponentFixture<CLBumpFeeComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [CLBumpFeeComponent],
|
||||
imports: [
|
||||
BrowserAnimationsModule,
|
||||
SharedModule,
|
||||
StoreModule.forRoot(RTLReducer, {
|
||||
runtimeChecks: {
|
||||
strictStateImmutability: false,
|
||||
strictActionImmutability: false
|
||||
}
|
||||
}),
|
||||
EffectsModule.forRoot([mockRTLEffects, mockLNDEffects, mockCLEffects, mockECLEffects])
|
||||
],
|
||||
providers: [
|
||||
{ provide: LoggerService, useClass: mockLoggerService },
|
||||
{ provide: MatDialogRef, useClass: mockMatDialogRef },
|
||||
{ provide: MAT_DIALOG_DATA, useValue: { channel: {} } },
|
||||
{ provide: DataService, useClass: mockDataService }
|
||||
]
|
||||
}).
|
||||
compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLBumpFeeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,101 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild, Inject } from '@angular/core';
|
||||
import { NgModel } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, take } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
import { faCopy, faInfoCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
import { Channel } from '../../../../shared/models/clModels';
|
||||
import { CLChannelInformation } from '../../../../shared/models/alertData';
|
||||
import { ADDRESS_TYPES, APICallStatusEnum } from '../../../../shared/services/consts-enums-functions';
|
||||
import { LoggerService } from '../../../../shared/services/logger.service';
|
||||
|
||||
import * as CLActions from '../../../store/cl.actions';
|
||||
import * as RTLActions from '../../../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-bump-fee',
|
||||
templateUrl: './bump-fee.component.html',
|
||||
styleUrls: ['./bump-fee.component.scss']
|
||||
})
|
||||
export class CLBumpFeeComponent implements OnInit, OnDestroy {
|
||||
|
||||
private outputIdx: NgModel;
|
||||
@ViewChild('outputIdx') set payReq(outIdx: NgModel) {
|
||||
if (outIdx) {
|
||||
this.outputIdx = outIdx;
|
||||
}
|
||||
}
|
||||
|
||||
public newAddress = '';
|
||||
public bumpFeeChannel: Channel;
|
||||
public fees = null;
|
||||
public outputIndex = null;
|
||||
public faCopy = faCopy;
|
||||
public faInfoCircle = faInfoCircle;
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public bumpFeeError = '';
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private actions: Actions, public dialogRef: MatDialogRef<CLBumpFeeComponent>, @Inject(MAT_DIALOG_DATA) public data: CLChannelInformation, private store: Store<fromRTLReducer.RTLState>, private logger: LoggerService, private snackBar: MatSnackBar) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.bumpFeeChannel = this.data.channel;
|
||||
}
|
||||
|
||||
onBumpFee(): boolean | void {
|
||||
if ((!this.outputIndex && this.outputIndex !== 0) || !this.fees) {
|
||||
return true;
|
||||
}
|
||||
this.bumpFeeError = '';
|
||||
this.store.dispatch(new CLActions.GetNewAddress(ADDRESS_TYPES[0]));
|
||||
this.actions.pipe(
|
||||
filter((action) => action.type === CLActions.SET_NEW_ADDRESS_CL || action.type === CLActions.SET_CHANNEL_TRANSACTION_RES_CL || action.type === CLActions.UPDATE_API_CALL_STATUS_CL),
|
||||
take(3)).
|
||||
subscribe((action: (CLActions.SetNewAddress | CLActions.SetChannelTransactionRes | CLActions.UpdateAPICallStatus)) => {
|
||||
if (action.type === CLActions.SET_NEW_ADDRESS_CL) {
|
||||
this.store.dispatch(new CLActions.SetChannelTransaction({
|
||||
address: action.payload,
|
||||
satoshis: 'all',
|
||||
feeRate: this.fees,
|
||||
utxos: [this.bumpFeeChannel.funding_txid + ':' + this.outputIndex.toString()]
|
||||
}));
|
||||
}
|
||||
if (action.type === CLActions.SET_CHANNEL_TRANSACTION_RES_CL) {
|
||||
this.store.dispatch(new RTLActions.OpenSnackBar('Successfully bumped the fee. Use the block explorer to verify transaction.'));
|
||||
this.dialogRef.close();
|
||||
}
|
||||
if (action.type === CLActions.UPDATE_API_CALL_STATUS_CL && action.payload.status === APICallStatusEnum.ERROR && (action.payload.action === 'SetChannelTransaction' || action.payload.action === 'GenerateNewAddress')) {
|
||||
this.logger.error(action.payload.message);
|
||||
this.bumpFeeError = action.payload.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCopyID(payload: string) {
|
||||
this.snackBar.open('Transaction ID copied.');
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.bumpFeeError = '';
|
||||
this.fees = null;
|
||||
this.outputIndex = null;
|
||||
this.outputIdx.control.setErrors(null);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach((completeSub) => {
|
||||
completeSub.next(null);
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +1,12 @@
|
||||
@import "../../../../../shared/theme/styles/mixins.scss";
|
||||
|
||||
.mat-column-actions {
|
||||
min-height: 4.8rem;
|
||||
}
|
||||
|
||||
.mat-column-state {
|
||||
flex: 1 1 15%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
Loading…
Reference in New Issue