Funder Policy Update

Funder Policy Update
pull/1030/head
Shahana Farooqui 2 years ago committed by ShahanaFarooqui
parent ee096d7e1b
commit 519c18bafc

@ -120,3 +120,22 @@ export const listForwards = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const funderUpdatePolicy = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting or Updating Funder Policy..' });
options = common.getOptions(req);
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/funderUpdate';
if (req.body && req.body.policy) {
options.body = req.body;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

@ -1,12 +1,13 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards } from '../../controllers/cln/channels.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy } from '../../controllers/cln/channels.js';
const router = Router();
router.get('/listChannels', isAuthenticated, listChannels);
router.post('/', isAuthenticated, openChannel);
router.post('/setChannelFee', isAuthenticated, setChannelFee);
router.delete('/:channelId', isAuthenticated, closeChannel);
router.get('/localremotebalance', isAuthenticated, getLocalRemoteBalance);
router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance);
router.get('/listForwards', isAuthenticated, listForwards);
router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy);
export default router;

@ -106,3 +106,21 @@ export const listForwards = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};
export const funderUpdatePolicy = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting or Updating Funder Policy..' });
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/funderUpdate';
if (req.body && req.body.policy) {
options.body = req.body;
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body });
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);
return res.status(err.statusCode).json({ message: err.message, error: err.error });
});
};

@ -1,7 +1,7 @@
import exprs from 'express';
const { Router } = exprs;
import { isAuthenticated } from '../../utils/authCheck.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards } from '../../controllers/cln/channels.js';
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy } from '../../controllers/cln/channels.js';
const router = Router();
@ -10,7 +10,9 @@ router.post('/', isAuthenticated, openChannel);
router.post('/setChannelFee', isAuthenticated, setChannelFee);
router.delete('/:channelId', isAuthenticated, closeChannel);
router.get('/localremotebalance', isAuthenticated, getLocalRemoteBalance);
router.get('/localRemoteBalance', isAuthenticated, getLocalRemoteBalance);
router.get('/listForwards', isAuthenticated, listForwards);
router.post('/funderUpdate', isAuthenticated, funderUpdatePolicy);
export default router;

