diff --git a/frontend/mempool-frontend-config.sample.json b/frontend/mempool-frontend-config.sample.json
index 231f1c7c8..938c71c1b 100644
--- a/frontend/mempool-frontend-config.sample.json
+++ b/frontend/mempool-frontend-config.sample.json
@@ -16,5 +16,6 @@
"MEMPOOL_WEBSITE_URL": "https://mempool.space",
"LIQUID_WEBSITE_URL": "https://liquid.network",
"BISQ_WEBSITE_URL": "https://bisq.markets",
- "MINING_DASHBOARD": true
+ "MINING_DASHBOARD": true,
+ "LIGHTNING": false
}
diff --git a/frontend/proxy.conf.local.js b/frontend/proxy.conf.local.js
index b1bf0656d..9be0ed770 100644
--- a/frontend/proxy.conf.local.js
+++ b/frontend/proxy.conf.local.js
@@ -102,6 +102,16 @@ if (configContent && configContent.BASE_MODULE === 'bisq') {
}
PROXY_CONFIG.push(...[
+ {
+ context: ['/lightning/api/v1/**'],
+ target: `http://localhost:8899`,
+ secure: false,
+ changeOrigin: true,
+ proxyTimeout: 30000,
+ pathRewrite: {
+ "^/lightning/api": "/api"
+ },
+ },
{
context: ['/api/v1/**'],
target: `http://localhost:8999`,
diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts
index b62f586a4..dfcc7aec7 100644
--- a/frontend/src/app/app-routing.module.ts
+++ b/frontend/src/app/app-routing.module.ts
@@ -20,6 +20,7 @@ import { AssetsFeaturedComponent } from './components/assets/assets-featured/ass
import { AssetsComponent } from './components/assets/assets.component';
import { AssetComponent } from './components/asset/asset.component';
import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component';
+import { LightningDashboardComponent } from './lightning/lightning-dashboard/lightning-dashboard.component';
let routes: Routes = [
{
@@ -96,6 +97,11 @@ let routes: Routes = [
path: 'api',
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
},
+ {
+ path: 'lightning',
+ component: LightningDashboardComponent,
+ loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule)
+ },
],
},
{
@@ -186,6 +192,11 @@ let routes: Routes = [
path: 'api',
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
},
+ {
+ path: 'lightning',
+ component: LightningDashboardComponent,
+ loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule)
+ },
],
},
{
@@ -273,6 +284,11 @@ let routes: Routes = [
path: 'api',
loadChildren: () => import('./docs/docs.module').then(m => m.DocsModule)
},
+ {
+ path: 'lightning',
+ component: LightningDashboardComponent,
+ loadChildren: () => import('./lightning/lightning.module').then(m => m.LightningModule)
+ },
],
},
{
diff --git a/frontend/src/app/components/change/change.component.html b/frontend/src/app/components/change/change.component.html
new file mode 100644
index 000000000..117a0c534
--- /dev/null
+++ b/frontend/src/app/components/change/change.component.html
@@ -0,0 +1,3 @@
+= 0 ? 'color: #42B747' : 'color: #B74242'">
+ {{ change >= 0 ? '+' : '' }}{{ change | amountShortener }}%
+
diff --git a/frontend/src/app/components/change/change.component.scss b/frontend/src/app/components/change/change.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/src/app/components/change/change.component.ts b/frontend/src/app/components/change/change.component.ts
new file mode 100644
index 000000000..1fba853c9
--- /dev/null
+++ b/frontend/src/app/components/change/change.component.ts
@@ -0,0 +1,21 @@
+import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
+
+@Component({
+ selector: 'app-change',
+ templateUrl: './change.component.html',
+ styleUrls: ['./change.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ChangeComponent implements OnChanges {
+ @Input() current: number;
+ @Input() previous: number;
+
+ change: number;
+
+ constructor() { }
+
+ ngOnChanges(): void {
+ this.change = (this.current - this.previous) / this.previous * 100;
+ }
+
+}
diff --git a/frontend/src/app/components/master-page/master-page.component.html b/frontend/src/app/components/master-page/master-page.component.html
index 59d82f128..bef1dba8c 100644
--- a/frontend/src/app/components/master-page/master-page.component.html
+++ b/frontend/src/app/components/master-page/master-page.component.html
@@ -35,15 +35,15 @@
+
+
+
-
-
-
diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts
new file mode 100644
index 000000000..3f36b55cd
--- /dev/null
+++ b/frontend/src/app/lightning/lightning-api.service.ts
@@ -0,0 +1,22 @@
+import { Injectable } from '@angular/core';
+import { HttpClient } from '@angular/common/http';
+import { Observable } from 'rxjs';
+
+const API_BASE_URL = '/lightning/api/v1';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class LightningApiService {
+ constructor(
+ private httpClient: HttpClient,
+ ) { }
+
+ getLatestStatistics$(): Observable {
+ return this.httpClient.get(API_BASE_URL + '/statistics/latest');
+ }
+
+ listTopNodes$(): Observable {
+ return this.httpClient.get(API_BASE_URL + '/nodes/top');
+ }
+}
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html
new file mode 100644
index 000000000..3c14763a5
--- /dev/null
+++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+ Nodes Statistics
+
+
+
+
+
+
+ Channels Statistics
+
+
+
+
+
+
+
+
+
+
Top Capacity Nodes
+
+
+
+
+
+
+
+
+
+
Most Connected Nodes
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.scss b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.scss
new file mode 100644
index 000000000..4fdadd57b
--- /dev/null
+++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.scss
@@ -0,0 +1,80 @@
+.dashboard-container {
+ padding-bottom: 60px;
+ text-align: center;
+ margin-top: 0.5rem;
+ @media (min-width: 992px) {
+ padding-bottom: 0px;
+ }
+ .col {
+ margin-bottom: 1.5rem;
+ }
+}
+
+.card {
+ background-color: #1d1f31;
+}
+
+.card-title {
+ font-size: 1rem;
+ color: #4a68b9;
+}
+.card-title > a {
+ color: #4a68b9;
+}
+
+.card-body {
+ padding: 1.25rem 1rem 0.75rem 1rem;
+}
+.card-body.pool-ranking {
+ padding: 1.25rem 0.25rem 0.75rem 0.25rem;
+}
+.card-text {
+ font-size: 22px;
+}
+
+
+.main-title {
+ position: relative;
+ color: #ffffff91;
+ margin-top: -13px;
+ font-size: 10px;
+ text-transform: uppercase;
+ font-weight: 500;
+ text-align: center;
+ padding-bottom: 3px;
+}
+
+.more-padding {
+ padding: 18px;
+}
+
+.card-wrapper {
+ .card {
+ height: auto !important;
+ }
+ .card-body {
+ display: flex;
+ flex: inherit;
+ text-align: center;
+ flex-direction: column;
+ justify-content: space-around;
+ padding: 22px 20px;
+ }
+}
+
+.skeleton-loader {
+ width: 100%;
+ display: block;
+ &:first-child {
+ max-width: 90px;
+ margin: 15px auto 3px;
+ }
+ &:last-child {
+ margin: 10px auto 3px;
+ max-width: 55px;
+ }
+}
+
+.card-text {
+ font-size: 22px;
+}
diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts
new file mode 100644
index 000000000..bb24d7a64
--- /dev/null
+++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts
@@ -0,0 +1,37 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { Observable } from 'rxjs';
+import { map, share } from 'rxjs/operators';
+import { LightningApiService } from '../lightning-api.service';
+
+@Component({
+ selector: 'app-lightning-dashboard',
+ templateUrl: './lightning-dashboard.component.html',
+ styleUrls: ['./lightning-dashboard.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class LightningDashboardComponent implements OnInit {
+ nodesByCapacity$: Observable;
+ nodesByChannels$: Observable;
+ statistics$: Observable;
+
+ constructor(
+ private lightningApiService: LightningApiService,
+ ) { }
+
+ ngOnInit(): void {
+ const sharedObservable = this.lightningApiService.listTopNodes$().pipe(share());
+
+ this.nodesByCapacity$ = sharedObservable
+ .pipe(
+ map((object) => object.topByCapacity),
+ );
+
+ this.nodesByChannels$ = sharedObservable
+ .pipe(
+ map((object) => object.topByChannels),
+ );
+
+ this.statistics$ = this.lightningApiService.getLatestStatistics$();
+ }
+
+}
diff --git a/frontend/src/app/lightning/lightning.module.ts b/frontend/src/app/lightning/lightning.module.ts
new file mode 100644
index 000000000..d98c0c910
--- /dev/null
+++ b/frontend/src/app/lightning/lightning.module.ts
@@ -0,0 +1,24 @@
+import { NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { SharedModule } from '../shared/shared.module';
+import { LightningDashboardComponent } from './lightning-dashboard/lightning-dashboard.component';
+import { LightningApiService } from './lightning-api.service';
+import { NodesListComponent } from './nodes-list/nodes-list.component';
+import { RouterModule } from '@angular/router';
+import { NodeStatisticsComponent } from './node-statistics/node-statistics.component';
+@NgModule({
+ declarations: [
+ LightningDashboardComponent,
+ NodesListComponent,
+ NodeStatisticsComponent,
+ ],
+ imports: [
+ CommonModule,
+ SharedModule,
+ RouterModule,
+ ],
+ providers: [
+ LightningApiService,
+ ]
+})
+export class LightningModule { }
diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.html b/frontend/src/app/lightning/node-statistics/node-statistics.component.html
new file mode 100644
index 000000000..4a3842673
--- /dev/null
+++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.html
@@ -0,0 +1,64 @@
+
+
+
+
Nodes
+
+
+ {{ statistics.latest.node_count | number }}
+
+
+
+
+
+
+
+
Channels
+
+
+ {{ statistics.latest.channel_count | number }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.scss b/frontend/src/app/lightning/node-statistics/node-statistics.component.scss
new file mode 100644
index 000000000..acc4578f3
--- /dev/null
+++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.scss
@@ -0,0 +1,85 @@
+.card-title {
+ color: #4a68b9;
+ font-size: 10px;
+ margin-bottom: 4px;
+ font-size: 1rem;
+}
+
+.card-text {
+ font-size: 22px;
+ span {
+ font-size: 11px;
+ position: relative;
+ top: -2px;
+ display: inline-flex;
+ }
+ .green-color {
+ display: block;
+ }
+}
+
+.fee-estimation-container {
+ display: flex;
+ justify-content: space-between;
+ @media (min-width: 376px) {
+ flex-direction: row;
+ }
+ .item {
+ max-width: 150px;
+ margin: 0;
+ width: -webkit-fill-available;
+ @media (min-width: 376px) {
+ margin: 0 auto 0px;
+ }
+ &:first-child{
+ display: none;
+ @media (min-width: 485px) {
+ display: block;
+ }
+ @media (min-width: 768px) {
+ display: none;
+ }
+ @media (min-width: 992px) {
+ display: block;
+ }
+ }
+ &:last-child {
+ margin-bottom: 0;
+ }
+ .card-text span {
+ color: #ffffff66;
+ font-size: 12px;
+ top: 0px;
+ }
+ .fee-text{
+ border-bottom: 1px solid #ffffff1c;
+ width: fit-content;
+ margin: auto;
+ line-height: 1.45;
+ padding: 0px 2px;
+ }
+ .fiat {
+ display: block;
+ font-size: 14px !important;
+ }
+ }
+}
+
+.loading-container {
+ min-height: 76px;
+}
+
+.card-text {
+ .skeleton-loader {
+ width: 100%;
+ display: block;
+ &:first-child {
+ max-width: 90px;
+ margin: 15px auto 3px;
+ }
+ &:last-child {
+ margin: 10px auto 3px;
+ max-width: 55px;
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/lightning/node-statistics/node-statistics.component.ts b/frontend/src/app/lightning/node-statistics/node-statistics.component.ts
new file mode 100644
index 000000000..c42720427
--- /dev/null
+++ b/frontend/src/app/lightning/node-statistics/node-statistics.component.ts
@@ -0,0 +1,18 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: 'app-node-statistics',
+ templateUrl: './node-statistics.component.html',
+ styleUrls: ['./node-statistics.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class NodeStatisticsComponent implements OnInit {
+ @Input() statistics$: Observable;
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/frontend/src/app/lightning/nodes-list/nodes-list.component.html b/frontend/src/app/lightning/nodes-list/nodes-list.component.html
new file mode 100644
index 000000000..441c2e0f1
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-list/nodes-list.component.html
@@ -0,0 +1,39 @@
+
+
+
+
+ | Alias |
+ Capacity |
+ Channels |
+
+
+
+ |
+ {{ node.alias }}
+ |
+
+
+ |
+
+ {{ node.channels_left + node.channels_right | number }}
+ |
+
+
+
+
+
+ |
+
+ |
+
+
+ |
+
+
+ |
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/lightning/nodes-list/nodes-list.component.scss b/frontend/src/app/lightning/nodes-list/nodes-list.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/frontend/src/app/lightning/nodes-list/nodes-list.component.ts b/frontend/src/app/lightning/nodes-list/nodes-list.component.ts
new file mode 100644
index 000000000..d6d05833e
--- /dev/null
+++ b/frontend/src/app/lightning/nodes-list/nodes-list.component.ts
@@ -0,0 +1,18 @@
+import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
+import { Observable } from 'rxjs';
+
+@Component({
+ selector: 'app-nodes-list',
+ templateUrl: './nodes-list.component.html',
+ styleUrls: ['./nodes-list.component.scss'],
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class NodesListComponent implements OnInit {
+ @Input() nodes$: Observable;
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+}
diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts
index 73c0e905d..0d0b05556 100644
--- a/frontend/src/app/services/state.service.ts
+++ b/frontend/src/app/services/state.service.ts
@@ -37,6 +37,7 @@ export interface Env {
LIQUID_WEBSITE_URL: string;
BISQ_WEBSITE_URL: string;
MINING_DASHBOARD: boolean;
+ LIGHTNING: boolean;
}
const defaultEnv: Env = {
@@ -60,7 +61,8 @@ const defaultEnv: Env = {
'MEMPOOL_WEBSITE_URL': 'https://mempool.space',
'LIQUID_WEBSITE_URL': 'https://liquid.network',
'BISQ_WEBSITE_URL': 'https://bisq.markets',
- 'MINING_DASHBOARD': true
+ 'MINING_DASHBOARD': true,
+ 'LIGHTNING': false,
};
@Injectable({
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index 1a799086b..a587c6934 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -40,7 +40,6 @@ import { BlockchainBlocksComponent } from '../components/blockchain-blocks/block
import { AmountComponent } from '../components/amount/amount.component';
import { RouterModule } from '@angular/router';
import { CapAddressPipe } from './pipes/cap-address-pipe/cap-address-pipe';
-
import { StartComponent } from '../components/start/start.component';
import { TransactionComponent } from '../components/transaction/transaction.component';
import { TransactionsListComponent } from '../components/transactions-list/transactions-list.component';
@@ -74,6 +73,7 @@ import { DataCyDirective } from '../data-cy.directive';
import { LoadingIndicatorComponent } from '../components/loading-indicator/loading-indicator.component';
import { IndexingProgressComponent } from '../components/indexing-progress/indexing-progress.component';
import { SvgImagesComponent } from '../components/svg-images/svg-images.component';
+import { ChangeComponent } from '../components/change/change.component';
@NgModule({
declarations: [
@@ -104,7 +104,6 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen
MempoolBlocksComponent,
BlockchainBlocksComponent,
AmountComponent,
-
AboutComponent,
MasterPageComponent,
BisqMasterPageComponent,
@@ -142,6 +141,7 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen
LoadingIndicatorComponent,
IndexingProgressComponent,
SvgImagesComponent,
+ ChangeComponent,
],
imports: [
CommonModule,
@@ -163,6 +163,7 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen
NoSanitizePipe,
ShortenStringPipe,
CapAddressPipe,
+ AmountShortenerPipe,
],
exports: [
RouterModule,
@@ -203,7 +204,6 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen
MempoolBlocksComponent,
BlockchainBlocksComponent,
AmountComponent,
-
StartComponent,
TransactionComponent,
BlockComponent,
@@ -237,6 +237,7 @@ import { SvgImagesComponent } from '../components/svg-images/svg-images.componen
LoadingIndicatorComponent,
IndexingProgressComponent,
SvgImagesComponent,
+ ChangeComponent,
]
})
export class SharedModule {
diff --git a/lightning-backend/mempool-config.sample.json b/lightning-backend/mempool-config.sample.json
index e45742042..4ad9ffdd1 100644
--- a/lightning-backend/mempool-config.sample.json
+++ b/lightning-backend/mempool-config.sample.json
@@ -2,7 +2,8 @@
"MEMPOOL": {
"NETWORK": "mainnet",
"BACKEND": "lnd",
- "HTTP_PORT": 8999,
+ "HTTP_PORT": 8899,
+ "API_URL_PREFIX": "/api/v1/",
"STDOUT_LOG_MIN_PRIORITY": "debug"
},
"SYSLOG": {
diff --git a/lightning-backend/src/api/lightning-api-abstract-factory.ts b/lightning-backend/src/api/lightning/lightning-api-abstract-factory.ts
similarity index 100%
rename from lightning-backend/src/api/lightning-api-abstract-factory.ts
rename to lightning-backend/src/api/lightning/lightning-api-abstract-factory.ts
diff --git a/lightning-backend/src/api/lightning-api-factory.ts b/lightning-backend/src/api/lightning/lightning-api-factory.ts
similarity index 89%
rename from lightning-backend/src/api/lightning-api-factory.ts
rename to lightning-backend/src/api/lightning/lightning-api-factory.ts
index 4dfb3ae03..e2a730650 100644
--- a/lightning-backend/src/api/lightning-api-factory.ts
+++ b/lightning-backend/src/api/lightning/lightning-api-factory.ts
@@ -1,4 +1,4 @@
-import config from '../config';
+import config from '../../config';
import { AbstractLightningApi } from './lightning-api-abstract-factory';
import LndApi from './lnd/lnd-api';
diff --git a/lightning-backend/src/api/lightning-api.interface.ts b/lightning-backend/src/api/lightning/lightning-api.interface.ts
similarity index 100%
rename from lightning-backend/src/api/lightning-api.interface.ts
rename to lightning-backend/src/api/lightning/lightning-api.interface.ts
diff --git a/lightning-backend/src/api/lnd/lnd-api.ts b/lightning-backend/src/api/lightning/lnd/lnd-api.ts
similarity index 94%
rename from lightning-backend/src/api/lnd/lnd-api.ts
rename to lightning-backend/src/api/lightning/lnd/lnd-api.ts
index edddc31c6..3388a04b3 100644
--- a/lightning-backend/src/api/lnd/lnd-api.ts
+++ b/lightning-backend/src/api/lightning/lnd/lnd-api.ts
@@ -2,8 +2,8 @@ import { AbstractLightningApi } from '../lightning-api-abstract-factory';
import { ILightningApi } from '../lightning-api.interface';
import * as fs from 'fs';
import * as lnService from 'ln-service';
-import config from '../../config';
-import logger from '../../logger';
+import config from '../../../config';
+import logger from '../../../logger';
class LndApi implements AbstractLightningApi {
private lnd: any;
diff --git a/lightning-backend/src/api/nodes/nodes.api.ts b/lightning-backend/src/api/nodes/nodes.api.ts
new file mode 100644
index 000000000..335ae61d2
--- /dev/null
+++ b/lightning-backend/src/api/nodes/nodes.api.ts
@@ -0,0 +1,42 @@
+import logger from '../../logger';
+import DB from '../../database';
+
+class NodesApi {
+ public async $getTopCapacityNodes(): Promise {
+ try {
+ const query = `SELECT nodes.*, nodes_stats.capacity_left, nodes_stats.capacity_right, nodes_stats.channels_left, nodes_stats.channels_right FROM nodes LEFT JOIN nodes_stats ON nodes_stats.public_key = nodes.public_key ORDER BY nodes_stats.capacity_left + nodes_stats.capacity_right DESC LIMIT 10`;
+ const [rows]: any = await DB.query(query);
+ return rows;
+ } catch (e) {
+ logger.err('$getTopCapacityNodes error: ' + (e instanceof Error ? e.message : e));
+ throw e;
+ }
+ }
+
+ public async $getTopChannelsNodes(): Promise {
+ try {
+ const query = `SELECT nodes.*, nodes_stats.capacity_left, nodes_stats.capacity_right, nodes_stats.channels_left, nodes_stats.channels_right FROM nodes LEFT JOIN nodes_stats ON nodes_stats.public_key = nodes.public_key ORDER BY nodes_stats.channels_left + nodes_stats.channels_right DESC LIMIT 10`;
+ const [rows]: any = await DB.query(query);
+ return rows;
+ } catch (e) {
+ logger.err('$getTopChannelsNodes error: ' + (e instanceof Error ? e.message : e));
+ throw e;
+ }
+ }
+
+ public async $getLatestStatistics(): Promise {
+ try {
+ const [rows]: any = await DB.query(`SELECT * FROM statistics ORDER BY id DESC LIMIT 1`);
+ const [rows2]: any = await DB.query(`SELECT * FROM statistics ORDER BY id DESC LIMIT 1 OFFSET 24`);
+ return {
+ latest: rows[0],
+ previous: rows2[0],
+ };
+ } catch (e) {
+ logger.err('$getTopChannelsNodes error: ' + (e instanceof Error ? e.message : e));
+ throw e;
+ }
+ }
+}
+
+export default new NodesApi();
diff --git a/lightning-backend/src/api/nodes/nodes.routes.ts b/lightning-backend/src/api/nodes/nodes.routes.ts
new file mode 100644
index 000000000..ac001f5dd
--- /dev/null
+++ b/lightning-backend/src/api/nodes/nodes.routes.ts
@@ -0,0 +1,35 @@
+import config from '../../config';
+import { Express, Request, Response } from 'express';
+import nodesApi from './nodes.api';
+class NodesRoutes {
+ constructor(app: Express) {
+ app
+ .get(config.MEMPOOL.API_URL_PREFIX + 'statistics/latest', this.$getGeneralStats)
+ .get(config.MEMPOOL.API_URL_PREFIX + 'nodes/top', this.$getTopNodes)
+ ;
+ }
+
+ private async $getGeneralStats(req: Request, res: Response) {
+ try {
+ const statistics = await nodesApi.$getLatestStatistics();
+ res.json(statistics);
+ } catch (e) {
+ res.status(500).send(e instanceof Error ? e.message : e);
+ }
+ }
+
+ private async $getTopNodes(req: Request, res: Response) {
+ try {
+ const topCapacityNodes = await nodesApi.$getTopCapacityNodes();
+ const topChannelsNodes = await nodesApi.$getTopChannelsNodes();
+ res.json({
+ topByCapacity: topCapacityNodes,
+ topByChannels: topChannelsNodes,
+ });
+ } catch (e) {
+ res.status(500).send(e instanceof Error ? e.message : e);
+ }
+ }
+}
+
+export default NodesRoutes;
diff --git a/lightning-backend/src/config.ts b/lightning-backend/src/config.ts
index 4e9e36246..48c237174 100644
--- a/lightning-backend/src/config.ts
+++ b/lightning-backend/src/config.ts
@@ -5,6 +5,7 @@ interface IConfig {
NETWORK: 'mainnet' | 'testnet' | 'signet';
BACKEND: 'lnd' | 'cln' | 'ldk';
HTTP_PORT: number;
+ API_URL_PREFIX: string;
STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
};
SYSLOG: {
@@ -33,6 +34,7 @@ const defaults: IConfig = {
'NETWORK': 'mainnet',
'BACKEND': 'lnd',
'HTTP_PORT': 8999,
+ 'API_URL_PREFIX': '/api/v1/',
'STDOUT_LOG_MIN_PRIORITY': 'debug',
},
'SYSLOG': {
diff --git a/lightning-backend/src/database-migration.ts b/lightning-backend/src/database-migration.ts
index 4690fa0e0..cac997ef7 100644
--- a/lightning-backend/src/database-migration.ts
+++ b/lightning-backend/src/database-migration.ts
@@ -76,6 +76,7 @@ class DatabaseMigration {
await this.$executeQuery(this.getCreateStatisticsQuery(), await this.$checkIfTableExists('statistics'));
await this.$executeQuery(this.getCreateNodesQuery(), await this.$checkIfTableExists('nodes'));
await this.$executeQuery(this.getCreateChannelsQuery(), await this.$checkIfTableExists('channels'));
+ await this.$executeQuery(this.getCreateNodesStatsQuery(), await this.$checkIfTableExists('nodes_stats'));
} catch (e) {
throw e;
}
@@ -204,28 +205,45 @@ class DatabaseMigration {
private getCreateChannelsQuery(): string {
return `CREATE TABLE IF NOT EXISTS channels (
id varchar(15) NOT NULL,
- capacity double unsigned NOT NULL,
+ capacity bigint(20) unsigned NOT NULL,
transaction_id varchar(64) NOT NULL,
transaction_vout int(11) NOT NULL,
- updated_at datetime NOT NULL,
+ updated_at datetime DEFAULT NULL,
node1_public_key varchar(66) NOT NULL,
- node1_base_fee_mtokens double unsigned NULL,
- node1_cltv_delta int(11) NULL,
- node1_fee_rate int(11) NULL,
- node1_is_disabled boolean NULL,
- node1_max_htlc_mtokens double unsigned NULL,
- node1_min_htlc_mtokens double unsigned NULL,
- node1_updated_at datetime NULL,
+ node1_base_fee_mtokens bigint(20) unsigned DEFAULT NULL,
+ node1_cltv_delta int(11) DEFAULT NULL,
+ node1_fee_rate bigint(11) DEFAULT NULL,
+ node1_is_disabled tinyint(1) DEFAULT NULL,
+ node1_max_htlc_mtokens bigint(20) unsigned DEFAULT NULL,
+ node1_min_htlc_mtokens bigint(20) unsigned DEFAULT NULL,
+ node1_updated_at datetime DEFAULT NULL,
node2_public_key varchar(66) NOT NULL,
- node2_base_fee_mtokens double unsigned NULL,
- node2_cltv_delta int(11) NULL,
- node2_fee_rate int(11) NULL,
- node2_is_disabled boolean NULL,
- node2_max_htlc_mtokens double unsigned NULL,
- node2_min_htlc_mtokens double unsigned NULL,
- node2_updated_at datetime NULL,
- CONSTRAINT PRIMARY KEY (id)
- ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
+ node2_base_fee_mtokens bigint(20) unsigned DEFAULT NULL,
+ node2_cltv_delta int(11) DEFAULT NULL,
+ node2_fee_rate bigint(11) DEFAULT NULL,
+ node2_is_disabled tinyint(1) DEFAULT NULL,
+ node2_max_htlc_mtokens bigint(20) unsigned DEFAULT NULL,
+ node2_min_htlc_mtokens bigint(20) unsigned DEFAULT NULL,
+ node2_updated_at datetime DEFAULT NULL,
+ PRIMARY KEY (id),
+ KEY node1_public_key (node1_public_key),
+ KEY node2_public_key (node2_public_key),
+ KEY node1_public_key_2 (node1_public_key,node2_public_key)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
+ }
+
+ private getCreateNodesStatsQuery(): string {
+ return `CREATE TABLE nodes_stats (
+ id int(11) unsigned NOT NULL AUTO_INCREMENT,
+ public_key varchar(66) NOT NULL DEFAULT '',
+ added date NOT NULL,
+ capacity_left bigint(11) unsigned DEFAULT NULL,
+ capacity_right bigint(11) unsigned DEFAULT NULL,
+ channels_left int(11) unsigned DEFAULT NULL,
+ channels_right int(11) unsigned DEFAULT NULL,
+ PRIMARY KEY (id),
+ KEY public_key (public_key)
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
}
}
diff --git a/lightning-backend/src/index.ts b/lightning-backend/src/index.ts
index a9e487ef5..d5e859f6e 100644
--- a/lightning-backend/src/index.ts
+++ b/lightning-backend/src/index.ts
@@ -1,13 +1,20 @@
import config from './config';
+import * as express from 'express';
+import * as http from 'http';
import logger from './logger';
import DB from './database';
+import { Express, Request, Response, NextFunction } from 'express';
import databaseMigration from './database-migration';
import statsUpdater from './tasks/stats-updater.service';
import nodeSyncService from './tasks/node-sync.service';
+import NodesRoutes from './api/nodes/nodes.routes';
logger.notice(`Mempool Server is running on port ${config.MEMPOOL.HTTP_PORT}`);
class LightningServer {
+ private server: http.Server | undefined;
+ private app: Express = express();
+
constructor() {
this.init();
}
@@ -18,6 +25,27 @@ class LightningServer {
statsUpdater.startService();
nodeSyncService.startService();
+
+ this.startServer();
+ }
+
+ startServer() {
+ this.app
+ .use((req: Request, res: Response, next: NextFunction) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ next();
+ })
+ .use(express.urlencoded({ extended: true }))
+ .use(express.text())
+ ;
+
+ this.server = http.createServer(this.app);
+
+ this.server.listen(config.MEMPOOL.HTTP_PORT, () => {
+ logger.notice(`Mempool Lightning is running on port ${config.MEMPOOL.HTTP_PORT}`);
+ });
+
+ const nodeRoutes = new NodesRoutes(this.app);
}
}
diff --git a/lightning-backend/src/tasks/node-sync.service.ts b/lightning-backend/src/tasks/node-sync.service.ts
index 8b5116be9..90e353028 100644
--- a/lightning-backend/src/tasks/node-sync.service.ts
+++ b/lightning-backend/src/tasks/node-sync.service.ts
@@ -1,8 +1,8 @@
import DB from '../database';
import logger from '../logger';
-import lightningApi from '../api/lightning-api-factory';
-import { ILightningApi } from '../api/lightning-api.interface';
+import lightningApi from '../api/lightning/lightning-api-factory';
+import { ILightningApi } from '../api/lightning/lightning-api.interface';
class NodeSyncService {
constructor() {}
diff --git a/lightning-backend/src/tasks/stats-updater.service.ts b/lightning-backend/src/tasks/stats-updater.service.ts
index 2946977bc..c1ab1b250 100644
--- a/lightning-backend/src/tasks/stats-updater.service.ts
+++ b/lightning-backend/src/tasks/stats-updater.service.ts
@@ -1,7 +1,7 @@
import DB from '../database';
import logger from '../logger';
-import lightningApi from '../api/lightning-api-factory';
+import lightningApi from '../api/lightning/lightning-api-factory';
class LightningStatsUpdater {
constructor() {}
@@ -19,12 +19,25 @@ class LightningStatsUpdater {
this.$logLightningStats();
}, 1000 * 60 * 60);
}, difference);
+
+ // this.$logNodeStatsDaily();
+ }
+
+ private async $logNodeStatsDaily() {
+ const query = `SELECT nodes.public_key, COUNT(DISTINCT c1.id) AS channels_count_left, COUNT(DISTINCT c2.id) AS channels_count_right, SUM(DISTINCT c1.capacity) AS channels_capacity_left, SUM(DISTINCT c2.capacity) AS channels_capacity_right FROM nodes LEFT JOIN channels AS c1 ON c1.node1_public_key = nodes.public_key LEFT JOIN channels AS c2 ON c2.node2_public_key = nodes.public_key GROUP BY nodes.public_key`;
+ const [nodes]: any = await DB.query(query);
+
+ for (const node of nodes) {
+ await DB.query(
+ `INSERT INTO nodes_stats(public_key, added, capacity_left, capacity_right, channels_left, channels_right) VALUES (?, NOW(), ?, ?, ?, ?)`,
+ [node.public_key, node.channels_capacity_left, node.channels_capacity_right, node.channels_count_left, node.channels_count_right]);
+ }
}
private async $logLightningStats() {
- const networkInfo = await lightningApi.$getNetworkInfo();
-
try {
+ const networkInfo = await lightningApi.$getNetworkInfo();
+
const query = `INSERT INTO statistics(
added,
channel_count,