Release 0.15.5 (#1492)

* Version Updated to 0.15.5-beta

* Fix to show correct experimental-dual-fund configuration from listconfig (#1479)

* feat: boltz swap in refund address (#1490)

require a refund address when creating a swap in and paying it
externally to make sure the swap can be refunded automatically if it
fails.

---------

Co-authored-by: jackstar12 <62219658+jackstar12@users.noreply.github.com>
This commit is contained in:
ShahanaFarooqui 2025-07-07 11:36:23 -07:00 committed by GitHub
parent 7a03673e6e
commit 8a0304c162
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 71 additions and 17 deletions

View file

@ -79,7 +79,7 @@ export const getSwapInfo = (req, res, next) => {
});
};
export const createSwap = (req, res, next) => {
const { amount, sendFromInternal, address } = req.body;
const { amount, sendFromInternal, address, refundAddress } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Swap..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -88,7 +88,7 @@ export const createSwap = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createswap';
options.body = { amount: amount };
options.body = { amount: amount, refundAddress };
if (sendFromInternal) {
options.body.send_from_internal = sendFromInternal;
}

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

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "rtl",
"version": "0.15.4-beta",
"version": "0.15.5-beta",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "rtl",
"version": "0.15.4-beta",
"version": "0.15.5-beta",
"license": "MIT",
"dependencies": {
"@ngrx/effects": "^17.2.0",

View file

@ -1,6 +1,6 @@
{
"name": "rtl",
"version": "0.15.4-beta",
"version": "0.15.5-beta",
"license": "MIT",
"type": "module",
"scripts": {

View file

@ -80,7 +80,7 @@ export const getSwapInfo = (req, res, next) => {
};
export const createSwap = (req, res, next) => {
const { amount, sendFromInternal, address } = req.body;
const { amount, sendFromInternal, address, refundAddress } = req.body;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Boltz', msg: 'Creating Swap..' });
options = common.getBoltzServerOptions(req);
if (options.url === '') {
@ -89,7 +89,7 @@ export const createSwap = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
options.url = options.url + '/v1/createswap';
options.body = { amount: amount };
options.body = { amount: amount, refundAddress };
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 });

View file

@ -38,6 +38,16 @@
<mat-icon matTooltip="Pay from the node's onchain wallet" matTooltipPosition="above" class="info-icon mt-2">info_outline</mat-icon>
</div>
</div>
<div disabled="direction === swapTypeEnum.SWAP_IN && isSendFromInternalCompatible && !inputFormGroup?.controls?.sendFromInternal.value" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" class="mt-1">
<mat-form-field fxLayout="column" fxFlex="100">
<mat-label>Refund Address</mat-label>
<input matInput type="text" tabindex="3" formControlName="refundAddress" [required]="!inputFormGroup?.controls?.sendFromInternal.value">
<mat-hint>The address where funds will be returned in case of a failed swap</mat-hint>
<mat-error *ngIf="inputFormGroup?.controls?.refundAddress?.errors?.required">
Refund address is required when not using internal wallet.
</mat-error>
</mat-form-field>
</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

@ -56,7 +56,8 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
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],
sendFromInternal: [true]
sendFromInternal: [true],
refundAddress: [{ value: '', disabled: true }]
});
this.addressFormGroup = this.formBuilder.group({
addressType: ['local', [Validators.required]],
@ -89,6 +90,25 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
this.addressFormGroup.setErrors({ Invalid: true });
});
}
if (this.direction === SwapTypeEnum.SWAP_IN) {
this.inputFormGroup.controls.sendFromInternal.valueChanges.
pipe(takeUntil(this.unSubs[4])).
subscribe(() => {
this.onSendFromInternalChange();
});
}
}
onSendFromInternalChange() {
if (!this.inputFormGroup.controls.sendFromInternal.value) {
this.inputFormGroup.controls.refundAddress.enable();
this.inputFormGroup.controls.refundAddress.setValidators([Validators.required]);
this.inputFormGroup.controls.refundAddress.updateValueAndValidity();
} else {
this.inputFormGroup.controls.refundAddress.disable();
this.inputFormGroup.controls.refundAddress.clearValidators();
this.inputFormGroup.controls.refundAddress.updateValueAndValidity();
}
}
onAddressTypeChange(event: any) {
@ -115,7 +135,22 @@ 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, this.isSendFromInternalCompatible ? this.inputFormGroup.controls.sendFromInternal.value : null).pipe(takeUntil(this.unSubs[2])).
const refundAddress = this.inputFormGroup.controls.sendFromInternal.value ?
null : this.inputFormGroup.controls.refundAddress.value;
const sendFromInternal = this.isSendFromInternalCompatible ? this.inputFormGroup.controls.sendFromInternal.value : null;
if (!sendFromInternal && !refundAddress) {
this.stepper.selected?.stepControl.setErrors({ Invalid: true });
this.flgEditable = true;
return;
}
this.boltzService.swapIn(
this.inputFormGroup.controls.amount.value,
sendFromInternal,
refundAddress
).pipe(takeUntil(this.unSubs[2])).
subscribe({
next: (swapStatus: CreateSwapResponse) => {
this.swapStatus = swapStatus;
@ -156,7 +191,11 @@ 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 | Send from Internal Wallet: ' + (this.inputFormGroup.controls.sendFromInternal.value ? 'Yes' : 'No');
let summary = 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');
if (!this.inputFormGroup.controls.sendFromInternal.value && this.inputFormGroup.controls.refundAddress.value) {
summary += ' | Refund Address: ' + this.inputFormGroup.controls.refundAddress.value;
}
this.inputFormLabel = summary;
} 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');
}
@ -201,7 +240,12 @@ export class SwapModalComponent implements OnInit, AfterViewInit, OnDestroy {
onRestart() {
this.stepper.reset();
this.flgEditable = true;
this.inputFormGroup.reset({ amount: this.serviceInfo.limits?.minimal, acceptZeroConf: false, sendFromInternal: true });
this.inputFormGroup.reset({
amount: this.serviceInfo.limits?.minimal,
acceptZeroConf: false,
sendFromInternal: true,
refundAddress: ''
});
this.statusFormGroup.reset();
this.addressFormGroup.reset({ addressType: 'local', address: '' });
this.addressFormGroup.controls.address.disable();

View file

@ -47,7 +47,7 @@ export class ExperimentalSettingsComponent implements OnInit, OnDestroy {
this.dataService.listConfigs().pipe(takeUntil(this.unSubs[0])).subscribe({
next: (res: any) => {
this.logger.info('Received List Configs: ' + JSON.stringify(res));
this.features[1].enabled = !!res['experimental-dual-fund'];
this.features[1].enabled = !!res['configs']['experimental-dual-fund']['set'];
}, error: (err) => {
this.logger.error('List Configs Error: ' + JSON.stringify(err));
this.features[1].enabled = false;

View file

@ -85,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, sendFromInternal: boolean) {
const requestBody = { amount: amount, sendFromInternal: sendFromInternal };
swapIn(amount: number, sendFromInternal: boolean, refundAddress?: string) {
const requestBody = { amount: amount, sendFromInternal: sendFromInternal, refundAddress };
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)));
}

View file

@ -16,7 +16,7 @@ export const SECS_IN_YEAR = 31536000;
export const DEFAULT_INVOICE_EXPIRY = HOUR_SECONDS * 24 * 7;
export const VERSION = '0.15.4-beta';
export const VERSION = '0.15.5-beta';
export const API_URL = isDevMode() ? 'http://localhost:3000/rtl/api' : './api';