@ -198,7 +198,7 @@ export class CLNEffects implements OnDestroy {
ofType(CLNActions.FETCH_LOCAL_REMOTE_BALANCE_CLN),
mergeMap(() => {
this.store.dispatch(updateCLAPICallStatus({ payload: { action: 'FetchLocalRemoteBalance', status: APICallStatusEnum.INITIATED } }));
return this.httpClient.get<LocalRemoteBalance>(this.CHILD_API_URL + environment.CHANNELS_API + '/localremotebalance');
return this.httpClient.get<LocalRemoteBalance>(this.CHILD_API_URL + environment.CHANNELS_API + '/localRemoteBalance');
}),
map((lrBalance) => {
this.logger.info(lrBalance);

@ -9,7 +9,7 @@
<span class="page-title">Features</span>
</div>
<mat-accordion>
<mat-expansion-panel [expanded]="false" class="flat-expansion-panel my-1" *ngFor="let feature of features; index as i">
<mat-expansion-panel [expanded]="false" class="flat-expansion-panel my-1" *ngFor="let feature of features; index as i" (opened)="onPanelOpen(i)">
<mat-expansion-panel-header>
<mat-panel-title fxFlex="100" fxLayoutAlign="space-between center">
<h4 class="font-bold-500">{{feature.name}}</h4>
@ -37,6 +37,53 @@
</div>
<mat-slide-toggle autoFocus class="my-1" tabindex="1" color="primary" name="enableOfr" [(ngModel)]="enableOffers" (change)="onUpdateFeature()">Enable Offers {{enableOffers ? '(You can find Offers under Lightning -> Transactions -> Offers)' : ''}}</mat-slide-toggle>
</form>
<form *ngIf="i === 1" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" class="page-sub-title-container" #form="ngForm">
<div fxFlex="100" fxLayout="row" class="alert alert-warn">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<span>These config changes should be configured permanently via the config file on your CLN node otherwise the policy would need to be configured again, if your node restarts.</span>
</div>
<div fxLayout="column" fxLayout.gt-sm="row" fxFlex="100" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="49" fxLayoutAlign="start end">
<mat-select autofocus tabindex="1" [(ngModel)]="selPolicyType" (selectionChange)="policyMod=null" placeholder="Policy" name="policy">
<mat-option *ngFor="let policyType of policyTypes" [value]="policyType">
{{policyType.id | titlecase}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="49">
<input matInput [(ngModel)]="policyMod" [placeholder]="selPolicyType.placeholder" type="number" [step]="selPolicyType.id === 'fixed' ? 1000 : 10" [min]="selPolicyType.min" [max]="selPolicyType.max" tabindex="2" required name="plcMod" #plcMod="ngModel">
<mat-hint>{{selPolicyType.placeholder}} should be between {{selPolicyType.min}} and {{selPolicyType.max}}</mat-hint>
<mat-error *ngIf="!policyMod">{{selPolicyType.placeholder}} is required.</mat-error>
<mat-error *ngIf="policyMod < selPolicyType.min">{{selPolicyType.placeholder}} must be greater than or equal to {{selPolicyType.min}}.</mat-error>
<mat-error *ngIf="policyMod > selPolicyType.max">{{selPolicyType.placeholder}} must be less than or equal to {{selPolicyType.max}}.</mat-error>
</mat-form-field>
</div>
<div fxLayout="column" fxLayout.gt-sm="row" fxFlex="100" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="49">
<input matInput [(ngModel)]="leaseFeeBaseSat" placeholder="Lease Base Fee (Sats)" type="number" step="1000" min="0" tabindex="3" required name="leaseFeeBaseSat">
<mat-error *ngIf="!leaseFeeBaseSat">Lease base fee is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="49">
<input matInput [(ngModel)]="leaseFeeBasis" placeholder="Lease Base Basis (bps)" type="number" step="10" min="0" tabindex="4" required name="leaseFeeBasis">
<mat-error *ngIf="!leaseFeeBasis">Lease base basis is required.</mat-error>
</mat-form-field>
</div>
<div fxLayout="column" fxLayout.gt-sm="row" fxFlex="100" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="49">
<input matInput [(ngModel)]="channelFeeMaxBaseSat" placeholder="Max Channel Routing Base Fee (Sats)" type="number" step="1" min="0" tabindex="5" required name="channelFeeMaxBaseSat">
<mat-error *ngIf="!channelFeeMaxBaseSat">Max channel routing base fee is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="49">
<input matInput [(ngModel)]="channelFeeMaxProportional" placeholder="Max Channel Routing Fee Rate (ppm)" type="number" step="10" min="0" tabindex="6" required name="channelFeeMaxProportional">
<mat-error *ngIf="!channelFeeMaxProportional">Max channel routing fee rate is required.</mat-error>
</mat-form-field>
</div>
<h4 *ngIf="flgUpdateCalled" fxLayoutAlign="start" class="font-bold-500 mt-2">{{(updateMsg.error && updateMsg.error !== '') ? (('Error: ' + updateMsg.error) || 'Unknown Error') : ((updateMsg) || 'Successfully Updated the Funding Policy!')}}</h4>
<div fxLayout="row" class="my-1">
<button class="mr-1" mat-stroked-button color="primary" (click)="onResetPolicy()" tabindex="7">Reset</button>
<button mat-flat-button color="primary" (click)="onUpdateFundingPolicy()" tabindex="8">Update</button>
</div>
</form>
</div>
</mat-expansion-panel>
</mat-accordion>

@ -12,7 +12,11 @@ import { updateServiceSettings } from '../../../../store/rtl.actions';
import { setChildNodeSettingsLND } from '../../../../lnd/store/lnd.actions';
import { setChildNodeSettingsCL } from '../../../../cln/store/cln.actions';
import { setChildNodeSettingsECL } from '../../../../eclair/store/ecl.actions';
import { ServicesEnum, UI_MESSAGES } from '../../../services/consts-enums-functions';
import { DataService } from '../../../services/data.service';
import { ServicesEnum, UI_MESSAGES, LADS_POLICY } from '../../../services/consts-enums-functions';
import { balance } from '../../../../cln/store/cln.selector';
import { Balance, FunderPolicy } from '../../../models/clModels';
import { ApiCallStatusPayload } from '../../../models/apiCallsPayload';
@Component({
selector: 'rtl-experimental-settings',
@ -24,21 +28,36 @@ export class ExperimentalSettingsComponent implements OnInit, OnDestroy {
public faInfoCircle = faInfoCircle;
public faExclamationTriangle = faExclamationTriangle;
public faCode = faCode;
public features = [{ name: 'Offers', enabled: false }];
public features = [{ name: 'Offers', enabled: false }, { name: 'Channel Funding Policy', enabled: false }];
public enableOffers = false;
public selNode: ConfigSettingsNode;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
public fundingPolicy: FunderPolicy = null;
public policyTypes = LADS_POLICY;
public selPolicyType = LADS_POLICY[0];
public policyMod: number;
public leaseFeeBaseSat: number;
public leaseFeeBasis: number;
public channelFeeMaxBaseSat: number;
public channelFeeMaxProportional: number;
public flgUpdateCalled = false;
public updateMsg = '';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<RTLState>) { }
constructor(private logger: LoggerService, private store: Store<RTLState>, private dataService: DataService) { }
ngOnInit() {
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[0])).
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[1])).
subscribe((selNode) => {
this.selNode = selNode;
this.enableOffers = this.selNode.settings.enableOffers;
this.features[0].enabled = this.enableOffers;
this.logger.info(this.selNode);
});
this.store.select(balance).pipe(takeUntil(this.unSubs[2])).
subscribe((balanceSeletor: { balance: Balance, apiCallStatus: ApiCallStatusPayload }) => {
this.policyTypes[2].max = balanceSeletor.balance.totalBalance || 1000;
});
}
onUpdateFeature(): boolean | void {
@ -66,6 +85,52 @@ export class ExperimentalSettingsComponent implements OnInit, OnDestroy {
}));
}
onPanelOpen(i) {
if (i === 1 && !this.fundingPolicy) {
this.dataService.getOrUpdateFunderPolicy().pipe(takeUntil(this.unSubs[0])).subscribe((res: any) => {
this.logger.info('Received Funder Update Policy: ' + JSON.stringify(res));
this.fundingPolicy = res;
if (this.fundingPolicy.policy) {
this.selPolicyType = LADS_POLICY.find((policy) => policy.id === this.fundingPolicy.policy);
}
this.policyMod = this.fundingPolicy.policy_mod || this.fundingPolicy.policy_mod === 0 ? this.fundingPolicy.policy_mod : null;
this.leaseFeeBaseSat = this.fundingPolicy.lease_fee_base_msat ? this.fundingPolicy.lease_fee_base_msat / 1000 : this.fundingPolicy.lease_fee_base_msat === 0 ? 0 : null;
this.leaseFeeBasis = this.fundingPolicy.lease_fee_basis || this.fundingPolicy.lease_fee_basis === 0 ? this.fundingPolicy.lease_fee_basis : null;
this.channelFeeMaxBaseSat = this.fundingPolicy.channel_fee_max_base_msat ? this.fundingPolicy.channel_fee_max_base_msat / 1000 : this.fundingPolicy.channel_fee_max_base_msat === 0 ? 0 : null;
this.channelFeeMaxProportional = this.fundingPolicy.channel_fee_max_proportional_thousandths || this.fundingPolicy.channel_fee_max_proportional_thousandths === 0 ? this.fundingPolicy.channel_fee_max_proportional_thousandths : null;
});
}
}
onUpdateFundingPolicy() {
this.flgUpdateCalled = false;
this.updateMsg = '';
this.dataService.getOrUpdateFunderPolicy(this.selPolicyType.id, this.policyMod, this.leaseFeeBaseSat, this.leaseFeeBasis, this.channelFeeMaxBaseSat * 1000, this.channelFeeMaxProportional).
pipe(takeUntil(this.unSubs[3])).
subscribe({
next: (updatePolicyRes: any) => {
this.logger.info(updatePolicyRes);
this.fundingPolicy = updatePolicyRes;
this.onResetPolicy();
this.updateMsg = 'Compact Lease: ' + updatePolicyRes.compact_lease;
}, error: (err) => {
this.logger.error(err);
this.updateMsg = JSON.stringify(err.error.error.message);
}
});
}
onResetPolicy() {
this.flgUpdateCalled = false;
this.updateMsg = '';
this.selPolicyType = LADS_POLICY[0];
this.policyMod = this.fundingPolicy.policy_mod !== 0 && !this.fundingPolicy.policy_mod ? null : this.fundingPolicy.policy_mod;
this.leaseFeeBaseSat = this.fundingPolicy.lease_fee_base_msat !== 0 && !this.fundingPolicy.lease_fee_base_msat ? null : this.fundingPolicy.lease_fee_base_msat / 1000;
this.leaseFeeBasis = this.fundingPolicy.lease_fee_basis !== 0 && !this.fundingPolicy.lease_fee_basis ? null : this.fundingPolicy.lease_fee_basis;
this.channelFeeMaxBaseSat = this.fundingPolicy.channel_fee_max_base_msat !== 0 && !this.fundingPolicy.channel_fee_max_base_msat ? null : this.fundingPolicy.channel_fee_max_base_msat / 1000;
this.channelFeeMaxProportional = this.fundingPolicy.channel_fee_max_proportional_thousandths !== 0 && !this.fundingPolicy.channel_fee_max_proportional_thousandths ? null : this.fundingPolicy.channel_fee_max_proportional_thousandths;
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(null);

@ -454,3 +454,22 @@ export interface FetchInvoices {
index_offset?: number;
reversed?: boolean;
}
export interface FunderPolicy {
summary?: string;
policy?: string;
policy_mod?: number;
leases_only?: boolean;
min_their_funding_msat?: string;
max_their_funding_msat?: string;
per_channel_min_msat?: string;
per_channel_max_msat?: string;
reserve_tank_msat?: string;
fuzz_percent?: number;
fund_probability?: number;
lease_fee_base_msat?: number;
lease_fee_basis?: number;
funding_weight?: number;
channel_fee_max_base_msat?: number;
channel_fee_max_proportional_thousandths?: number;
}

@ -317,6 +317,7 @@ export const UI_MESSAGES = {
DISABLE_OFFER: 'Disabling Offer...',
CREATE_OFFER: 'Creating Offer...',
DELETE_OFFER_BOOKMARK: 'Deleting Bookmark...',
GET_FUNDER_POLICY: 'Getting Or Updating Funder Policy...',
LOG_OUT: 'Logging Out...'
};
@ -615,3 +616,9 @@ export enum NodeFeaturesLND {
'anchors-zero-fee-htlc-tx' = 'Anchor commitment type with zero fee HTLC transactions',
'amp' = 'AMP'
};
export const LADS_POLICY = [
{ id: 'match', placeholder: 'Policy Match (%age)', min: 0, max: 200 },
{ id: 'available', placeholder: 'Policy Available (%age)', min: 0, max: 100 },
{ id: 'fixed', placeholder: 'Fixed Policy (Sats)', min: 0, max: 100 }
];

@ -247,6 +247,21 @@ export class DataService implements OnDestroy {
}));
}
getOrUpdateFunderPolicy(policy?: any, policyMod?: any, leaseFeeBaseMsat?: any, leaseFeeBasis?: any, channelFeeMaxBaseMsat?: any, channelFeeMaxProportional?: any) {
const postParams = policy ? { policy: policy, policyMod: policyMod, leaseFeeBaseMsat: leaseFeeBaseMsat, leaseFeeBasis: leaseFeeBasis, channelFeeMaxBaseMsat: channelFeeMaxBaseMsat, channelFeeMaxProportional: channelFeeMaxProportional } : null;
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.GET_FUNDER_POLICY }));
return this.httpClient.post(this.childAPIUrl + environment.CHANNELS_API + '/funderUpdate', postParams).pipe(
takeUntil(this.unSubs[8]),
map((res) => {
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_FUNDER_POLICY }));
return res;
}), catchError((err) => {
this.handleErrorWithoutAlert('Funder Policy', UI_MESSAGES.GET_FUNDER_POLICY, err);
return throwError(() => new Error(this.extractErrorMessage(err)));
})
);
}
extractErrorMessage(err: any, genericErrorMessage: string = 'Unknown Error.') {
return this.titleCasePipe.transform((err.error && err.error.error && err.error.error.error && err.error.error.error.error && err.error.error.error.error.error && typeof err.error.error.error.error.error === 'string') ? err.error.error.error.error.error :
(err.error && err.error.error && err.error.error.error && err.error.error.error.error && typeof err.error.error.error.error === 'string') ? err.error.error.error.error :

Loading…
Cancel
Save