feat: add boltz service to cln (#1352)

pull/1366/head
jackstar12 2 months ago committed by GitHub
parent 53d9da95c2
commit 93e48845f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -109,7 +109,7 @@ export const createReverseSwap = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createreverseswap';
options.body = { amount: req.body.amount };
options.body = { amount: req.body.amount, accept_zero_conf: req.body.acceptZeroConf || false };
if (req.body.address !== '') {
options.body.address = req.body.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

@ -109,7 +109,7 @@ export const createReverseSwap = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createreverseswap';
options.body = { amount: req.body.amount };
options.body = { amount: req.body.amount, accept_zero_conf: req.body.acceptZeroConf || false };
if (req.body.address !== '') { options.body.address = req.body.address; }
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Boltz', msg: 'Create Reverse Swap Body', data: options.body });
request.post(options).then((createReverseSwapRes) => {

@ -21,6 +21,7 @@ import { NotFoundComponent } from './shared/components/not-found/not-found.compo
import { ErrorComponent } from './shared/components/error/error.component';
import { AuthGuard } from './shared/services/auth.guard';
import { ExperimentalSettingsComponent } from './shared/components/node-config/experimental-settings/experimental-settings.component';
import { NoServiceFoundComponent } from './shared/components/node-config/services-settings/no-service-found/no-service-found.component';
type PathMatch = 'full' | 'prefix' | undefined;
@ -46,7 +47,8 @@ export const routes: Routes = [
path: 'services', component: ServicesSettingsComponent, canActivate: [AuthGuard()], children: [
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'loop' },
{ path: 'loop', component: LoopServiceSettingsComponent, canActivate: [AuthGuard()] },
{ path: 'boltz', component: BoltzServiceSettingsComponent, canActivate: [AuthGuard()] }
{ path: 'boltz', component: BoltzServiceSettingsComponent, canActivate: [AuthGuard()] },
{ path: 'noservice', component: NoServiceFoundComponent }
]
},
{ path: 'experimental', component: ExperimentalSettingsComponent, canActivate: [AuthGuard()] },

@ -2,7 +2,7 @@
<div fxFlex="100">
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
<div fxLayoutAlign="start start" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '83' : '91'"><span class="page-title">{{swapDirectionCaption}}</span></div>
<div fxLayoutAlign="space-between end" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '17' : '9'">
<div fxLayoutAlign="end end" [fxFlex]="screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM ? '17' : '9'">
<button tabindex="21" class="btn-close-x p-0" mat-button (click)="showInfo()">?</button>
<button tabindex="22" class="btn-close-x p-0" mat-button (click)="onClose()">X</button>
</div>
@ -26,6 +26,12 @@
<mat-error *ngIf="inputFormGroup?.controls?.amount?.errors?.min">Amount must be greater than or equal to {{serviceInfo?.limits?.minimal | number}}.</mat-error>
<mat-error *ngIf="inputFormGroup?.controls?.amount?.errors?.max">Amount must be less than or equal to {{serviceInfo?.limits?.maximal | number}}.</mat-error>
</mat-form-field>
<div *ngIf="direction === swapTypeEnum.SWAP_OUT" 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="acceptZeroConf" name="acceptZeroConf">Accept Zero Conf</mat-slide-toggle>
<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>
<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>

@ -1,12 +1,10 @@
import { Component, OnInit, Inject, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DecimalPipe } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { MatStepper } from '@angular/material/stepper';
import { Store } from '@ngrx/store';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { opacityAnimation } from '../../../../animation/opacity-animation';
@ -17,8 +15,6 @@ import { BoltzService } from '../../../../services/boltz.service';
import { LoggerService } from '../../../../services/logger.service';
import { CommonService } from '../../../../services/common.service';
import { RTLState } from '../../../../../store/rtl.state';
@Component({
selector: 'rtl-boltz-swap-modal',
templateUrl: './swap-modal.component.html',
@ -56,7 +52,8 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
this.swapDirectionCaption = this.direction === SwapTypeEnum.SWAP_OUT ? 'Swap Out' : 'Swap in';
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)]]
amount: [this.serviceInfo.limits?.minimal, [Validators.required, Validators.min(this.serviceInfo.limits?.minimal || 0), Validators.max(this.serviceInfo.limits?.maximal || 0)]],
acceptZeroConf: [false]
});
this.addressFormGroup = this.formBuilder.group({
addressType: ['local', [Validators.required]],
@ -119,7 +116,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).pipe(takeUntil(this.unSubs[4])).
this.boltzService.swapOut(this.inputFormGroup.controls.amount.value, destAddress, this.inputFormGroup.controls.acceptZeroConf.value).pipe(takeUntil(this.unSubs[4])).
subscribe({
next: (swapStatus: CreateReverseSwapResponse) => {
this.swapStatus = swapStatus;
@ -147,7 +144,7 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
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';
} else {
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 | Zero Conf: ' + (this.inputFormGroup.controls.acceptZeroConf.value ? 'Yes' : 'No');
}
} else {
this.inputFormLabel = 'Amount to ' + this.swapDirectionCaption;
@ -190,10 +187,11 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
onRestart() {
this.stepper.reset();
this.flgEditable = true;
this.inputFormGroup.reset({ amount: this.serviceInfo.limits?.minimal });
this.inputFormGroup.reset({ amount: this.serviceInfo.limits?.minimal, acceptZeroConf: false });
this.statusFormGroup.reset();
this.addressFormGroup.reset({ addressType: 'local', address: '' });
this.addressFormGroup.controls.address.disable();
this.swapStatus = null;
}
ngOnDestroy() {

@ -177,7 +177,8 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
this.navMenus.data = clonedMenu?.filter((navMenuData: any) => {
if (navMenuData.children && navMenuData.children.length) {
navMenuData.children = navMenuData.children?.filter((navMenuChild) => ((navMenuChild.userPersona === UserPersonaEnum.ALL || navMenuChild.userPersona === this.settings?.userPersona) && navMenuChild.link !== '/services/peerswap') ||
(navMenuChild.link === '/services/peerswap' && this.settings?.enablePeerswap));
(navMenuChild.link === '/services/peerswap' && this.settings?.enablePeerswap) ||
(navMenuChild.link === '/services/boltz' && this.settings?.boltzServerUrl && this.settings.boltzServerUrl.trim() !== ''));
return navMenuData.children.length > 0;
}
return navMenuData.userPersona === UserPersonaEnum.ALL || navMenuData.userPersona === this.settings?.userPersona;

@ -8,7 +8,7 @@
<nav mat-tab-nav-bar mat-stretch-tabs="false" mat-align-tabs="start" [tabPanel]="tabPanel">
<div tabindex="1" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[0].link" routerLink="{{links[0].link}}" (click)="activeLink = links[0].link">{{links[0].name}}</div>
<div tabindex="2" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[1].link" routerLink="{{links[1].link}}" (click)="activeLink = links[1].link">{{links[1].name}}</div>
<div *ngIf="selNode?.lnImplementation?.toUpperCase() === 'LND'" tabindex="3" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[2].link" routerLink="{{links[2].link}}" [state]="{ initial: false }" (click)="activeLink = links[2].link">{{links[2].name}}</div>
<div *ngIf="selNode?.lnImplementation?.toUpperCase() !== 'ECL'" tabindex="3" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[2].link" routerLink="{{links[2].link}}" [state]="{ initial: false }" (click)="activeLink = links[2].link">{{links[2].name}}</div>
<div *ngIf="selNode?.lnImplementation?.toUpperCase() === 'CLN'" tabindex="4" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[3].link" routerLink="{{links[3].link}}" (click)="activeLink = links[3].link">{{links[3].name}}</div>
<div *ngIf="showLnConfig" tabindex="5" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[4].link" (click)="showLnConfigClicked()">{{links[4].name}}</div>
</nav>

@ -0,0 +1,9 @@
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column" class="padding-gap-large">
<div fxLayout="column" fxLayoutAlign="start start">
<div class="box-text">No Service Found!</div>
</div>
</mat-card-content>
</mat-card>
</div>

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'rtl-no-service-found',
templateUrl: './no-service-found.component.html'
})
export class NoServiceFoundComponent {
constructor() {}
}

@ -8,9 +8,11 @@
<mat-card>
<mat-card-content fxLayout="column">
<nav mat-tab-nav-bar mat-stretch-tabs="false" mat-align-tabs="start" [tabPanel]="tabPanel">
<div *ngIf="selNode?.lnImplementation?.toUpperCase() === 'LND'" tabindex="1" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[0].link" routerLink="{{links[0].link}}" (click)="activeLink = links[0].link">{{links[0].name}}</div>
<div *ngIf="selNode?.lnImplementation?.toUpperCase() === 'LND'" tabindex="2" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[1].link" routerLink="{{links[1].link}}" [state]="{ initial: false }" (click)="activeLink = links[1].link">{{links[1].name}}</div>
<div *ngIf="selNode?.lnImplementation?.toUpperCase() === 'CLN'" tabindex="3" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[2].link" routerLink="{{links[2].link}}" (click)="activeLink = links[2].link">{{links[2].name}}</div>
<div *ngIf="selNode?.settings?.swapServerUrl?.trim() !== ''" tabindex="1" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[0].link" routerLink="{{links[0].link}}" (click)="activeLink = links[0].link">{{links[0].name}}</div>
<div *ngIf="selNode?.settings?.boltzServerUrl?.trim() !== ''" tabindex="2" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[1].link" routerLink="{{links[1].link}}" [state]="{ initial: false }" (click)="activeLink = links[1].link">{{links[1].name}}</div>
<!-- <div *ngIf="selNode?.settings?.enablePeerswap" tabindex="3" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[2].link" routerLink="{{links[2].link}}" (click)="activeLink = links[2].link">{{links[2].name}}</div> -->
<!-- <div *ngIf="selNode?.settings?.swapServerUrl?.trim() === '' && selNode?.settings?.boltzServerUrl?.trim() === '' && !selNode?.settings?.enablePeerswap" tabindex="4" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[3].link" routerLink="{{links[3].link}}" (click)="activeLink = links[3].link">{{links[3].name}}</div> -->
<div *ngIf="selNode?.settings?.swapServerUrl?.trim() === '' && selNode?.settings?.boltzServerUrl?.trim() === ''" tabindex="3" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[2].link" routerLink="{{links[2].link}}" (click)="activeLink = links[2].link">{{links[2].name}}</div>
</nav>
<mat-tab-nav-panel #tabPanel />
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="mat-tab-body-wrapper">

@ -16,7 +16,8 @@ import { rootSelectedNode } from '../../../../store/rtl.selector';
export class ServicesSettingsComponent implements OnInit, OnDestroy {
public faLayerGroup = faLayerGroup;
public links = [{ link: 'loop', name: 'Loop' }, { link: 'boltz', name: 'Boltz' }, { link: 'peerswap', name: 'Peerswap' }];
// public links = [{ link: 'loop', name: 'Loop' }, { link: 'boltz', name: 'Boltz' }, { link: 'peerswap', name: 'Peerswap' }, { link: 'noservice', name: 'No Service' }];
public links = [{ link: 'loop', name: 'Loop' }, { link: 'boltz', name: 'Boltz' }, { link: 'noservice', name: 'No Service' }];
public activeLink = '';
public selNode: ConfigSettingsNode | any;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
@ -24,26 +25,34 @@ export class ServicesSettingsComponent implements OnInit, OnDestroy {
constructor(private store: Store<RTLState>, private router: Router, private activatedRoute: ActivatedRoute) { }
ngOnInit() {
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
this.setActiveLink();
this.router.events.pipe(takeUntil(this.unSubs[0]), filter((e) => e instanceof ResolveEnd)).
subscribe({
next: (value: ResolveEnd | Event) => {
const linkFound = this.links.find((link) => (<ResolveEnd>value).urlAfterRedirects.includes(link.link));
if (this.selNode.lnImplementation.toUpperCase() === 'CLN') {
this.activeLink = this.links[2].link;
} else {
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
}
this.setActiveLink();
}
});
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[1])).subscribe((selNode) => {
this.selNode = selNode;
if (this.selNode.lnImplementation.toUpperCase() === 'CLN') {
this.setActiveLink();
this.router.navigate(['./' + this.activeLink], { relativeTo: this.activatedRoute });
});
}
setActiveLink() {
if (this.selNode && this.selNode.settings) {
if (this.selNode.settings.swapServerUrl && this.selNode.settings.swapServerUrl.trim() !== '') {
this.activeLink = this.links[0].link;
} else if (this.selNode.settings.boltzServerUrl && this.selNode.settings.boltzServerUrl.trim() !== '') {
this.activeLink = this.links[1].link;
} else if (this.selNode.settings.enablePeerswap) {
this.activeLink = this.links[2].link;
this.router.navigate(['./' + this.activeLink], { relativeTo: this.activatedRoute });
} else {
this.activeLink = this.links[this.links.length - 1].link;
}
});
} else {
this.activeLink = this.links[this.links.length - 1].link;
}
}
ngOnDestroy() {

@ -64,11 +64,12 @@ export const MENU_DATA: MenuRootNode = {
{ id: 39, parentId: 3, name: 'Node/Fee Rates', iconType: 'FA', icon: faServer, link: '/cln/rates', userPersona: UserPersonaEnum.MERCHANT }
]
},
// {
// id: 4, parentId: 0, name: 'Services', iconType: 'FA', icon: faLayerGroup, link: '/services/peerswap', userPersona: UserPersonaEnum.ALL, children: [
// { id: 41, parentId: 4, name: 'Peerswap', iconType: 'FA', icon: faHandshake, link: '/services/peerswap', userPersona: UserPersonaEnum.ALL },
// ]
// },
{
id: 4, parentId: 0, name: 'Services', iconType: 'FA', icon: faLayerGroup, link: '/services/loop', userPersona: UserPersonaEnum.ALL, children: [
//{ id: 41, parentId: 4, name: 'Peerswap', iconType: 'FA', icon: faHandshake, link: '/services/peerswap', userPersona: UserPersonaEnum.ALL },
{ id: 42, parentId: 4, name: 'Boltz', iconType: 'SVG', icon: 'boltzIconBlock', link: '/services/boltz', userPersona: UserPersonaEnum.ALL }
]
},
{ id: 5, parentId: 0, name: 'Node Config', iconType: 'FA', icon: faTools, link: '/config', userPersona: UserPersonaEnum.ALL },
{ id: 6, parentId: 0, name: 'Help', iconType: 'FA', icon: faQuestion, link: '/help', userPersona: UserPersonaEnum.ALL }
],

@ -59,8 +59,8 @@ export class BoltzService implements OnDestroy {
);
}
swapOut(amount: number, address: string) {
const requestBody = { amount: amount, address: address };
swapOut(amount: number, address: string, acceptZeroConf: boolean) {
const requestBody = { amount: amount, address: address, acceptZeroConf: acceptZeroConf };
this.swapUrl = API_URL + API_END_POINTS.BOLTZ_API + '/createreverseswap';
return this.httpClient.post(this.swapUrl, requestBody).pipe(catchError((err) => this.handleErrorWithoutAlert('Swap Out for Address: ' + address, UI_MESSAGES.NO_SPINNER, err)));
}

@ -68,6 +68,7 @@ import { ServicesSettingsComponent } from './components/node-config/services-set
import { LoopServiceSettingsComponent } from './components/node-config/services-settings/loop-service-settings/loop-service-settings.component';
import { BoltzServiceSettingsComponent } from './components/node-config/services-settings/boltz-service-settings/boltz-service-settings.component';
import { PeerswapServiceSettingsComponent } from './components/node-config/services-settings/peerswap-service-settings/peerswap-service-settings.component';
import { NoServiceFoundComponent } from './components/node-config/services-settings/no-service-found/no-service-found.component';
import { ExperimentalSettingsComponent } from './components/node-config/experimental-settings/experimental-settings.component';
import { ErrorComponent } from './components/error/error.component';
import { CurrencyUnitConverterComponent } from './components/currency-unit-converter/currency-unit-converter.component';
@ -256,6 +257,7 @@ export const DEFAULT_DATE_FORMAT: MatDateFormats = {
LoopServiceSettingsComponent,
BoltzServiceSettingsComponent,
PeerswapServiceSettingsComponent,
NoServiceFoundComponent,
ExperimentalSettingsComponent,
CurrencyUnitConverterComponent,
HorizontalScrollerComponent,
@ -296,6 +298,7 @@ export const DEFAULT_DATE_FORMAT: MatDateFormats = {
LoopServiceSettingsComponent,
BoltzServiceSettingsComponent,
PeerswapServiceSettingsComponent,
NoServiceFoundComponent,
ExperimentalSettingsComponent,
CurrencyUnitConverterComponent,
HorizontalScrollerComponent,

@ -1451,6 +1451,10 @@ mat-cell:last-of-type, .mdc-data-table__header-cell:last-of-type, mat-footer-cel
padding: ($gap*1.25) ($gap*1.25) ($gap*1.25) $gap !important;
}
.mat-vertical-stepper-content {
margin: 0 $gap;
}
.ellipsis-child {
flex: 1;
white-space: nowrap;

Loading…
Cancel
Save