cln: Boltz auto-send

- Added auto send option for Swap In
- Checking compatiblity with v2.0.0 and above
pull/1366/head
ShahanaFarooqui 2 months ago
parent 3acfb2b044
commit 9db447d31c

@ -79,7 +79,7 @@ export const getSwapInfo = (req, res, next) => {
});
};
export const createSwap = (req, res, next) => {
const { amount, address } = req.body;
const { amount, sendFromInternal, address } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Swap..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -89,6 +89,9 @@ export const createSwap = (req, res, next) => {
}
options.url = options.url + '/v1/createswap';
options.body = { amount: amount };
if (sendFromInternal) {
options.body.send_from_internal = sendFromInternal;
}
if (address && address !== '') {
options.body.address = address;
}

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

@ -80,7 +80,7 @@ export const getSwapInfo = (req, res, next) => {
};
export const createSwap = (req, res, next) => {
const { amount, address } = req.body;
const { amount, sendFromInternal, address } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Swap..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -90,6 +90,7 @@ export const createSwap = (req, res, next) => {
}
options.url = options.url + '/v1/createswap';
options.body = { amount: amount };
if (sendFromInternal) { options.body.send_from_internal = sendFromInternal; }
if (address && address !== '') { options.body.address = address; }
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Boltz', msg: 'Create Swap Options Body', data: options.body });
request.post(options).then((createSwapRes) => {

@ -32,6 +32,7 @@ export class BoltzRootComponent implements OnInit, OnDestroy {
constructor(private router: Router, private store: Store<RTLState>, private boltzService: BoltzService) { }
ngOnInit() {
this.boltzService.getBoltzInfo();
this.boltzService.listSwaps();
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
this.activeTab = linkFound ? linkFound : this.links[0];

@ -32,6 +32,12 @@
<mat-icon matTooltip="Only recommended for smaller payments, involves trust in Boltz" matTooltipPosition="above" class="info-icon mt-2">info_outline</mat-icon>
</div>
</div>
<div *ngIf="direction === swapTypeEnum.SWAP_IN && isSendFromInternalCompatible" fxLayout="column" fxFlex="48" fxLayoutAlign="start stretch">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<mat-slide-toggle fxLayoutAlign="start center" tabindex="2" color="primary" formControlName="sendFromInternal" name="sendFromInternal">Send from Internal Wallet</mat-slide-toggle>
<mat-icon matTooltip="Pay from the node's onchain wallet" matTooltipPosition="above" class="info-icon mt-2">info_outline</mat-icon>
</div>
</div>
</div>
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
<button *ngIf="direction === swapTypeEnum.SWAP_OUT" mat-button color="primary" tabindex="2" type="button" matStepperNext>Next</button>
@ -87,7 +93,7 @@
</div>
</div>
<ng-template #swapStatusBlock>
<rtl-boltz-swap-status fxLayout="column" [swapStatus]="swapStatus" [acceptZeroConf]="inputFormGroup?.controls?.acceptZeroConf.value" [direction]="direction" />
<rtl-boltz-swap-status fxLayout="column" [swapStatus]="swapStatus" [direction]="direction" [acceptZeroConf]="inputFormGroup?.controls?.acceptZeroConf.value" [sendFromInternal]="inputFormGroup?.controls?.sendFromInternal.value" />
</ng-template>
<div *ngIf="flgShowInfo" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" class="info-graphics-container" [@opacityAnimation]>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">

@ -9,7 +9,7 @@ import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { opacityAnimation } from '../../../../animation/opacity-animation';
import { ScreenSizeEnum, SwapTypeEnum } from '../../../../services/consts-enums-functions';
import { ServiceInfo, CreateSwapResponse, CreateReverseSwapResponse } from '../../../../models/boltzModels';
import { ServiceInfo, CreateSwapResponse, CreateReverseSwapResponse, BoltzInfo } from '../../../../models/boltzModels';
import { SwapAlert } from '../../../../models/alertData';
import { BoltzService } from '../../../../services/boltz.service';
import { LoggerService } from '../../../../services/logger.service';
@ -25,6 +25,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild('stepper', { static: false }) stepper: MatStepper;
public faInfoCircle = faInfoCircle;
public boltzInfo: BoltzInfo = null;
public serviceInfo: ServiceInfo = { fees: { percentage: null, miner: { normal: null, reverse: null } }, limits: { minimal: 10000, maximal: 50000000 } };
public swapTypeEnum = SwapTypeEnum;
public direction = SwapTypeEnum.SWAP_OUT;
@ -38,6 +39,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
public screenSizeEnum = ScreenSizeEnum;
public animationDirection = 'forward';
public flgEditable = true;
public isSendFromInternalCompatible = true;
inputFormGroup: UntypedFormGroup;
addressFormGroup: UntypedFormGroup;
statusFormGroup: UntypedFormGroup;
@ -53,7 +55,8 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
this.inputFormLabel = 'Amount to ' + this.swapDirectionCaption;
this.inputFormGroup = this.formBuilder.group({
amount: [this.serviceInfo.limits?.minimal, [Validators.required, Validators.min(this.serviceInfo.limits?.minimal || 0), Validators.max(this.serviceInfo.limits?.maximal || 0)]],
acceptZeroConf: [false]
acceptZeroConf: [false],
sendFromInternal: [true]
});
this.addressFormGroup = this.formBuilder.group({
addressType: ['local', [Validators.required]],
@ -61,6 +64,17 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
});
this.statusFormGroup = this.formBuilder.group({});
this.onFormValueChanges();
this.boltzService.boltzInfoChanged.
pipe(takeUntil(this.unSubs[0])).
subscribe({
next: (boltzInfo: BoltzInfo) => {
this.boltzInfo = boltzInfo;
this.isSendFromInternalCompatible = this.commonService.isVersionCompatible(this.boltzInfo.version, '2.0.0');
}, error: (err) => {
this.boltzInfo = { version: '2.0.0' };
this.logger.error(err);
}
});
}
ngAfterViewInit() {
@ -71,7 +85,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
onFormValueChanges() {
if (this.direction === SwapTypeEnum.SWAP_OUT) {
this.addressFormGroup.valueChanges.pipe(takeUntil(this.unSubs[2])).subscribe((changedValues) => {
this.addressFormGroup.valueChanges.pipe(takeUntil(this.unSubs[1])).subscribe((changedValues) => {
this.addressFormGroup.setErrors({ Invalid: true });
});
}
@ -101,7 +115,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
this.stepper.selected?.stepControl.setErrors(null);
this.stepper.next();
if (this.direction === SwapTypeEnum.SWAP_IN) {
this.boltzService.swapIn(this.inputFormGroup.controls.amount.value).pipe(takeUntil(this.unSubs[3])).
this.boltzService.swapIn(this.inputFormGroup.controls.amount.value, this.isSendFromInternalCompatible ? this.inputFormGroup.controls.sendFromInternal.value : null).pipe(takeUntil(this.unSubs[2])).
subscribe({
next: (swapStatus: CreateSwapResponse) => {
this.swapStatus = swapStatus;
@ -116,7 +130,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
});
} else {
const destAddress = this.addressFormGroup.controls.addressType.value === 'external' ? this.addressFormGroup.controls.address.value : '';
this.boltzService.swapOut(this.inputFormGroup.controls.amount.value, destAddress, this.inputFormGroup.controls.acceptZeroConf.value).pipe(takeUntil(this.unSubs[4])).
this.boltzService.swapOut(this.inputFormGroup.controls.amount.value, destAddress, this.inputFormGroup.controls.acceptZeroConf.value).pipe(takeUntil(this.unSubs[3])).
subscribe({
next: (swapStatus: CreateReverseSwapResponse) => {
this.swapStatus = swapStatus;
@ -142,7 +156,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
case 1:
if (this.inputFormGroup.controls.amount.value) {
if (this.direction === SwapTypeEnum.SWAP_IN) {
this.inputFormLabel = this.swapDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats';
this.inputFormLabel = this.swapDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats | Send from Internal Wallet: ' + (this.inputFormGroup.controls.sendFromInternal.value ? 'Yes' : 'No');
} else {
this.inputFormLabel = this.swapDirectionCaption + ' Amount: ' + (this.decimalPipe.transform(this.inputFormGroup.controls.amount.value ? this.inputFormGroup.controls.amount.value : 0)) + ' Sats | Zero Conf: ' + (this.inputFormGroup.controls.acceptZeroConf.value ? 'Yes' : 'No');
}
@ -187,7 +201,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
onRestart() {
this.stepper.reset();
this.flgEditable = true;
this.inputFormGroup.reset({ amount: this.serviceInfo.limits?.minimal, acceptZeroConf: false });
this.inputFormGroup.reset({ amount: this.serviceInfo.limits?.minimal, acceptZeroConf: false, sendFromInternal: true });
this.statusFormGroup.reset();
this.addressFormGroup.reset({ addressType: 'local', address: '' });
this.addressFormGroup.controls.address.disable();

@ -30,29 +30,48 @@
</div>
</ng-template>
<ng-template #swapInBlock>
<div fxLayout="column">
<div fxLayout="row">
<div fxFlex="50">
<h4 fxLayoutAlign="start" class="font-bold-500">ID</h4>
<span class="foreground-secondary-text">{{swapStatus?.id}}</span>
</div>
<div fxFlex="50">
<h4 fxLayoutAlign="start" class="font-bold-500">Expected Amount (Sats)</h4>
<span class="foreground-secondary-text">{{swapStatus?.expectedAmount | number}}</span>
</div>
</div>
<mat-divider class="w-100 my-1" />
<div fxLayout="row">
<div fxFlex="100">
<h4 fxLayoutAlign="start" class="font-bold-500">Address</h4>
<span class="foreground-secondary-text">{{swapStatus?.address}}</span>
</div>
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
<div fxFlex="35" class="modal-qr-code-container padding-gap-large" [fxLayoutAlign]="(swapStatus?.txId || swapStatus?.address) !== '' ? 'center start' : 'center center'" [ngClass]="{'display-none': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<qr-code [value]="swapStatus?.txId || swapStatus?.address" [size]="qrWidth" [errorCorrectionLevel]="'L'" />
<span *ngIf="(swapStatus?.txId || swapStatus?.address) === ''" class="font-size-300">N/A</span>
</div>
<mat-divider class="w-100 my-1" />
<div fxLayout="row">
<div fxFlex="100">
<h4 fxLayoutAlign="start" class="font-bold-500">BIP 21</h4>
<span class="foreground-secondary-text">{{swapStatus?.bip21}}</span>
<div fxFlex="65">
<div fxLayout="column">
<div fxFlex="30" class="modal-qr-code-container padding-gap" [fxLayoutAlign]="(swapStatus?.txId || swapStatus?.address) !== '' ? 'center start' : 'center center'" [ngClass]="{'display-none': screenSize !== screenSizeEnum.XS && screenSize !== screenSizeEnum.SM}">
<qr-code [value]="swapStatus?.txId || swapStatus?.address" [size]="qrWidth" [errorCorrectionLevel]="'L'" />
<span *ngIf="(swapStatus?.txId || swapStatus?.address) === ''" class="font-size-120">QR Code Not Applicable</span>
</div>
<mat-divider *ngIf="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM" class="my-1" [inset]="true" />
<div *ngIf="sendFromInternal" fxLayout="row">
<div fxFlex="100">
<h4 fxLayoutAlign="start" class="font-bold-500">Transaction ID</h4>
<span class="foreground-secondary-text">{{swapStatus?.txId}}</span>
</div>
</div>
<div *ngIf="!sendFromInternal" fxLayout="row">
<div fxFlex="50">
<h4 fxLayoutAlign="start" class="font-bold-500">ID</h4>
<span class="foreground-secondary-text">{{swapStatus?.id}}</span>
</div>
<div fxFlex="50">
<h4 fxLayoutAlign="start" class="font-bold-500">Expected Amount (Sats)</h4>
<span class="foreground-secondary-text">{{swapStatus?.expectedAmount | number}}</span>
</div>
</div>
<mat-divider *ngIf="!sendFromInternal" class="w-100 my-1" />
<div *ngIf="!sendFromInternal" fxLayout="row">
<div fxFlex="100">
<h4 fxLayoutAlign="start" class="font-bold-500">Address</h4>
<span class="foreground-secondary-text">{{swapStatus?.address}}</span>
</div>
</div>
<mat-divider *ngIf="!sendFromInternal" class="w-100 my-1" />
<div *ngIf="!sendFromInternal" fxLayout="row">
<div fxFlex="100">
<h4 fxLayoutAlign="start" class="font-bold-500">BIP 21</h4>
<span class="foreground-secondary-text">{{swapStatus?.bip21}}</span>
</div>
</div>
</div>
</div>
</div>

@ -1,19 +1,31 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { SwapTypeEnum } from '../../../../services/consts-enums-functions';
import { SwapTypeEnum, ScreenSizeEnum } from '../../../../services/consts-enums-functions';
import { CommonService } from '../../../../services/common.service';
@Component({
selector: 'rtl-boltz-swap-status',
templateUrl: './swap-status.component.html',
styleUrls: ['./swap-status.component.scss']
})
export class SwapStatusComponent {
export class SwapStatusComponent implements OnInit {
@Input() swapStatus: any = null;
@Input() direction = SwapTypeEnum.SWAP_OUT;
@Input() acceptZeroConf = false;
@Input() sendFromInternal = true;
public qrWidth = 240;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
public swapTypeEnum = SwapTypeEnum;
constructor() {}
constructor(private commonService: CommonService) {}
ngOnInit() {
this.screenSize = this.commonService.getScreenSize();
if (this.screenSize === ScreenSizeEnum.XS) {
this.qrWidth = 180;
}
}
}

@ -10,9 +10,9 @@
<mat-divider class="w-100" />
<mat-tree *ngIf="settings?.lnServerUrl" #tree [dataSource]="navMenus" [treeControl]="treeControlNested">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle routerLinkActive="active-link" routerLink="{{node.link}}">
<div tabindex="2" (click)="onChildNavClicked(node)">
<div tabindex="2" (click)="onChildNavClicked(node)">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<span *ngIf="node.iconType === 'SVG'" class="fa-icon-small mr-2"><ng-container [ngTemplateOutlet]="node.icon === 'boltzIconBlock' ? boltzIconBlock : null" /></span>
<span *ngIf="node.iconType === 'SVG'" class="fa-icon-small mr-2" fxLayout="row" fxFlex="100" fxLayoutAlign="start center"><ng-container [ngTemplateOutlet]="node.icon === 'boltzIconBlock' ? boltzIconBlock : null" /></span>
<fa-icon *ngIf="node.iconType === 'FA'" class="fa-icon-small mr-2" [icon]="node.icon" />
<mat-icon *ngIf="!node.iconType" class="mat-icon-36">{{node.icon}}</mat-icon>
<span>{{node.name}}</span>

@ -4,7 +4,7 @@ import { GetInfo, Invoice, Channel, Peer, PendingOpenChannel, UTXO } from './lnd
import { Invoice as InvoiceCLN, GetInfo as GetInfoCLN, Peer as PeerCLN, Channel as ChannelCLN, UTXO as UTXOCLN, Offer as OfferCLN, LookupNode as LookupNodeCLN } from './clnModels';
import { GetInfo as GetInfoECL, Peer as PeerECL, Channel as ChannelECL, Invoice as InvoiceECL, PaymentSent as PaymentSentECL } from './eclModels';
import { LoopQuote } from './loopModels';
import { ServiceInfo } from './boltzModels';
import { BoltzInfo, ServiceInfo } from './boltzModels';
export interface MessageErrorField {
code: number;

@ -1,3 +1,12 @@
export interface BoltzInfo {
version: string;
node?: string;
network?: string;
nodePubkey?: string;
autoSwapStatus?: string;
blockHeights?: any;
}
export interface ServiceInfo {
fees?: {percentage?: number | null, miner: {normal?: number | null, reverse?: number | null}};
limits?: {minimal?: number | null, maximal?: number | null};
@ -62,6 +71,7 @@ export interface CreateSwapResponse {
address?: string;
expectedAmount?: string;
bip21?: string;
txId?: string;
}
export interface CreateReverseSwapRequest {

@ -8,7 +8,7 @@ import { ErrorMessageComponent } from '../components/data-modal/error-message/er
import { CommonService } from './common.service';
import { LoggerService } from './logger.service';
import { API_URL, API_END_POINTS, AlertTypeEnum, UI_MESSAGES } from './consts-enums-functions';
import { ListSwaps } from '../models/boltzModels';
import { ListSwaps, BoltzInfo } from '../models/boltzModels';
import { closeSpinner, logout, openAlert, openSpinner } from '../../store/rtl.actions';
import { RTLState } from '../../store/rtl.state';
@ -18,8 +18,10 @@ export class BoltzService implements OnDestroy {
private swapUrl = '';
private swaps: ListSwaps = {};
private boltzInfo: BoltzInfo = null;
public boltzInfoChanged = new BehaviorSubject<BoltzInfo>(null);
public swapsChanged = new BehaviorSubject<ListSwaps>({});
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private httpClient: HttpClient, private logger: LoggerService, private store: Store<RTLState>, private commonService: CommonService) { }
@ -46,11 +48,29 @@ export class BoltzService implements OnDestroy {
return this.httpClient.get(this.swapUrl).pipe(catchError((err) => of(this.handleErrorWithAlert(UI_MESSAGES.NO_SPINNER, this.swapUrl, err))));
}
getBoltzInfo() {
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.GET_BOLTZ_INFO }));
this.swapUrl = API_URL + API_END_POINTS.BOLTZ_API + '/info';
this.httpClient.get(this.swapUrl).
pipe(takeUntil(this.unSubs[1])).
subscribe({
next: (res: BoltzInfo) => {
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_BOLTZ_INFO }));
this.boltzInfo = res;
this.boltzInfoChanged.next(this.boltzInfo);
}, error: (err) => {
this.boltzInfo = { version: '2.0.0' };
this.boltzInfoChanged.next(this.boltzInfo);
return of(this.handleErrorWithoutAlert(UI_MESSAGES.GET_BOLTZ_INFO, this.swapUrl, err));
}
});
}
serviceInfo() {
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.GET_SERVICE_INFO }));
this.swapUrl = API_URL + API_END_POINTS.BOLTZ_API + '/serviceInfo';
return this.httpClient.get(this.swapUrl).pipe(
takeUntil(this.unSubs[1]),
takeUntil(this.unSubs[2]),
map((res) => {
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_SERVICE_INFO }));
return res;
@ -65,8 +85,8 @@ export class BoltzService implements OnDestroy {
return this.httpClient.post(this.swapUrl, requestBody).pipe(catchError((err) => this.handleErrorWithoutAlert('Swap Out for Address: ' + address, UI_MESSAGES.NO_SPINNER, err)));
}
swapIn(amount: number) {
const requestBody = { amount: amount };
swapIn(amount: number, sendFromInternal: boolean) {
const requestBody = { amount: amount, sendFromInternal: sendFromInternal };
this.swapUrl = API_URL + API_END_POINTS.BOLTZ_API + '/createswap';
return this.httpClient.post(this.swapUrl, requestBody).pipe(catchError((err) => this.handleErrorWithoutAlert('Swap In for Amount: ' + amount, UI_MESSAGES.NO_SPINNER, err)));
}

@ -361,6 +361,7 @@ export const UI_MESSAGES = {
UPDATE_NODE_SETTINGS: 'Updating Node Settings...',
UPDATE_SELECTED_NODE: 'Updating Selected Node...',
OPEN_CONFIG_FILE: 'Opening Config File...',
GET_BOLTZ_INFO: 'Getting Boltz Info...',
GET_SERVICE_INFO: 'Getting Service Info...',
GET_QUOTE: 'Getting Quotes...',
UPDATE_DEFAULT_NODE_SETTING: 'Updating Defaule Node Settings...',

@ -220,9 +220,12 @@
& .ng-fa-icon, & .mat-icon {
color: $primary-darker;
}
& .sidenav-img svg {
& .sidenav-img svg, & .boltz-icon-fill {
fill: $primary-darker;
}
& .boltz-icon {
stroke: $primary-darker;
}
}
.mat-tree-node .sidenav-img, .mat-nested-tree-node .sidenav-img, .mat-nested-tree-node-parent .sidenav-img,
.page-title-container .page-title-img, svg.top-icon-small {

Loading…
Cancel
Save