Parse & display liquidity ads on node page

This commit is contained in:
Mononaut 2022-11-04 23:24:44 -06:00
parent 010e9f2bb1
commit c2ab0bc715
No known key found for this signature in database
GPG key ID: A3F058E41374C04E
4 changed files with 151 additions and 14 deletions

View file

@ -0,0 +1,31 @@
export interface ILiquidityAd {
funding_weight: number;
lease_fee_basis: number; // lease fee rate in parts-per-thousandth
lease_fee_base_sat: number; // fixed lease fee in sats
channel_fee_max_rate: number; // max routing fee rate in parts-per-thousandth
channel_fee_max_base: number; // max routing base fee in milli-sats
compact_lease?: string;
}
export function parseLiquidityAdHex(compact_lease: string): ILiquidityAd | false {
if (!compact_lease || compact_lease.length < 20 || compact_lease.length > 28) {
return false;
}
try {
const liquidityAd: ILiquidityAd = {
funding_weight: parseInt(compact_lease.slice(0, 4), 16),
lease_fee_basis: parseInt(compact_lease.slice(4, 8), 16),
channel_fee_max_rate: parseInt(compact_lease.slice(8, 12), 16),
lease_fee_base_sat: parseInt(compact_lease.slice(12, 20), 16),
channel_fee_max_base: compact_lease.length > 20 ? parseInt(compact_lease.slice(20), 16) : 0,
}
if (Object.values(liquidityAd).reduce((valid: boolean, value: number): boolean => (valid && !isNaN(value) && value >= 0), true)) {
liquidityAd.compact_lease = compact_lease;
return liquidityAd;
} else {
return false;
}
} catch (err) {
return false;
}
}

View file

@ -127,19 +127,84 @@
<div *ngIf="hasDetails" [hidden]="!showDetails" id="details" class="details mt-3">
<div class="box">
<h5 class="mb-3" i18n="node.tlv.records">TLV extension records</h5>
<div class="row">
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr *ngFor="let recordItem of node.custom_records | keyvalue">
<td class="tlv-type">{{ recordItem.key }}</td>
<td class="tlv-payload">{{ recordItem.value }}</td>
</tr>
</tbody>
</table>
<ng-template [ngIf]="liquidityAd">
<div class="detail-section">
<h5 class="mb-3" i18n="node.liquidity-ad">Liquidity ad</h5>
<div class="row">
<div class="col-md">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="label" i18n="liquidity-ad.lease-fee-rate|Liquidity ad lease fee rate">Lease fee rate</td>
<td>
<span class="d-inline-block">
{{ liquidityAd.lease_fee_basis !== null ? ((liquidityAd.lease_fee_basis * 1000) | amountShortener : 2 : undefined : true) : '-' }} <span class="symbol">ppm {{ liquidityAd.lease_fee_basis !== null ? '(' + (liquidityAd.lease_fee_basis / 10 | amountShortener : 2 : undefined : true) + '%)' : '' }}</span>
</span>
</td>
</tr>
<tr>
<td class="label" i18n="liquidity-ad.lease-base-fee">Lease base fee</td>
<td>
<app-sats [valueOverride]="liquidityAd.lease_fee_base_sat === null ? '- ' : undefined" [satoshis]="liquidityAd.lease_fee_base_sat"></app-sats>
</td>
</tr>
<tr>
<td class="label" i18n="liquidity-ad.funding-weight">Funding weight</td>
<td [innerHTML]="'&lrm;' + (liquidityAd.funding_weight | wuBytes: 2)"></td>
</tr>
</tbody>
</table>
</div>
<div class="col-md">
<table class="table table-borderless table-striped">
<tbody>
<tr>
<td class="label" i18n="liquidity-ad.channel-fee-rate|Liquidity ad channel fee rate">Channel fee rate</td>
<td>
<span class="d-inline-block">
{{ liquidityAd.channel_fee_max_rate !== null ? ((liquidityAd.channel_fee_max_rate * 1000) | amountShortener : 2 : undefined : true) : '-' }} <span class="symbol">ppm {{ liquidityAd.channel_fee_max_rate !== null ? '(' + (liquidityAd.channel_fee_max_rate / 10 | amountShortener : 2 : undefined : true) + '%)' : '' }}</span>
</span>
</td>
</tr>
<tr>
<td class="label" i18n="liquidity-ad.channel-base-fee">Channel base fee</td>
<td>
<span *ngIf="liquidityAd.channel_fee_max_base !== null">
{{ liquidityAd.channel_fee_max_base | amountShortener : 0 }}
<span class="symbol" i18n="shared.m-sats">mSats</span>
</span>
<span *ngIf="liquidityAd.channel_fee_max_base === null">
-
</span>
</td>
</tr>
<tr>
<td class="label" i18n="liquidity-ad.compact-lease">Compact lease</td>
<td class="compact-lease">{{ liquidityAd.compact_lease }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</ng-template>
<ng-template [ngIf]="tlvRecords?.length">
<div class="detail-section">
<h5 class="mb-3" i18n="node.tlv.records">TLV extension records</h5>
<div class="row">
<div class="col">
<table class="table table-borderless table-striped">
<tbody>
<tr *ngFor="let recordItem of tlvRecords">
<td class="tlv-type">{{ recordItem.type }}</td>
<td class="tlv-payload">{{ recordItem.payload }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</ng-template>
</div>
</div>

View file

@ -73,17 +73,31 @@ app-fiat {
};
}
.details tbody {
font-size: 12px;
.details {
.detail-section {
margin-bottom: 1.5rem;
&:last-child {
margin-bottom: 0;
}
}
.tlv-type {
font-size: 12px;
color: #ffffff66;
}
.tlv-payload {
font-size: 12px;
width: 100%;
word-break: break-all;
white-space: normal;
font-family: "Courier New", Courier, monospace;
}
.compact-lease {
word-break: break-all;
white-space: normal;
font-family: "Courier New", Courier, monospace;
}
}

View file

@ -5,6 +5,12 @@ import { catchError, map, switchMap, tap } from 'rxjs/operators';
import { SeoService } from '../../services/seo.service';
import { LightningApiService } from '../lightning-api.service';
import { GeolocationData } from '../../shared/components/geolocation/geolocation.component';
import { ILiquidityAd, parseLiquidityAdHex } from './liquidity-ad';
interface CustomRecord {
type: string;
payload: string;
}
@Component({
selector: 'app-node',
@ -26,6 +32,8 @@ export class NodeComponent implements OnInit {
torSocketCount = 0;
hasDetails = false;
showDetails = false;
liquidityAd: ILiquidityAd;
tlvRecords: CustomRecord[];
constructor(
private lightningApiService: LightningApiService,
@ -38,6 +46,8 @@ export class NodeComponent implements OnInit {
.pipe(
switchMap((params: ParamMap) => {
this.publicKey = params.get('public_key');
this.tlvRecords = [];
this.liquidityAd = null;
return this.lightningApiService.getNode$(params.get('public_key'));
}),
map((node) => {
@ -83,6 +93,23 @@ export class NodeComponent implements OnInit {
}),
tap((node) => {
this.hasDetails = Object.keys(node.custom_records).length > 0;
for (const [type, payload] of Object.entries(node.custom_records)) {
if (typeof payload !== 'string') {
break;
}
let parsed = false;
if (type === '1') {
const ad = parseLiquidityAdHex(payload);
if (ad) {
parsed = true;
this.liquidityAd = ad;
}
}
if (!parsed) {
this.tlvRecords.push({ type, payload });
}
}
}),
catchError(err => {
this.error = err;