Send fund coin selection #406 (#422)

Send fund coin selection #406
pull/423/head
ShahanaFarooqui 4 years ago committed by GitHub
parent da4d163d35
commit 1409042275
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -15,5 +15,5 @@
<link rel="stylesheet" href="styles.7f0a84d9b012559f3600.css"></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.f85917275662fd535bb6.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.930629deff965c22cb1b.js" defer></script></body>
<script src="runtime.2493904abb92f5d5f622.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.8a5e79e0326ca1db85cb.js" defer></script></body>
</html>

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 @@
!function(e){function r(r){for(var n,u,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)u=i[p],Object.prototype.hasOwnProperty.call(o,u)&&o[u]&&s.push(o[u][0]),o[u]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return a.push.apply(a,f||[]),t()}function t(){for(var e,r=0;r<a.length;r++){for(var t=a[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(a.splice(r--,1),e=u(u.s=t[0]))}return e}var n={},o={0:0},a=[];function u(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,u),t.l=!0,t.exports}u.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 a,i=document.createElement("script");i.charset="utf-8",i.timeout=120,u.nc&&i.setAttribute("nonce",u.nc),i.src=function(e){return u.p+""+({}[e]||e)+"."+{1:"9bb271dd8dffd2d994a5",6:"34493377e46c62128b7c",7:"4a00e92294df28ac9ca1",8:"5ad5f94d29aeaf5781a7"}[e]+".js"}(e);var c=new Error;a=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),a=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+a+")",c.name="ChunkLoadError",c.type=n,c.request=a,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){a({type:"timeout",target:i})}),12e4);i.onerror=i.onload=a,document.head.appendChild(i)}return Promise.all(r)},u.m=e,u.c=n,u.d=function(e,r,t){u.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},u.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},u.t=function(e,r){if(1&r&&(e=u(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(u.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)u.d(t,n,(function(r){return e[r]}).bind(null,n));return t},u.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(r,"a",r),r},u.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},u.p="",u.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()}([]);

@ -1 +0,0 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,d=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&d.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);d.length;)d.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"9bb271dd8dffd2d994a5",6:"006d3904cf050331e88e",7:"4a00e92294df28ac9ca1",8:"977dd110c527ac6c88c5"}[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()}([]);

