diff --git a/frontend/src/app/lightning/node/liquidity-ad.ts b/frontend/src/app/lightning/node/liquidity-ad.ts
new file mode 100644
index 000000000..4b0e04b0b
--- /dev/null
+++ b/frontend/src/app/lightning/node/liquidity-ad.ts
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/lightning/node/node.component.html b/frontend/src/app/lightning/node/node.component.html
index 9c8fc6149..858aa9b48 100644
--- a/frontend/src/app/lightning/node/node.component.html
+++ b/frontend/src/app/lightning/node/node.component.html
@@ -127,19 +127,84 @@
-
TLV extension records
-
-
-
-
-
- {{ recordItem.key }} |
- {{ recordItem.value }} |
-
-
-
+
+
+
Liquidity ad
+
+
+
+
+
+ Lease fee rate |
+
+
+ {{ liquidityAd.lease_fee_basis !== null ? ((liquidityAd.lease_fee_basis * 1000) | amountShortener : 2 : undefined : true) : '-' }} ppm {{ liquidityAd.lease_fee_basis !== null ? '(' + (liquidityAd.lease_fee_basis / 10 | amountShortener : 2 : undefined : true) + '%)' : '' }}
+
+ |
+
+
+ Lease base fee |
+
+
+ |
+
+
+ Funding weight |
+ |
+
+
+
+
+
+
+
+
+ Channel fee rate |
+
+
+ {{ liquidityAd.channel_fee_max_rate !== null ? ((liquidityAd.channel_fee_max_rate * 1000) | amountShortener : 2 : undefined : true) : '-' }} ppm {{ liquidityAd.channel_fee_max_rate !== null ? '(' + (liquidityAd.channel_fee_max_rate / 10 | amountShortener : 2 : undefined : true) + '%)' : '' }}
+
+ |
+
+
+ Channel base fee |
+
+
+ {{ liquidityAd.channel_fee_max_base | amountShortener : 0 }}
+ mSats
+
+
+ -
+
+ |
+
+
+ Compact lease |
+ {{ liquidityAd.compact_lease }} |
+
+
+
+
+
-
+
+
+
+
TLV extension records
+
+
+
+
+
+ {{ recordItem.type }} |
+ {{ recordItem.payload }} |
+
+
+
+
+
+
+
diff --git a/frontend/src/app/lightning/node/node.component.scss b/frontend/src/app/lightning/node/node.component.scss
index fe9737b85..d54b1851b 100644
--- a/frontend/src/app/lightning/node/node.component.scss
+++ b/frontend/src/app/lightning/node/node.component.scss
@@ -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;
+ }
}
diff --git a/frontend/src/app/lightning/node/node.component.ts b/frontend/src/app/lightning/node/node.component.ts
index 161a53e37..ec2edd252 100644
--- a/frontend/src/app/lightning/node/node.component.ts
+++ b/frontend/src/app/lightning/node/node.component.ts
@@ -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;