feat: add boltz service to cln (#1352)

This commit is contained in:
jackstar12 2024-03-13 00:55:31 +01:00 committed by GitHub
parent 53d9da95c2
commit 93e48845f5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 87 additions and 41 deletions

View File

@ -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

View File

@ -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) => {

View File

@ -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()] },

View File

@ -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>

View File

@ -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() {

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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() {}
}

View File

@ -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">

View File

@ -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,28 +25,36 @@ 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.activeLink = this.links[2].link;
this.router.navigate(['./' + this.activeLink], { relativeTo: this.activatedRoute });
}
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;
} else {
this.activeLink = this.links[this.links.length - 1].link;
}
} else {
this.activeLink = this.links[this.links.length - 1].link;
}
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(<any>null);

View File

@ -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 }
],

View File

@ -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)));
}

View File

@ -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,

View File

@ -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;