@ -53,7 +53,7 @@ exports.onChainWithdraw = (req, res, next) => {
logger.error({fileName: 'OnChain', lineNum: 51, msg: 'OnChain Withdraw Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: 'OnChain Withdraw Failed!',
error: err.error
error: err
});
});
}
@ -62,6 +62,7 @@ exports.getTransactions = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/listFunds';
request(options).then((body) => {
if (body.outputs) { body.outputs = common.sortDescByStrKey(body.outputs, 'status'); }
res.status(200).json(body);
}).catch(errRes => {
let err = JSON.parse(JSON.stringify(errRes));

@ -7,43 +7,64 @@
<button tabindex="8" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" default mat-button>X</button>
</mat-card-header>
<mat-card-content class="mt-5px">
<form fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start" class="padding-gap overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()" #form="ngForm">
<mat-form-field fxFlex="55">
<input matInput autoFocus [(ngModel)]="transaction.address" placeholder="Bitcoin Address" tabindex="1" name="address" required #address="ngModel">
<mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="30">
<input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount" name="amount" type="number" step="100" min="0" tabindex="2" required #amount="ngModel">
<span matSuffix> {{selAmountUnit}} </span>
<mat-error *ngIf="!transaction.satoshis">Amount is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="10" fxLayoutAlign="start end">
<mat-select [value]="selAmountUnit" tabindex="3" required name="amountUnit" (selectionChange)="onAmountUnitChange($event)">
<mat-option *ngFor="let amountUnit of amountUnits" [value]="amountUnit">{{amountUnit}}</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="60" fxLayoutAlign="space-between stretch" fxLayout="row wrap">
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
<mat-select tabindex="6" placeholder="Fee Rate" [(value)]="transaction.feeRate" [disabled]="flgMinConf">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
<form fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()" #form="ngForm">
<div fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
<mat-form-field fxFlex="55">
<input matInput autoFocus [(ngModel)]="transaction.address" placeholder="Bitcoin Address" tabindex="1" name="address" required #address="ngModel">
<mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="30">
<input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount" name="amount" type="number" step="100" min="0" tabindex="2" required #amount="ngModel">
<span matSuffix> {{selAmountUnit}} </span>
<mat-error *ngIf="!transaction.satoshis">Amount is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="10" fxLayoutAlign="start end">
<mat-select [value]="selAmountUnit" tabindex="3" required name="amountUnit" (selectionChange)="onAmountUnitChange($event)">
<mat-option *ngFor="let amountUnit of amountUnits" [value]="amountUnit">{{amountUnit}}</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="7" color="primary" [(ngModel)]="flgMinConf" (change)="transaction.feeRate=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98">
<input matInput [(ngModel)]="transaction.minconf" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf">
<mat-error *ngIf="flgMinConf && !transaction.minconf">Min Confirmation Blocks is required.</mat-error>
<div fxFlex="60" fxLayoutAlign="space-between stretch" fxLayout="row wrap">
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
<mat-select tabindex="6" placeholder="Fee Rate" [(value)]="transaction.feeRate" [disabled]="flgMinConf">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="7" color="primary" [(ngModel)]="flgMinConf" (change)="transaction.feeRate=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98">
<input matInput [(ngModel)]="transaction.minconf" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf">
<mat-error *ngIf="flgMinConf && !transaction.minconf">Min Confirmation Blocks is required.</mat-error>
</mat-form-field>
</div>
</div>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between stretch">
<mat-expansion-panel fxLayout="column" fxFlex="100" class="flat-expansion-panel mt-2" expanded="false" (closed)="onAdvancedPanelToggle(true)" (opened)="onAdvancedPanelToggle(false)">
<mat-expansion-panel-header>
<mat-panel-title>
<span>{{advancedTitle}}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="30" fxLayoutAlign="start end">
<mat-select tabindex="8" placeholder="Coin Selection" (selectionChange)="onUTXOSelectionChange($event)" [(value)]="selUTXOs" multiple>
<mat-select-trigger>{{totalSelectedUTXOAmount | number}} Sats ({{selUTXOs.length > 1 ? selUTXOs.length + ' UTXOs' : '1 UTXO'}})</mat-select-trigger>
<mat-option *ngFor="let transaction of transactions" [value]="transaction">{{transaction.value | number}} Sats</mat-option>
</mat-select>
</mat-form-field>
</div>
</div>
</mat-expansion-panel>
</div>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch"></div>
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="sendFundError !== ''">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<span *ngIf="sendFundError !== ''">{{sendFundError}}</span>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end center">
<div class="mt-2" fxLayout="row" fxFlex="100" fxLayoutAlign="end center">
<button class="mr-1" mat-stroked-button color="primary" tabindex="7" type="reset">Clear Fields</button>
<button mat-flat-button color="primary" type="submit" tabindex="8">Send Funds</button>
</div>

@ -1,6 +1,6 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { Subject } from 'rxjs';
import { Subject, combineLatest } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
@ -8,7 +8,7 @@ import { MatDialogRef } from '@angular/material/dialog';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { SelNodeChild, GetInfoRoot } from '../../../shared/models/RTLconfig';
import { GetInfo, Balance, OnChain } from '../../../shared/models/clModels';
import { GetInfo, Balance, OnChain, Transaction } from '../../../shared/models/clModels';
import { CURRENCY_UNITS, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, ADDRESS_TYPES, FEE_RATE_TYPES } from '../../../shared/services/consts-enums-functions';
import { RTLConfiguration } from '../../../shared/models/RTLconfig';
import { CommonService } from '../../../shared/services/common.service';
@ -30,6 +30,9 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
public appConfig: RTLConfiguration;
public nodeData: GetInfoRoot;
public addressTypes = [];
public transactions: Transaction[] = [];
public selUTXOs = [];
public totalSelectedUTXOAmount = 0;
public flgLoadingWallet: Boolean | 'error' = true;
public selectedAddress = ADDRESS_TYPES[1];
public blockchainBalance: Balance = {};
@ -45,24 +48,30 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
public currConvertorRate = {};
public unitConversionValue = 0;
public currencyUnitFormats = CURRENCY_UNIT_FORMATS;
public advancedTitle = 'Advanced Options';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<CLOnChainSendComponent>, private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions$: Actions) {}
ngOnInit() {
this.store.select('root')
combineLatest(
this.store.select('root'),
this.store.select('cl'))
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rootStore) => {
.subscribe(([rootStore, rtlStore]) => {
this.fiatConversion = rootStore.selNode.settings.fiatConversion;
this.amountUnits = rootStore.selNode.settings.currencyUnits;
this.appConfig = rootStore.appConfig;
this.nodeData = rootStore.nodeData;
this.transactions = this.commonService.sortAscByKey(rtlStore.transactions.filter(tran => tran.status === 'confirmed'), 'value');
this.logger.info(rootStore);
this.logger.info(rtlStore);
});
this.actions$.pipe(takeUntil(this.unSubs[1]),
filter(action => action.type === CLActions.EFFECT_ERROR_CL || action.type === CLActions.SET_CHANNEL_TRANSACTION_RES_CL))
.subscribe((action: CLActions.EffectError | CLActions.SetChannelTransactionRes) => {
if (action.type === CLActions.SET_CHANNEL_TRANSACTION_RES_CL) {
this.store.dispatch(new CLActions.FetchTransactions());
this.store.dispatch(new RTLActions.OpenSnackBar('Fund Sent Successfully!'));
this.dialogRef.close();
}
@ -76,6 +85,10 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
onSendFunds() {
if(this.invalidValues) { return true; }
this.sendFundError = '';
if (this.selUTXOs.length && this.selUTXOs.length > 0) {
this.transaction.utxos = [];
this.selUTXOs.forEach(utxo => this.transaction.utxos.push(utxo.txid + ':' + utxo.output));
}
this.store.dispatch(new RTLActions.OpenSpinner('Sending Funds...'));
if(this.transaction.satoshis && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
this.commonService.convertCurrency(this.transaction.satoshis, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, this.amountUnits[2], this.fiatConversion)
@ -100,6 +113,15 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
this.sendFundError = '';
this.transaction = {};
this.flgMinConf = false;
this.totalSelectedUTXOAmount = 0;
this.selUTXOs = [];
}
onUTXOSelectionChange(event: any) {
let utxoNew = {value: 0};
if (this.selUTXOs.length && this.selUTXOs.length > 0) {
this.totalSelectedUTXOAmount = this.selUTXOs.reduce((a, b) => {utxoNew.value = a.value + b.value; return utxoNew;}).value;
}
}
onAmountUnitChange(event: any) {
@ -116,6 +138,14 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
this.selAmountUnit = event.value;
}
onAdvancedPanelToggle(isClosed: boolean) {
if (isClosed) {
this.advancedTitle = (this.selUTXOs.length && this.selUTXOs.length > 0) ? 'Advanced Options | Selected UTXOs: ' + this.selUTXOs.length + ' | Total Amount: ' + this.decimalPipe.transform(this.totalSelectedUTXOAmount) + ' Sats' : 'Advanced Options';
} else {
this.advancedTitle = 'Advanced Options';
}
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();

@ -109,6 +109,7 @@ export interface OnChain {
satoshis?: number;
feeRate?: string;
minconf?: number;
utxos?: string[];
}
export interface Hop {

@ -32,6 +32,14 @@ export class CommonService implements OnInit {
});
}
sortAscByKey(array, key) {
return array.sort(function (a, b) {
const x = +a[key];
const y = +b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
camelCase(str) {
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => {
return index == 0 ? word.toLowerCase() : word.toUpperCase();

Loading…
Cancel
Save