Merge branch 'master' into nymkappa/feature/channel-map-color
This commit is contained in:
commit
56ec8b900c
@ -228,34 +228,75 @@ export class Common {
|
|||||||
return d.toISOString().split('T')[0] + ' ' + d.toTimeString().split(' ')[0];
|
return d.toISOString().split('T')[0] + ' ' + d.toTimeString().split(' ')[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
static formatSocket(publicKey: string, socket: {network: string, addr: string}): NodeSocket {
|
static findSocketNetwork(addr: string): {network: string | null, url: string} {
|
||||||
let network: string | null = null;
|
let network: string | null = null;
|
||||||
|
let url = addr.split('://')[1];
|
||||||
|
|
||||||
if (config.LIGHTNING.BACKEND === 'cln') {
|
if (!url) {
|
||||||
network = socket.network;
|
return {
|
||||||
} else if (config.LIGHTNING.BACKEND === 'lnd') {
|
network: null,
|
||||||
if (socket.addr.indexOf('onion') !== -1) {
|
url: addr,
|
||||||
if (socket.addr.split('.')[0].length >= 56) {
|
};
|
||||||
network = 'torv3';
|
}
|
||||||
} else {
|
|
||||||
network = 'torv2';
|
if (addr.indexOf('onion') !== -1) {
|
||||||
}
|
if (url.split('.')[0].length >= 56) {
|
||||||
} else if (socket.addr.indexOf('i2p') !== -1) {
|
network = 'torv3';
|
||||||
network = 'i2p';
|
|
||||||
} else {
|
} else {
|
||||||
const ipv = isIP(socket.addr.split(':')[0]);
|
network = 'torv2';
|
||||||
if (ipv === 4) {
|
|
||||||
network = 'ipv4';
|
|
||||||
} else if (ipv === 6) {
|
|
||||||
network = 'ipv6';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else if (addr.indexOf('i2p') !== -1) {
|
||||||
|
network = 'i2p';
|
||||||
|
} else if (addr.indexOf('ipv4') !== -1) {
|
||||||
|
const ipv = isIP(url.split(':')[0]);
|
||||||
|
if (ipv === 4) {
|
||||||
|
network = 'ipv4';
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
network: null,
|
||||||
|
url: addr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else if (addr.indexOf('ipv6') !== -1) {
|
||||||
|
url = url.split('[')[1].split(']')[0];
|
||||||
|
const ipv = isIP(url);
|
||||||
|
if (ipv === 6) {
|
||||||
|
const parts = addr.split(':');
|
||||||
|
network = 'ipv6';
|
||||||
|
url = `[${url}]:${parts[parts.length - 1]}`;
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
network: null,
|
||||||
|
url: addr,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
network: null,
|
||||||
|
url: addr,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
publicKey: publicKey,
|
|
||||||
network: network,
|
network: network,
|
||||||
addr: socket.addr,
|
url: url,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static formatSocket(publicKey: string, socket: {network: string, addr: string}): NodeSocket {
|
||||||
|
if (config.LIGHTNING.BACKEND === 'cln') {
|
||||||
|
return {
|
||||||
|
publicKey: publicKey,
|
||||||
|
network: socket.network,
|
||||||
|
addr: socket.addr,
|
||||||
|
};
|
||||||
|
} else /* if (config.LIGHTNING.BACKEND === 'lnd') */ {
|
||||||
|
const formatted = this.findSocketNetwork(socket.addr);
|
||||||
|
return {
|
||||||
|
publicKey: publicKey,
|
||||||
|
network: formatted.network,
|
||||||
|
addr: formatted.url,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -503,6 +503,18 @@ class NodesApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update node sockets
|
||||||
|
*/
|
||||||
|
public async $updateNodeSockets(publicKey: string, sockets: {network: string; addr: string}[]): Promise<void> {
|
||||||
|
const formattedSockets = (sockets.map(a => a.addr).join(',')) ?? '';
|
||||||
|
try {
|
||||||
|
await DB.query(`UPDATE nodes SET sockets = ? WHERE public_key = ?`, [formattedSockets, publicKey]);
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot update node sockets for ${publicKey}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set all nodes not in `nodesPubkeys` as inactive (status = 0)
|
* Set all nodes not in `nodesPubkeys` as inactive (status = 0)
|
||||||
*/
|
*/
|
||||||
|
@ -27,7 +27,7 @@ class StatisticsApi {
|
|||||||
public async $getLatestStatistics(): Promise<any> {
|
public async $getLatestStatistics(): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const [rows]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY added DESC LIMIT 1`);
|
const [rows]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY added DESC LIMIT 1`);
|
||||||
const [rows2]: any = await DB.query(`SELECT * FROM lightning_stats ORDER BY added DESC LIMIT 1 OFFSET 7`);
|
const [rows2]: any = await DB.query(`SELECT * FROM lightning_stats WHERE DATE(added) = DATE(NOW() - INTERVAL 7 DAY)`);
|
||||||
return {
|
return {
|
||||||
latest: rows[0],
|
latest: rows[0],
|
||||||
previous: rows2[0],
|
previous: rows2[0],
|
||||||
|
@ -57,6 +57,8 @@ class LightningStatsImporter {
|
|||||||
features: node.features,
|
features: node.features,
|
||||||
});
|
});
|
||||||
nodesInDb[node.pub_key] = node;
|
nodesInDb[node.pub_key] = node;
|
||||||
|
} else {
|
||||||
|
await nodesApi.$updateNodeSockets(node.pub_key, node.addresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
let hasOnion = false;
|
let hasOnion = false;
|
||||||
@ -369,7 +371,7 @@ class LightningStatsImporter {
|
|||||||
graph = JSON.parse(fileContent);
|
graph = JSON.parse(fileContent);
|
||||||
graph = await this.cleanupTopology(graph);
|
graph = await this.cleanupTopology(graph);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content`);
|
logger.debug(`Invalid topology file ${this.topologiesFolder}/${filename}, cannot parse the content. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,9 +421,10 @@ class LightningStatsImporter {
|
|||||||
const addressesParts = (node.addresses ?? '').split(',');
|
const addressesParts = (node.addresses ?? '').split(',');
|
||||||
const addresses: any[] = [];
|
const addresses: any[] = [];
|
||||||
for (const address of addressesParts) {
|
for (const address of addressesParts) {
|
||||||
|
const formatted = Common.findSocketNetwork(address);
|
||||||
addresses.push({
|
addresses.push({
|
||||||
network: '',
|
network: formatted.network,
|
||||||
addr: address
|
addr: formatted.url
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 1130px) {
|
@media (min-width: 1130px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 1130px) {
|
@media (min-width: 830px) and (max-width: 1130px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -39,7 +39,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup"
|
<form [formGroup]="radioGroupForm" class="formRadioGroup"
|
||||||
[class]="stateService.env.MINING_DASHBOARD ? 'mining' : ''" (click)="saveGraphPreference()">
|
[class]="(stateService.env.MINING_DASHBOARD || stateService.env.LIGHTNING) ? 'mining' : 'no-menu'" (click)="saveGraphPreference()">
|
||||||
<div *ngIf="!isMobile()" class="btn-group btn-group-toggle">
|
<div *ngIf="!isMobile()" class="btn-group btn-group-toggle">
|
||||||
<label ngbButtonLabel class="btn-primary btn-sm mr-2">
|
<label ngbButtonLabel class="btn-primary btn-sm mr-2">
|
||||||
<a [routerLink]="['/tv' | relativeUrl]" style="color: white" id="btn-tv">
|
<a [routerLink]="['/tv' | relativeUrl]" style="color: white" id="btn-tv">
|
||||||
|
@ -55,13 +55,19 @@
|
|||||||
.formRadioGroup.mining {
|
.formRadioGroup.mining {
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.formRadioGroup.no-menu {
|
||||||
|
@media (min-width: 991px) {
|
||||||
|
position: relative;
|
||||||
|
top: -33px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.loading{
|
.loading{
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<table class="table table-borderless" *ngIf="response.channels.length > 0">
|
<table class="table table-borderless" *ngIf="response.channels.length > 0" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||||
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let channel of response.channels; let i = index;">
|
<tr *ngFor="let channel of response.channels; let i = index;">
|
||||||
|
@ -14,6 +14,7 @@ import { LightningApiService } from '../lightning-api.service';
|
|||||||
export class ChannelsListComponent implements OnInit, OnChanges {
|
export class ChannelsListComponent implements OnInit, OnChanges {
|
||||||
@Input() publicKey: string;
|
@Input() publicKey: string;
|
||||||
@Output() channelsStatusChangedEvent = new EventEmitter<string>();
|
@Output() channelsStatusChangedEvent = new EventEmitter<string>();
|
||||||
|
@Output() loadingEvent = new EventEmitter<boolean>(false);
|
||||||
channels$: Observable<any>;
|
channels$: Observable<any>;
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -26,6 +27,7 @@ export class ChannelsListComponent implements OnInit, OnChanges {
|
|||||||
defaultStatus = 'open';
|
defaultStatus = 'open';
|
||||||
status = 'open';
|
status = 'open';
|
||||||
publicKeySize = 25;
|
publicKeySize = 25;
|
||||||
|
isLoading = false;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private lightningApiService: LightningApiService,
|
private lightningApiService: LightningApiService,
|
||||||
@ -56,6 +58,8 @@ export class ChannelsListComponent implements OnInit, OnChanges {
|
|||||||
)
|
)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((val) => {
|
tap((val) => {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.loadingEvent.emit(true);
|
||||||
if (typeof val === 'string') {
|
if (typeof val === 'string') {
|
||||||
this.status = val;
|
this.status = val;
|
||||||
this.page = 1;
|
this.page = 1;
|
||||||
@ -64,10 +68,12 @@ export class ChannelsListComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
this.channelsStatusChangedEvent.emit(this.status);
|
this.channelsStatusChangedEvent.emit(this.status);
|
||||||
return this.lightningApiService.getChannelsByNodeId$(this.publicKey, (this.page - 1) * this.itemsPerPage, this.status);
|
return this.lightningApiService.getChannelsByNodeId$(this.publicKey, (this.page - 1) * this.itemsPerPage, this.status);
|
||||||
}),
|
}),
|
||||||
map((response) => {
|
map((response) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.loadingEvent.emit(false);
|
||||||
return {
|
return {
|
||||||
channels: response.body,
|
channels: response.body,
|
||||||
totalItems: parseInt(response.headers.get('x-total-count'), 10)
|
totalItems: parseInt(response.headers.get('x-total-count'), 10)
|
||||||
|
@ -9,44 +9,44 @@
|
|||||||
<div class="fee-estimation-wrapper" *ngIf="statistics$ | async as statistics; else loadingReward">
|
<div class="fee-estimation-wrapper" *ngIf="statistics$ | async as statistics; else loadingReward">
|
||||||
|
|
||||||
<div class="fee-estimation-container" *ngIf="mode === 'avg'">
|
<div class="fee-estimation-container" *ngIf="mode === 'avg'">
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="ln.average-capacity">Avg Capacity</h5>
|
<h5 class="card-title" i18n="ln.average-capacity">Avg Capacity</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.avg_capacity || 0 | number: '1.0-0' }}
|
{{ statistics.latest?.avg_capacity || 0 | number: '1.0-0' }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">sats</span>
|
<span i18n="shared.sat-vbyte|sat/vB">sats</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.avg_capacity" [previous]="statistics.previous?.avg_capacity"></app-change>
|
<app-change [current]="statistics.latest?.avg_capacity" [previous]="statistics.previous?.avg_capacity"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="ln.average-feerate">Avg Fee Rate</h5>
|
<h5 class="card-title" i18n="ln.average-feerate">Avg Fee Rate</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="ln.average-feerate-desc"
|
<div class="card-text" i18n-ngbTooltip="ln.average-feerate-desc"
|
||||||
ngbTooltip="The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm"
|
ngbTooltip="The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm"
|
||||||
placement="bottom">
|
placement="bottom">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.avg_fee_rate || 0 | number: '1.0-0' }}
|
{{ statistics.latest?.avg_fee_rate || 0 | number: '1.0-0' }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">ppm</span>
|
<span i18n="shared.sat-vbyte|sat/vB">ppm</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.avg_fee_rate" [previous]="statistics.previous?.avg_fee_rate"></app-change>
|
<app-change [current]="statistics.latest?.avg_fee_rate" [previous]="statistics.previous?.avg_fee_rate"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="ln.average-basefee">Avg Base Fee</h5>
|
<h5 class="card-title" i18n="ln.average-basefee">Avg Base Fee</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="ln.average-basefee-desc"
|
<div class="card-text" i18n-ngbTooltip="ln.average-basefee-desc"
|
||||||
ngbTooltip="The average base fee charged by routing nodes, ignoring base fees > 5000ppm" placement="bottom">
|
ngbTooltip="The average base fee charged by routing nodes, ignoring base fees > 5000ppm" placement="bottom">
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.avg_base_fee_mtokens || 0 | number: '1.0-0' }}
|
{{ statistics.latest?.avg_base_fee_mtokens || 0 | number: '1.0-0' }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">msats</span>
|
<span i18n="shared.sat-vbyte|sat/vB">msats</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.avg_base_fee_mtokens" [previous]="statistics.previous?.avg_base_fee_mtokens"></app-change>
|
<app-change [current]="statistics.latest?.avg_base_fee_mtokens" [previous]="statistics.previous?.avg_base_fee_mtokens"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -55,43 +55,45 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="fee-estimation-container" *ngIf="mode === 'med'">
|
<div class="fee-estimation-container" *ngIf="mode === 'med'">
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="ln.median-capacity">Med Capacity</h5>
|
<h5 class="card-title" i18n="ln.median-capacity">Med Capacity</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.med_capacity || 0 | number: '1.0-0' }}
|
{{ statistics.latest?.med_capacity || 0 | number: '1.0-0' }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">sats</span>
|
<span i18n="shared.sat-vbyte|sat/vB">sats</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.med_capacity" [previous]="statistics.previous?.med_capacity"></app-change>
|
<app-change [current]="statistics.latest?.med_capacity" [previous]="statistics.previous?.med_capacity"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
|
||||||
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="ln.average-feerate">Med Fee Rate</h5>
|
<h5 class="card-title" i18n="ln.average-feerate">Med Fee Rate</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="ln.median-feerate-desc"
|
<div class="card-text" i18n-ngbTooltip="ln.median-feerate-desc"
|
||||||
ngbTooltip="The average fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm"
|
ngbTooltip="The median fee rate charged by routing nodes, ignoring fee rates > 0.5% or 5000ppm"
|
||||||
placement="bottom">
|
placement="bottom">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.med_fee_rate || 0 | number: '1.0-0' }}
|
{{ statistics.latest?.med_fee_rate || 0 | number: '1.0-0' }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">ppm</span>
|
<span i18n="shared.sat-vbyte|sat/vB">ppm</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.med_fee_rate" [previous]="statistics.previous?.med_fee_rate"></app-change>
|
<app-change [current]="statistics.latest?.med_fee_rate" [previous]="statistics.previous?.med_fee_rate"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
|
||||||
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="ln.median-basefee">Med Base Fee</h5>
|
<h5 class="card-title" i18n="ln.median-basefee">Med Base Fee</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="ln.median-basefee-desc"
|
<div class="card-text" i18n-ngbTooltip="ln.median-basefee-desc"
|
||||||
ngbTooltip="The median base fee charged by routing nodes, ignoring base fees > 5000ppm" placement="bottom">
|
ngbTooltip="The median base fee charged by routing nodes, ignoring base fees > 5000ppm" placement="bottom">
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.med_base_fee_mtokens || 0 | number: '1.0-0' }}
|
{{ statistics.latest?.med_base_fee_mtokens || 0 | number: '1.0-0' }}
|
||||||
<span i18n="shared.sat-vbyte|sat/vB">msats</span>
|
<span i18n="shared.sat-vbyte|sat/vB">msats</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.med_base_fee_mtokens" [previous]="statistics.previous?.med_base_fee_mtokens"></app-change>
|
<app-change [current]="statistics.latest?.med_base_fee_mtokens" [previous]="statistics.previous?.med_base_fee_mtokens"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fee-estimation-wrapper {
|
||||||
|
min-height: 77px;
|
||||||
|
}
|
||||||
|
|
||||||
.fee-estimation-container {
|
.fee-estimation-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -30,7 +34,10 @@
|
|||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
@media (min-width: 376px) {
|
@media (min-width: 376px) {
|
||||||
margin: 0 auto 0px;
|
margin: 0 auto 0px;
|
||||||
}
|
}
|
||||||
|
&.more-padding {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
&:first-child{
|
&:first-child{
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
@ -57,6 +64,9 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
|
&.no-border {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.fiat {
|
.fiat {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -1,76 +1,64 @@
|
|||||||
<div class="fee-estimation-wrapper" *ngIf="statistics$ | async as statistics; else loadingReward">
|
<div class="fee-estimation-wrapper" *ngIf="statistics$ | async as statistics; else loadingReward">
|
||||||
<div class="fee-estimation-container">
|
<div class="fee-estimation-container">
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="mining.average-fee">Capacity</h5>
|
<h5 class="card-title" i18n="lightning.capacity">Capacity</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.average-fee" ngbTooltip="Percentage change past week"
|
<div class="card-text" i18n-ngbTooltip="mining.percentage-change-last-week" ngbTooltip="Percentage change past week"
|
||||||
placement="bottom">
|
[disableTooltip]="!statistics.previous" placement="bottom">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
<app-amount [satoshis]="statistics.latest?.total_capacity" digitsInfo="1.2-2"></app-amount>
|
<app-amount [satoshis]="statistics.latest?.total_capacity" digitsInfo="1.2-2"></app-amount>
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.total_capacity" [previous]="statistics.previous?.total_capacity">
|
<app-change [current]="statistics.latest?.total_capacity" [previous]="statistics.previous?.total_capacity">
|
||||||
</app-change>
|
</app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="mining.rewards">Nodes</h5>
|
<h5 class="card-title" i18n="lightning.nodes">Nodes</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.rewards-desc" ngbTooltip="Percentage change past week"
|
<div class="card-text" i18n-ngbTooltip="mining.percentage-change-last-week" ngbTooltip="Percentage change past week"
|
||||||
placement="bottom">
|
[disableTooltip]="!statistics.previous">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.node_count || 0 | number }}
|
{{ statistics.latest?.node_count || 0 | number }}
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.node_count" [previous]="statistics.previous?.node_count"></app-change>
|
<app-change [current]="statistics.latest?.node_count" [previous]="statistics.previous?.node_count"></app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item" [class]="!statistics.previous ? 'more-padding' : ''">
|
||||||
<h5 class="card-title" i18n="mining.rewards-per-tx">Channels</h5>
|
<h5 class="card-title" i18n="lightning.channels">Channels</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.rewards-per-tx-desc" ngbTooltip="Percentage change past week"
|
<div class="card-text" i18n-ngbTooltip="mining.percentage-change-last-week" ngbTooltip="Percentage change past week"
|
||||||
placement="bottom">
|
[disableTooltip]="!statistics.previous">
|
||||||
<div class="fee-text">
|
<div class="fee-text" [class]="!statistics.previous ? 'no-border' : ''">
|
||||||
{{ statistics.latest?.channel_count || 0 | number }}
|
{{ statistics.latest?.channel_count || 0 | number }}
|
||||||
</div>
|
</div>
|
||||||
<span class="fiat">
|
<span class="fiat" *ngIf="statistics.previous">
|
||||||
<app-change [current]="statistics.latest?.channel_count" [previous]="statistics.previous?.channel_count">
|
<app-change [current]="statistics.latest?.channel_count" [previous]="statistics.previous?.channel_count">
|
||||||
</app-change>
|
</app-change>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
|
||||||
<div class="item">
|
|
||||||
<h5 class="card-title" i18n="mining.average-fee">Average Channel</h5>
|
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.average-fee"
|
|
||||||
ngbTooltip="Fee paid on average for each transaction in the past 144 blocks" placement="bottom">
|
|
||||||
<app-amount [satoshis]="statistics.latest.average_channel_size" digitsInfo="1.2-3"></app-amount>
|
|
||||||
<span class="fiat">
|
|
||||||
<app-change [current]="statistics.latest.average_channel_size" [previous]="statistics.previous.average_channel_size"></app-change>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
-->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #loadingReward>
|
<ng-template #loadingReward>
|
||||||
<div class="fee-estimation-container loading-container">
|
<div class="fee-estimation-container loading-container">
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.rewards">Nodes</h5>
|
<h5 class="card-title" i18n="lightning.nodes">Nodes</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.rewards-per-tx">Channels</h5>
|
<h5 class="card-title" i18n="lightning.channels">Channels</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="mining.average-fee">Average Channel</h5>
|
<h5 class="card-title" i18n="lightning.average-channels">Average Channel</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
|
@ -18,6 +18,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fee-estimation-wrapper {
|
||||||
|
min-height: 77px;
|
||||||
|
}
|
||||||
|
|
||||||
.fee-estimation-container {
|
.fee-estimation-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -30,7 +34,10 @@
|
|||||||
width: -webkit-fill-available;
|
width: -webkit-fill-available;
|
||||||
@media (min-width: 376px) {
|
@media (min-width: 376px) {
|
||||||
margin: 0 auto 0px;
|
margin: 0 auto 0px;
|
||||||
}
|
}
|
||||||
|
&.more-padding {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
&:first-child{
|
&:first-child{
|
||||||
display: none;
|
display: none;
|
||||||
@media (min-width: 485px) {
|
@media (min-width: 485px) {
|
||||||
@ -57,6 +64,9 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
padding: 0px 2px;
|
padding: 0px 2px;
|
||||||
|
&.no-border {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.fiat {
|
.fiat {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -133,7 +133,7 @@
|
|||||||
|
|
||||||
<app-node-channels style="display:block;margin-bottom: 40px" [publicKey]="node.public_key"></app-node-channels>
|
<app-node-channels style="display:block;margin-bottom: 40px" [publicKey]="node.public_key"></app-node-channels>
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex">
|
||||||
<h2 *ngIf="channelsListStatus === 'open'">
|
<h2 *ngIf="channelsListStatus === 'open'">
|
||||||
<span i18n="lightning.open-channels">Open channels</span>
|
<span i18n="lightning.open-channels">Open channels</span>
|
||||||
<span> ({{ node.opened_channel_count }})</span>
|
<span> ({{ node.opened_channel_count }})</span>
|
||||||
@ -142,10 +142,13 @@
|
|||||||
<span i18n="lightning.open-channels">Closed channels</span>
|
<span i18n="lightning.open-channels">Closed channels</span>
|
||||||
<span> ({{ node.closed_channel_count }})</span>
|
<span> ({{ node.closed_channel_count }})</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
<div *ngIf="channelListLoading" class="spinner-border ml-3" role="status"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<app-channels-list [publicKey]="node.public_key"
|
<app-channels-list [publicKey]="node.public_key"
|
||||||
(channelsStatusChangedEvent)="onChannelsListStatusChanged($event)"></app-channels-list>
|
(channelsStatusChangedEvent)="onChannelsListStatusChanged($event)"
|
||||||
|
(loadingEvent)="onLoadingEvent($event)"
|
||||||
|
></app-channels-list>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,4 +56,17 @@ app-fiat {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.spinner-border {
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
margin-top: 6.5px;
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
}
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
margin-top: 2.3px;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,7 +22,7 @@ export class NodeComponent implements OnInit {
|
|||||||
channelsListStatus: string;
|
channelsListStatus: string;
|
||||||
error: Error;
|
error: Error;
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
|
channelListLoading = false;
|
||||||
publicKeySize = 99;
|
publicKeySize = 99;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -97,4 +97,8 @@ export class NodeComponent implements OnInit {
|
|||||||
onChannelsListStatusChanged(e) {
|
onChannelsListStatusChanged(e) {
|
||||||
this.channelsListStatus = e;
|
this.channelsListStatus = e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLoadingEvent(e) {
|
||||||
|
this.channelListLoading = e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,13 +44,13 @@ export class NodeChannels implements OnChanges {
|
|||||||
switchMap((response) => {
|
switchMap((response) => {
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
if ((response.body?.length ?? 0) <= 0) {
|
if ((response.body?.length ?? 0) <= 0) {
|
||||||
return [];
|
this.isLoading = false;
|
||||||
|
return [''];
|
||||||
}
|
}
|
||||||
return [response.body];
|
return [response.body];
|
||||||
}),
|
}),
|
||||||
tap((body: any[]) => {
|
tap((body: any[]) => {
|
||||||
if (body.length === 0) {
|
if (body.length === 0 || body[0].length === 0) {
|
||||||
this.isLoading = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const biggestCapacity = body[0].capacity;
|
const biggestCapacity = body[0].capacity;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
|
|
||||||
<div class="card-header" *ngIf="!widget">
|
<div class="card-header" *ngIf="!widget">
|
||||||
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
|
<div class="d-flex d-md-block align-items-baseline" style="margin-bottom: -5px">
|
||||||
<span i18n="lightning.top-100-isp-ln">Top 100 ISP hosting LN nodes</span>
|
<span i18n="lightning.top-100-isp-ln">Top 100 ISPs hosting LN nodes</span>
|
||||||
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
||||||
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
|
@ -40,7 +40,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@media (min-width: 991px) {
|
@media (min-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: -65px;
|
top: -100px;
|
||||||
}
|
}
|
||||||
@media (min-width: 830px) and (max-width: 991px) {
|
@media (min-width: 830px) and (max-width: 991px) {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<title>mempool - Bisq Markets</title>
|
<title>mempool - Bisq Markets</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the Bisq Network.">
|
<meta name="description" content="The Mempool Open Source Project™ - Explore the full Bitcoin ecosystem.">
|
||||||
|
|
||||||
<meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
|
<meta property="og:image" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
|
||||||
<meta property="og:image:type" content="image/jpeg" />
|
<meta property="og:image:type" content="image/jpeg" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<meta property="twitter:site" content="https://bisq.markets/">
|
<meta property="twitter:site" content="https://bisq.markets/">
|
||||||
<meta property="twitter:creator" content="@bisq_network">
|
<meta property="twitter:creator" content="@bisq_network">
|
||||||
<meta property="twitter:title" content="The Mempool Open Source Project™">
|
<meta property="twitter:title" content="The Mempool Open Source Project™">
|
||||||
<meta property="twitter:description" content="Our self-hosted markets explorer for the Bisq community.">
|
<meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space™" />
|
||||||
<meta property="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
|
<meta property="twitter:image:src" content="https://bisq.markets/resources/bisq/bisq-markets-preview.png" />
|
||||||
<meta property="twitter:domain" content="bisq.markets">
|
<meta property="twitter:domain" content="bisq.markets">
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<title>mempool - Liquid Network</title>
|
<title>mempool - Liquid Network</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the Liquid Network.">
|
<meta name="description" content="The Mempool Open Source Project™ - Explore the full Bitcoin ecosystem.">
|
||||||
<meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
|
<meta property="og:image" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
|
||||||
<meta property="og:image:type" content="image/png" />
|
<meta property="og:image:type" content="image/png" />
|
||||||
<meta property="og:image:width" content="1000" />
|
<meta property="og:image:width" content="1000" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<meta property="twitter:site" content="@mempool">
|
<meta property="twitter:site" content="@mempool">
|
||||||
<meta property="twitter:creator" content="@mempool">
|
<meta property="twitter:creator" content="@mempool">
|
||||||
<meta property="twitter:title" content="The Mempool Open Source Project™">
|
<meta property="twitter:title" content="The Mempool Open Source Project™">
|
||||||
<meta property="twitter:description" content="Our self-hosted network explorer for the Liquid community.">
|
<meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space™" />
|
||||||
<meta property="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
|
<meta property="twitter:image:src" content="https://liquid.network/resources/liquid/liquid-network-preview.png" />
|
||||||
<meta property="twitter:domain" content="liquid.network">
|
<meta property="twitter:domain" content="liquid.network">
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<title>mempool - Bitcoin Explorer</title>
|
<title>mempool - Bitcoin Explorer</title>
|
||||||
<base href="/">
|
<base href="/">
|
||||||
|
|
||||||
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the Bitcoin community." />
|
<meta name="description" content="The Mempool Open Source Project™ - Explore the full Bitcoin ecosystem." />
|
||||||
<meta property="og:image" content="https://mempool.space/resources/mempool-space-preview.png" />
|
<meta property="og:image" content="https://mempool.space/resources/mempool-space-preview.png" />
|
||||||
<meta property="og:image:type" content="image/png" />
|
<meta property="og:image:type" content="image/png" />
|
||||||
<meta property="og:image:width" content="1000" />
|
<meta property="og:image:width" content="1000" />
|
||||||
@ -14,7 +14,7 @@
|
|||||||
<meta property="twitter:site" content="@mempool">
|
<meta property="twitter:site" content="@mempool">
|
||||||
<meta property="twitter:creator" content="@mempool">
|
<meta property="twitter:creator" content="@mempool">
|
||||||
<meta property="twitter:title" content="The Mempool Open Source Project™">
|
<meta property="twitter:title" content="The Mempool Open Source Project™">
|
||||||
<meta property="twitter:description" content="Our self-hosted mempool explorer for the Bitcoin community." />
|
<meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space™" />
|
||||||
<meta property="twitter:image:src" content="https://mempool.space/resources/mempool-space-preview.png" />
|
<meta property="twitter:image:src" content="https://mempool.space/resources/mempool-space-preview.png" />
|
||||||
<meta property="twitter:domain" content="mempool.space">
|
<meta property="twitter:domain" content="mempool.space">
|
||||||
|
|
||||||
|
BIN
frontend/src/resources/previews/dashboard.png
Normal file
BIN
frontend/src/resources/previews/dashboard.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
frontend/src/resources/previews/lightning.png
Normal file
BIN
frontend/src/resources/previews/lightning.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 288 KiB |
BIN
frontend/src/resources/previews/mining.png
Normal file
BIN
frontend/src/resources/previews/mining.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 112 KiB |
@ -48,6 +48,9 @@ BITCOIN_MAINNET_ENABLE=ON
|
|||||||
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
BITCOIN_MAINNET_MINFEE_ENABLE=ON
|
||||||
BITCOIN_TESTNET_ENABLE=ON
|
BITCOIN_TESTNET_ENABLE=ON
|
||||||
BITCOIN_SIGNET_ENABLE=ON
|
BITCOIN_SIGNET_ENABLE=ON
|
||||||
|
LN_BITCOIN_MAINNET_ENABLE=ON
|
||||||
|
LN_BITCOIN_TESTNET_ENABLE=ON
|
||||||
|
LN_BITCOIN_SIGNET_ENABLE=ON
|
||||||
BISQ_MAINNET_ENABLE=ON
|
BISQ_MAINNET_ENABLE=ON
|
||||||
ELEMENTS_LIQUID_ENABLE=ON
|
ELEMENTS_LIQUID_ENABLE=ON
|
||||||
ELEMENTS_LIQUIDTESTNET_ENABLE=ON
|
ELEMENTS_LIQUIDTESTNET_ENABLE=ON
|
||||||
@ -227,6 +230,9 @@ MYSQL_GROUP=mysql
|
|||||||
MEMPOOL_MAINNET_USER='mempool'
|
MEMPOOL_MAINNET_USER='mempool'
|
||||||
MEMPOOL_TESTNET_USER='mempool_testnet'
|
MEMPOOL_TESTNET_USER='mempool_testnet'
|
||||||
MEMPOOL_SIGNET_USER='mempool_signet'
|
MEMPOOL_SIGNET_USER='mempool_signet'
|
||||||
|
LN_MEMPOOL_MAINNET_USER='mempool_mainnet_lightning'
|
||||||
|
LN_MEMPOOL_TESTNET_USER='mempool_testnet_lightning'
|
||||||
|
LN_MEMPOOL_SIGNET_USER='mempool_signet_lightning'
|
||||||
MEMPOOL_LIQUID_USER='mempool_liquid'
|
MEMPOOL_LIQUID_USER='mempool_liquid'
|
||||||
MEMPOOL_LIQUIDTESTNET_USER='mempool_liquidtestnet'
|
MEMPOOL_LIQUIDTESTNET_USER='mempool_liquidtestnet'
|
||||||
MEMPOOL_BISQ_USER='mempool_bisq'
|
MEMPOOL_BISQ_USER='mempool_bisq'
|
||||||
@ -234,6 +240,9 @@ MEMPOOL_BISQ_USER='mempool_bisq'
|
|||||||
MEMPOOL_MAINNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_MAINNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_TESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_TESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_SIGNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_SIGNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
|
LN_MEMPOOL_MAINNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
|
LN_MEMPOOL_TESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
|
LN_MEMPOOL_SIGNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_LIQUID_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_LIQUID_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_LIQUIDTESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_LIQUIDTESTNET_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
MEMPOOL_BISQ_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
MEMPOOL_BISQ_PASS=$(head -150 /dev/urandom | ${MD5} | awk '{print $1}')
|
||||||
@ -391,6 +400,10 @@ FREEBSD_PKG+=(boost-libs autoconf automake gmake gcc libevent libtool pkgconf)
|
|||||||
FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase)
|
FREEBSD_PKG+=(nginx rsync py39-certbot-nginx mariadb105-server keybase)
|
||||||
FREEBSD_PKG+=(geoipupdate)
|
FREEBSD_PKG+=(geoipupdate)
|
||||||
|
|
||||||
|
FREEBSD_UNFURL_PKG=()
|
||||||
|
FREEBSD_UNFURL_PKG+=(nvidia-driver-470-470.129.06 chromium xinit xterm twm ja-sourcehansans-otf)
|
||||||
|
FREEBSD_UNFURL_PKG+=(zh-sourcehansans-sc-otf ko-aleefonts-ttf lohit tlwg-ttf)
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
##### utility functions #####
|
##### utility functions #####
|
||||||
#############################
|
#############################
|
||||||
@ -747,6 +760,9 @@ $CUT >$input <<-EOF
|
|||||||
Tor:Enable Tor v3 HS Onion:ON
|
Tor:Enable Tor v3 HS Onion:ON
|
||||||
Mainnet:Enable Bitcoin Mainnet:ON
|
Mainnet:Enable Bitcoin Mainnet:ON
|
||||||
Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
|
Mainnet-Minfee:Enable Bitcoin Mainnet Minfee:ON
|
||||||
|
LN-Mainnet:Enable Bitcoin Mainnet Lightning:ON
|
||||||
|
LN-Testnet:Enable Bitcoin Testnet Lightning:ON
|
||||||
|
LN-Signet:Enable Bitcoin Signet Lightning:ON
|
||||||
Testnet:Enable Bitcoin Testnet:ON
|
Testnet:Enable Bitcoin Testnet:ON
|
||||||
Signet:Enable Bitcoin Signet:ON
|
Signet:Enable Bitcoin Signet:ON
|
||||||
Liquid:Enable Elements Liquid:ON
|
Liquid:Enable Elements Liquid:ON
|
||||||
@ -809,6 +825,24 @@ else
|
|||||||
BITCOIN_INSTALL=OFF
|
BITCOIN_INSTALL=OFF
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if grep LN-Mainnet $tempfile >/dev/null 2>&1;then
|
||||||
|
LN_BITCOIN_MAINNET_ENABLE=ON
|
||||||
|
else
|
||||||
|
LN_BITCOIN_MAINNET_ENABLE=OFF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep LN-Testnet $tempfile >/dev/null 2>&1;then
|
||||||
|
LN_BITCOIN_TESTNET_ENABLE=ON
|
||||||
|
else
|
||||||
|
LN_BITCOIN_TESTNET_ENABLE=OFF
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep LN-Signet $tempfile >/dev/null 2>&1;then
|
||||||
|
LN_BITCOIN_SIGNET_ENABLE=ON
|
||||||
|
else
|
||||||
|
LN_BITCOIN_SIGNET_ENABLE=OFF
|
||||||
|
fi
|
||||||
|
|
||||||
if grep Liquid $tempfile >/dev/null 2>&1;then
|
if grep Liquid $tempfile >/dev/null 2>&1;then
|
||||||
ELEMENTS_LIQUID_ENABLE=ON
|
ELEMENTS_LIQUID_ENABLE=ON
|
||||||
else
|
else
|
||||||
@ -831,6 +865,7 @@ if grep CoreLN $tempfile >/dev/null 2>&1;then
|
|||||||
CLN_INSTALL=ON
|
CLN_INSTALL=ON
|
||||||
else
|
else
|
||||||
CLN_INSTALL=OFF
|
CLN_INSTALL=OFF
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
BITCOIN_ELECTRS_INSTALL=ON
|
BITCOIN_ELECTRS_INSTALL=ON
|
||||||
@ -1279,8 +1314,11 @@ case $OS in
|
|||||||
echo "[*] Creating Core Lightning user"
|
echo "[*] Creating Core Lightning user"
|
||||||
osGroupCreate "${CLN_GROUP}"
|
osGroupCreate "${CLN_GROUP}"
|
||||||
osUserCreate "${CLN_USER}" "${CLN_HOME}" "${CLN_GROUP}"
|
osUserCreate "${CLN_USER}" "${CLN_HOME}" "${CLN_GROUP}"
|
||||||
|
osSudo "${ROOT_USER}" pw usermod ${MEMPOOL_USER} -G "${CLN_GROUP}"
|
||||||
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
||||||
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
||||||
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning" "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
||||||
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
||||||
|
|
||||||
echo "[*] Installing Core Lightning package"
|
echo "[*] Installing Core Lightning package"
|
||||||
@ -1397,7 +1435,42 @@ if [ "${UNFURL_INSTALL}" = ON ];then
|
|||||||
case $OS in
|
case $OS in
|
||||||
|
|
||||||
FreeBSD)
|
FreeBSD)
|
||||||
echo "[*] FIXME: Unfurl must be installed manually on FreeBSD"
|
|
||||||
|
if pciconf -lv|grep -i nvidia >/dev/null 2>&1;then
|
||||||
|
echo "[*] GPU detected: Installing packages for Unfurl"
|
||||||
|
osPackageInstall ${FREEBSD_UNFURL_PKG[@]}
|
||||||
|
|
||||||
|
echo 'allowed_users = anybody' >> /usr/local/etc/X11/Xwrapper.config
|
||||||
|
echo 'kld_list="nvidia"' >> /etc/rc.conf
|
||||||
|
echo 'nvidia_xorg_enable="YES"' >> /etc/rc.conf
|
||||||
|
|
||||||
|
echo "[*] Installing color emoji"
|
||||||
|
osSudo "${ROOT_USER}" curl "https://github.com/samuelngs/apple-emoji-linux/releases/download/ios-15.4/AppleColorEmoji.ttf" -o /usr/local/share/fonts/TTF/AppleColorEmoji.ttf
|
||||||
|
cat >> /usr/local/etc/fonts/conf.d/01-emoji.conf <<EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
|
||||||
|
<fontconfig>
|
||||||
|
<match>
|
||||||
|
<test name="family"><string>sans-serif</string></test>
|
||||||
|
<edit name="family" mode="prepend" binding="strong">
|
||||||
|
<string>Apple Color Emoji</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
<match>
|
||||||
|
<test name="family"><string>serif</string></test>
|
||||||
|
<edit name="family" mode="prepend" binding="strong">
|
||||||
|
<string>Apple Color Emoji</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
<match>
|
||||||
|
<test name="family"><string>Apple Color Emoji</string></test>
|
||||||
|
<edit name="family" mode="prepend" binding="strong">
|
||||||
|
<string>Apple Color Emoji</string>
|
||||||
|
</edit>
|
||||||
|
</match>
|
||||||
|
</fontconfig>
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
;;
|
;;
|
||||||
|
|
||||||
Debian)
|
Debian)
|
||||||
@ -1671,7 +1744,16 @@ if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${
|
|||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/mainnet"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/mainnet"
|
||||||
|
|
||||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Mainnet"
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Mainnet"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/mainnet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${LN_BITCOIN_MAINNET_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Creating Mempool instance for Lightning Network on Bitcoin Mainnet"
|
||||||
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/mainnet-lightning"
|
||||||
|
|
||||||
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Lightning Network on Bitcoin Mainnet"
|
||||||
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/mainnet-lightning && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
@ -1680,7 +1762,16 @@ if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then
|
|||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/testnet"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/testnet"
|
||||||
|
|
||||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Testnet"
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Testnet"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${LN_BITCOIN_TESTNET_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Creating Mempool instance for Lightning Network on Bitcoin Testnet"
|
||||||
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/testnet-lightning"
|
||||||
|
|
||||||
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Lightning Network on Bitcoin Testnet"
|
||||||
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/testnet-lightning && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
@ -1689,7 +1780,16 @@ if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then
|
|||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/signet"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/signet"
|
||||||
|
|
||||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Signet"
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bitcoin Signet"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/signet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${LN_BITCOIN_SIGNET_ENABLE}" = ON ];then
|
||||||
|
echo "[*] Creating Mempool instance for Lightning Network on Bitcoin Signet"
|
||||||
|
osSudo "${MEMPOOL_USER}" git config --global advice.detachedHead false
|
||||||
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/signet-lightning"
|
||||||
|
|
||||||
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Lightning Network on Bitcoin Signet"
|
||||||
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/signet-lightning && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
||||||
@ -1698,7 +1798,7 @@ if [ "${ELEMENTS_LIQUID_ENABLE}" = ON ];then
|
|||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/liquid"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/liquid"
|
||||||
|
|
||||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Liquid"
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Liquid"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/liquid && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
||||||
@ -1707,7 +1807,7 @@ if [ "${ELEMENTS_LIQUIDTESTNET_ENABLE}" = ON ];then
|
|||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/liquidtestnet"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/liquidtestnet"
|
||||||
|
|
||||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Liquid Testnet"
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Liquid Testnet"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/liquidtestnet && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${BISQ_INSTALL}" = ON ];then
|
if [ "${BISQ_INSTALL}" = ON ];then
|
||||||
@ -1716,7 +1816,7 @@ if [ "${BISQ_INSTALL}" = ON ];then
|
|||||||
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/bisq"
|
osSudo "${MEMPOOL_USER}" git clone --branch "${MEMPOOL_REPO_BRANCH}" "${MEMPOOL_REPO_URL}" "${MEMPOOL_HOME}/bisq"
|
||||||
|
|
||||||
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bisq"
|
echo "[*] Checking out Mempool ${MEMPOOL_LATEST_RELEASE} for Bisq"
|
||||||
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME} && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
osSudo "${MEMPOOL_USER}" sh -c "cd ${MEMPOOL_HOME}/bisq && git checkout ${MEMPOOL_LATEST_RELEASE}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
##### mariadb
|
##### mariadb
|
||||||
@ -1742,6 +1842,15 @@ grant all on mempool_testnet.* to '${MEMPOOL_TESTNET_USER}'@'localhost' identifi
|
|||||||
create database mempool_signet;
|
create database mempool_signet;
|
||||||
grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
|
grant all on mempool_signet.* to '${MEMPOOL_SIGNET_USER}'@'localhost' identified by '${MEMPOOL_SIGNET_PASS}';
|
||||||
|
|
||||||
|
create database mempool_mainnet_lightning;
|
||||||
|
grant all on mempool_mainnet_lightning.* to '${LN_MEMPOOL_MAINNET_USER}'@'%' identified by '${LN_MEMPOOL_MAINNET_PASS}';
|
||||||
|
|
||||||
|
create database mempool_testnet_lightning;
|
||||||
|
grant all on mempool_testnet_lightning.* to '${LN_MEMPOOL_TESTNET_USER}'@'%' identified by '${LN_MEMPOOL_TESTNET_PASS}';
|
||||||
|
|
||||||
|
create database mempool_signet_lightning;
|
||||||
|
grant all on mempool_signet_lightning.* to '${LN_MEMPOOL_SIGNET_USER}'@'%' identified by '${LN_MEMPOOL_SIGNET_PASS}';
|
||||||
|
|
||||||
create database mempool_liquid;
|
create database mempool_liquid;
|
||||||
grant all on mempool_liquid.* to '${MEMPOOL_LIQUID_USER}'@'localhost' identified by '${MEMPOOL_LIQUID_PASS}';
|
grant all on mempool_liquid.* to '${MEMPOOL_LIQUID_USER}'@'localhost' identified by '${MEMPOOL_LIQUID_PASS}';
|
||||||
|
|
||||||
@ -1760,6 +1869,12 @@ declare -x MEMPOOL_TESTNET_USER="${MEMPOOL_TESTNET_USER}"
|
|||||||
declare -x MEMPOOL_TESTNET_PASS="${MEMPOOL_TESTNET_PASS}"
|
declare -x MEMPOOL_TESTNET_PASS="${MEMPOOL_TESTNET_PASS}"
|
||||||
declare -x MEMPOOL_SIGNET_USER="${MEMPOOL_SIGNET_USER}"
|
declare -x MEMPOOL_SIGNET_USER="${MEMPOOL_SIGNET_USER}"
|
||||||
declare -x MEMPOOL_SIGNET_PASS="${MEMPOOL_SIGNET_PASS}"
|
declare -x MEMPOOL_SIGNET_PASS="${MEMPOOL_SIGNET_PASS}"
|
||||||
|
declare -x LN_MEMPOOL_MAINNET_USER="${LN_MEMPOOL_MAINNET_USER}"
|
||||||
|
declare -x LN_MEMPOOL_MAINNET_PASS="${LN_MEMPOOL_MAINNET_PASS}"
|
||||||
|
declare -x LN_MEMPOOL_TESTNET_USER="${LN_MEMPOOL_TESTNET_USER}"
|
||||||
|
declare -x LN_MEMPOOL_TESTNET_PASS="${LN_MEMPOOL_TESTNET_PASS}"
|
||||||
|
declare -x LN_MEMPOOL_SIGNET_USER="${LN_MEMPOOL_SIGNET_USER}"
|
||||||
|
declare -x LN_MEMPOOL_SIGNET_PASS="${LN_MEMPOOL_SIGNET_PASS}"
|
||||||
declare -x MEMPOOL_LIQUID_USER="${MEMPOOL_LIQUID_USER}"
|
declare -x MEMPOOL_LIQUID_USER="${MEMPOOL_LIQUID_USER}"
|
||||||
declare -x MEMPOOL_LIQUID_PASS="${MEMPOOL_LIQUID_PASS}"
|
declare -x MEMPOOL_LIQUID_PASS="${MEMPOOL_LIQUID_PASS}"
|
||||||
declare -x MEMPOOL_LIQUIDTESTNET_USER="${MEMPOOL_LIQUIDTESTNET_USER}"
|
declare -x MEMPOOL_LIQUIDTESTNET_USER="${MEMPOOL_LIQUIDTESTNET_USER}"
|
||||||
@ -1770,24 +1885,32 @@ _EOF_
|
|||||||
|
|
||||||
##### nginx
|
##### nginx
|
||||||
|
|
||||||
echo "[*] Adding Nginx configuration"
|
case $OS in
|
||||||
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
|
|
||||||
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
|
FreeBSD)
|
||||||
chown ${NGINX_USER}: /var/cache/nginx/services /var/cache/nginx/api
|
;;
|
||||||
ln -s /mempool/mempool /etc/nginx/mempool
|
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_USER__!${NGINX_USER}!" "${NGINX_CONFIGURATION}"
|
Debian)
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_ETC_FOLDER__!${NGINX_ETC_FOLDER}!" "${NGINX_CONFIGURATION}"
|
echo "[*] Adding Nginx configuration"
|
||||||
if [ "${TOR_INSTALL}" = ON ];then
|
osSudo "${ROOT_USER}" install -c -o "${ROOT_USER}" -g "${ROOT_GROUP}" -m 644 "${MEMPOOL_HOME}/${MEMPOOL_REPO_NAME}/production/nginx/nginx.conf" "${NGINX_CONFIGURATION}"
|
||||||
echo "[*] Read tor v3 onion hostnames"
|
mkdir -p /var/cache/nginx/services /var/cache/nginx/api
|
||||||
NGINX_MEMPOOL_ONION=$(cat "${TOR_RESOURCES}/mempool/hostname")
|
chown ${NGINX_USER}: /var/cache/nginx/services /var/cache/nginx/api
|
||||||
NGINX_BISQ_ONION=$(cat "${TOR_RESOURCES}/bisq/hostname")
|
ln -s /mempool/mempool /etc/nginx/mempool
|
||||||
NGINX_LIQUID_ONION=$(cat "${TOR_RESOURCES}/liquid/hostname")
|
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_USER__!${NGINX_USER}!" "${NGINX_CONFIGURATION}"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}"
|
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_ETC_FOLDER__!${NGINX_ETC_FOLDER}!" "${NGINX_CONFIGURATION}"
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${NGINX_CONFIGURATION}"
|
if [ "${TOR_INSTALL}" = ON ];then
|
||||||
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}"
|
echo "[*] Read tor v3 onion hostnames"
|
||||||
fi
|
NGINX_MEMPOOL_ONION=$(cat "${TOR_RESOURCES}/mempool/hostname")
|
||||||
echo "[*] Restarting Nginx"
|
NGINX_BISQ_ONION=$(cat "${TOR_RESOURCES}/bisq/hostname")
|
||||||
osSudo "${ROOT_USER}" service nginx restart
|
NGINX_LIQUID_ONION=$(cat "${TOR_RESOURCES}/liquid/hostname")
|
||||||
|
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_MEMPOOL_ONION__!${NGINX_MEMPOOL_ONION%.onion}!" "${NGINX_CONFIGURATION}"
|
||||||
|
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_BISQ_ONION__!${NGINX_BISQ_ONION%.onion}!" "${NGINX_CONFIGURATION}"
|
||||||
|
osSudo "${ROOT_USER}" sed -i.orig "s!__NGINX_LIQUID_ONION__!${NGINX_LIQUID_ONIONi%.onion}!" "${NGINX_CONFIGURATION}"
|
||||||
|
fi
|
||||||
|
echo "[*] Restarting Nginx"
|
||||||
|
osSudo "${ROOT_USER}" service nginx restart
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
##### OS systemd
|
##### OS systemd
|
||||||
|
|
||||||
|
@ -98,6 +98,12 @@ build_backend()
|
|||||||
-e "s!__MEMPOOL_TESTNET_PASS__!${MEMPOOL_TESTNET_PASS}!" \
|
-e "s!__MEMPOOL_TESTNET_PASS__!${MEMPOOL_TESTNET_PASS}!" \
|
||||||
-e "s!__MEMPOOL_SIGNET_USER__!${MEMPOOL_SIGNET_USER}!" \
|
-e "s!__MEMPOOL_SIGNET_USER__!${MEMPOOL_SIGNET_USER}!" \
|
||||||
-e "s!__MEMPOOL_SIGNET_PASS__!${MEMPOOL_SIGNET_PASS}!" \
|
-e "s!__MEMPOOL_SIGNET_PASS__!${MEMPOOL_SIGNET_PASS}!" \
|
||||||
|
-e "s!__LN_MEMPOOL_MAINNET_USER__!${LN_MEMPOOL_MAINNET_USER}!" \
|
||||||
|
-e "s!__LN_MEMPOOL_MAINNET_PASS__!${LN_MEMPOOL_MAINNET_PASS}!" \
|
||||||
|
-e "s!__LN_MEMPOOL_TESTNET_USER__!${LN_MEMPOOL_TESTNET_USER}!" \
|
||||||
|
-e "s!__LN_MEMPOOL_TESTNET_PASS__!${LN_MEMPOOL_TESTNET_PASS}!" \
|
||||||
|
-e "s!__LN_MEMPOOL_SIGNET_USER__!${LN_MEMPOOL_SIGNET_USER}!" \
|
||||||
|
-e "s!__LN_MEMPOOL_SIGNET_PASS__!${LN_MEMPOOL_SIGNET_PASS}!" \
|
||||||
-e "s!__MEMPOOL_LIQUID_USER__!${MEMPOOL_LIQUID_USER}!" \
|
-e "s!__MEMPOOL_LIQUID_USER__!${MEMPOOL_LIQUID_USER}!" \
|
||||||
-e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
|
-e "s!__MEMPOOL_LIQUID_PASS__!${MEMPOOL_LIQUID_PASS}!" \
|
||||||
-e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${LIQUIDTESTNET_USER}!" \
|
-e "s!__MEMPOOL_LIQUIDTESTNET_USER__!${LIQUIDTESTNET_USER}!" \
|
||||||
@ -145,7 +151,7 @@ for repo in $backend_repos;do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# build unfurlers
|
# build unfurlers
|
||||||
for repo in mainnet liquid;do
|
for repo in mainnet liquid bisq;do
|
||||||
build_unfurler "${repo}"
|
build_unfurler "${repo}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -9,18 +9,20 @@ for site in mainnet mainnet-lightning testnet testnet-lightning signet signet-li
|
|||||||
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
|
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
|
||||||
done
|
done
|
||||||
|
|
||||||
# only start unfurler if GPU present
|
# only start xorg if GPU present
|
||||||
if pciconf -lv|grep -i nvidia >/dev/null 2>&1;then
|
if pciconf -lv|grep -i nvidia >/dev/null 2>&1;then
|
||||||
export DISPLAY=:0
|
export DISPLAY=:0
|
||||||
screen -dmS x startx
|
screen -dmS x startx
|
||||||
sleep 3
|
sleep 3
|
||||||
for site in mainnet liquid;do
|
|
||||||
cd "$HOME/${site}/unfurler" && \
|
|
||||||
echo "starting mempool unfurler: ${site}" && \
|
|
||||||
screen -dmS "unfurler-${site}" sh -c 'while true;do npm run unfurler;sleep 2;done'
|
|
||||||
done
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# start unfurlers for each frontend
|
||||||
|
for site in mainnet liquid bisq;do
|
||||||
|
cd "$HOME/${site}/unfurler" && \
|
||||||
|
echo "starting mempool unfurler: ${site}" && \
|
||||||
|
screen -dmS "unfurler-${site}" sh -c 'while true;do npm run unfurler;sleep 2;done'
|
||||||
|
done
|
||||||
|
|
||||||
# start nginx warm cacher
|
# start nginx warm cacher
|
||||||
for site in mainnet;do
|
for site in mainnet;do
|
||||||
echo "starting mempool cache warmer: ${site}"
|
echo "starting mempool cache warmer: ${site}"
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
# start on reboot
|
# start on reboot
|
||||||
@reboot sleep 10 ; $HOME/start
|
@reboot sleep 10 ; $HOME/start
|
||||||
|
|
||||||
# start cache warmer on reboot
|
|
||||||
@reboot sleep 180 ; /mempool/mempool/production/nginx-cache-warmer >/dev/null 2>&1 &
|
|
||||||
|
|
||||||
# daily backup
|
# daily backup
|
||||||
37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 &
|
37 13 * * * sleep 30 ; /mempool/mempool.space/backup >/dev/null 2>&1 &
|
||||||
|
|
||||||
|
17
production/unfurler-config.bisq.json
Normal file
17
production/unfurler-config.bisq.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"SERVER": {
|
||||||
|
"HOST": "https://bisq.fra.mempool.space",
|
||||||
|
"HTTP_PORT": 8002
|
||||||
|
},
|
||||||
|
"MEMPOOL": {
|
||||||
|
"HTTP_HOST": "http://127.0.0.1",
|
||||||
|
"HTTP_PORT": 82,
|
||||||
|
"NETWORK": "bisq"
|
||||||
|
},
|
||||||
|
"PUPPETEER": {
|
||||||
|
"CLUSTER_SIZE": 8,
|
||||||
|
"EXEC_PATH": "/usr/local/bin/chrome",
|
||||||
|
"MAX_PAGE_AGE": 86400,
|
||||||
|
"RENDER_TIMEOUT": 3000
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"SERVER": {
|
"SERVER": {
|
||||||
"HOST": "https://liquid.network",
|
"HOST": "https://liquid.fra.mempool.space",
|
||||||
"HTTP_PORT": 8002
|
"HTTP_PORT": 8003
|
||||||
},
|
},
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
"HTTP_HOST": "https://liquid.network",
|
"HTTP_HOST": "http://127.0.0.1",
|
||||||
"HTTP_PORT": 443,
|
"HTTP_PORT": 83,
|
||||||
"NETWORK": "liquid"
|
"NETWORK": "bitcoin"
|
||||||
},
|
},
|
||||||
"PUPPETEER": {
|
"PUPPETEER": {
|
||||||
"CLUSTER_SIZE": 8,
|
"CLUSTER_SIZE": 8,
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"SERVER": {
|
"SERVER": {
|
||||||
"HOST": "https://mempool.space",
|
"HOST": "https://mempool.fra.mempool.space",
|
||||||
"HTTP_PORT": 8001
|
"HTTP_PORT": 8001
|
||||||
},
|
},
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
"HTTP_HOST": "https://mempool.space",
|
"HTTP_HOST": "http://127.0.0.1",
|
||||||
"HTTP_PORT": 443,
|
"HTTP_PORT": 81,
|
||||||
"NETWORK": "bitcoin"
|
"NETWORK": "bitcoin"
|
||||||
},
|
},
|
||||||
"PUPPETEER": {
|
"PUPPETEER": {
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"NETWORK": "bitcoin" // "bitcoin" | "liquid" | "bisq" (optional - defaults to "bitcoin")
|
"NETWORK": "bitcoin" // "bitcoin" | "liquid" | "bisq" (optional - defaults to "bitcoin")
|
||||||
},
|
},
|
||||||
"PUPPETEER": {
|
"PUPPETEER": {
|
||||||
|
"DISABLE": false, // optional, boolean, disables puppeteer and /render endpoints
|
||||||
"CLUSTER_SIZE": 2,
|
"CLUSTER_SIZE": 2,
|
||||||
"EXEC_PATH": "/usr/local/bin/chrome", // optional
|
"EXEC_PATH": "/usr/local/bin/chrome", // optional
|
||||||
"MAX_PAGE_AGE": 86400, // maximum lifetime of a page session (in seconds)
|
"MAX_PAGE_AGE": 86400, // maximum lifetime of a page session (in seconds)
|
||||||
|
4
unfurler/package-lock.json
generated
4
unfurler/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "mempool-unfurl",
|
"name": "mempool-unfurl",
|
||||||
"version": "0.0.1",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "mempool-unfurl",
|
"name": "mempool-unfurl",
|
||||||
"version": "0.0.1",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "^16.11.41",
|
"@types/node": "^16.11.41",
|
||||||
"express": "^4.18.0",
|
"express": "^4.18.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mempool-unfurl",
|
"name": "mempool-unfurl",
|
||||||
"version": "0.0.2",
|
"version": "0.1.0",
|
||||||
"description": "Renderer for mempool open graph link preview images",
|
"description": "Renderer for mempool open graph link preview images",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -11,6 +11,7 @@ interface IConfig {
|
|||||||
NETWORK?: string;
|
NETWORK?: string;
|
||||||
};
|
};
|
||||||
PUPPETEER: {
|
PUPPETEER: {
|
||||||
|
DISABLE: boolean;
|
||||||
CLUSTER_SIZE: number;
|
CLUSTER_SIZE: number;
|
||||||
EXEC_PATH?: string;
|
EXEC_PATH?: string;
|
||||||
MAX_PAGE_AGE?: number;
|
MAX_PAGE_AGE?: number;
|
||||||
@ -28,6 +29,7 @@ const defaults: IConfig = {
|
|||||||
'HTTP_PORT': 4200,
|
'HTTP_PORT': 4200,
|
||||||
},
|
},
|
||||||
'PUPPETEER': {
|
'PUPPETEER': {
|
||||||
|
'DISABLE': false,
|
||||||
'CLUSTER_SIZE': 1,
|
'CLUSTER_SIZE': 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import { Application, Request, Response, NextFunction } from 'express';
|
import { Application, Request, Response, NextFunction } from 'express';
|
||||||
import * as http from 'http';
|
import * as http from 'http';
|
||||||
|
import * as https from 'https';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
import { Cluster } from 'puppeteer-cluster';
|
import { Cluster } from 'puppeteer-cluster';
|
||||||
import ReusablePage from './concurrency/ReusablePage';
|
import ReusablePage from './concurrency/ReusablePage';
|
||||||
import { parseLanguageUrl } from './language/lang';
|
import { parseLanguageUrl } from './language/lang';
|
||||||
|
import { matchRoute } from './routes';
|
||||||
const puppeteerConfig = require('../puppeteer.config.json');
|
const puppeteerConfig = require('../puppeteer.config.json');
|
||||||
|
|
||||||
if (config.PUPPETEER.EXEC_PATH) {
|
if (config.PUPPETEER.EXEC_PATH) {
|
||||||
@ -17,13 +19,13 @@ class Server {
|
|||||||
cluster?: Cluster;
|
cluster?: Cluster;
|
||||||
mempoolHost: string;
|
mempoolHost: string;
|
||||||
network: string;
|
network: string;
|
||||||
defaultImageUrl: string;
|
secureHost = true;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
this.mempoolHost = config.MEMPOOL.HTTP_HOST + (config.MEMPOOL.HTTP_PORT ? ':' + config.MEMPOOL.HTTP_PORT : '');
|
this.mempoolHost = config.MEMPOOL.HTTP_HOST + (config.MEMPOOL.HTTP_PORT ? ':' + config.MEMPOOL.HTTP_PORT : '');
|
||||||
|
this.secureHost = this.mempoolHost.startsWith('https');
|
||||||
this.network = config.MEMPOOL.NETWORK || 'bitcoin';
|
this.network = config.MEMPOOL.NETWORK || 'bitcoin';
|
||||||
this.defaultImageUrl = this.getDefaultImageUrl();
|
|
||||||
this.startServer();
|
this.startServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,12 +39,14 @@ class Server {
|
|||||||
.use(express.text())
|
.use(express.text())
|
||||||
;
|
;
|
||||||
|
|
||||||
this.cluster = await Cluster.launch({
|
if (!config.PUPPETEER.DISABLE) {
|
||||||
concurrency: ReusablePage,
|
this.cluster = await Cluster.launch({
|
||||||
maxConcurrency: config.PUPPETEER.CLUSTER_SIZE,
|
concurrency: ReusablePage,
|
||||||
puppeteerOptions: puppeteerConfig,
|
maxConcurrency: config.PUPPETEER.CLUSTER_SIZE,
|
||||||
});
|
puppeteerOptions: puppeteerConfig,
|
||||||
await this.cluster?.task(async (args) => { return this.clusterTask(args) });
|
});
|
||||||
|
await this.cluster?.task(async (args) => { return this.clusterTask(args) });
|
||||||
|
}
|
||||||
|
|
||||||
this.setUpRoutes();
|
this.setUpRoutes();
|
||||||
|
|
||||||
@ -64,7 +68,11 @@ class Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setUpRoutes() {
|
setUpRoutes() {
|
||||||
this.app.get('/render*', async (req, res) => { return this.renderPreview(req, res) })
|
if (!config.PUPPETEER.DISABLE) {
|
||||||
|
this.app.get('/render*', async (req, res) => { return this.renderPreview(req, res) })
|
||||||
|
} else {
|
||||||
|
this.app.get('/render*', async (req, res) => { return this.renderDisabled(req, res) })
|
||||||
|
}
|
||||||
this.app.get('*', (req, res) => { return this.renderHTML(req, res) })
|
this.app.get('*', (req, res) => { return this.renderHTML(req, res) })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,13 +119,31 @@ class Server {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async renderDisabled(req, res) {
|
||||||
|
res.status(500).send("preview rendering disabled");
|
||||||
|
}
|
||||||
|
|
||||||
async renderPreview(req, res) {
|
async renderPreview(req, res) {
|
||||||
try {
|
try {
|
||||||
const path = req.params[0]
|
const rawPath = req.params[0];
|
||||||
const img = await this.cluster?.execute({ url: this.mempoolHost + path, path: path, action: 'screenshot' });
|
|
||||||
|
let img = null;
|
||||||
|
|
||||||
|
const { lang, path } = parseLanguageUrl(rawPath);
|
||||||
|
const matchedRoute = matchRoute(this.network, path);
|
||||||
|
|
||||||
|
// don't bother unless the route is definitely renderable
|
||||||
|
if (rawPath.includes('/preview/') && matchedRoute.render) {
|
||||||
|
img = await this.cluster?.execute({ url: this.mempoolHost + rawPath, path: rawPath, action: 'screenshot' });
|
||||||
|
}
|
||||||
|
|
||||||
if (!img) {
|
if (!img) {
|
||||||
res.status(500).send('failed to render page preview');
|
// proxy fallback image from the frontend
|
||||||
|
if (this.secureHost) {
|
||||||
|
https.get(config.SERVER.HOST + matchedRoute.fallbackImg, (got) => got.pipe(res));
|
||||||
|
} else {
|
||||||
|
http.get(config.SERVER.HOST + matchedRoute.fallbackImg, (got) => got.pipe(res));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
res.contentType('image/png');
|
res.contentType('image/png');
|
||||||
res.send(img);
|
res.send(img);
|
||||||
@ -137,50 +163,14 @@ class Server {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let previewSupported = true;
|
|
||||||
let mode = 'mainnet'
|
|
||||||
let ogImageUrl = this.defaultImageUrl;
|
|
||||||
let ogTitle;
|
|
||||||
const { lang, path } = parseLanguageUrl(rawPath);
|
const { lang, path } = parseLanguageUrl(rawPath);
|
||||||
const parts = path.slice(1).split('/');
|
const matchedRoute = matchRoute(this.network, path);
|
||||||
|
let ogImageUrl = config.SERVER.HOST + (matchedRoute.staticImg || matchedRoute.fallbackImg);
|
||||||
|
let ogTitle = 'The Mempool Open Source Project™';
|
||||||
|
|
||||||
// handle network mode modifiers
|
if (matchedRoute.render) {
|
||||||
if (['testnet', 'signet'].includes(parts[0])) {
|
|
||||||
mode = parts.shift();
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle supported preview routes
|
|
||||||
switch (parts[0]) {
|
|
||||||
case 'block':
|
|
||||||
ogTitle = `Block: ${parts[1]}`;
|
|
||||||
break;
|
|
||||||
case 'address':
|
|
||||||
ogTitle = `Address: ${parts[1]}`;
|
|
||||||
break;
|
|
||||||
case 'tx':
|
|
||||||
ogTitle = `Transaction: ${parts[1]}`;
|
|
||||||
break;
|
|
||||||
case 'lightning':
|
|
||||||
switch (parts[1]) {
|
|
||||||
case 'node':
|
|
||||||
ogTitle = `Lightning Node: ${parts[2]}`;
|
|
||||||
break;
|
|
||||||
case 'channel':
|
|
||||||
ogTitle = `Lightning Channel: ${parts[2]}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
previewSupported = false;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
previewSupported = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (previewSupported) {
|
|
||||||
ogImageUrl = `${config.SERVER.HOST}/render/${lang || 'en'}/preview${path}`;
|
ogImageUrl = `${config.SERVER.HOST}/render/${lang || 'en'}/preview${path}`;
|
||||||
ogTitle = `${this.network ? capitalize(this.network) + ' ' : ''}${mode !== 'mainnet' ? capitalize(mode) + ' ' : ''}${ogTitle}`;
|
ogTitle = `${this.network ? capitalize(this.network) + ' ' : ''}${matchedRoute.networkMode !== 'mainnet' ? capitalize(matchedRoute.networkMode) + ' ' : ''}${matchedRoute.title}`;
|
||||||
} else {
|
|
||||||
ogTitle = 'The Mempool Open Source Project™';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res.send(`
|
res.send(`
|
||||||
@ -189,34 +179,23 @@ class Server {
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>${ogTitle}</title>
|
<title>${ogTitle}</title>
|
||||||
<meta name="description" content="The Mempool Open Source Project™ - our self-hosted explorer for the ${capitalize(this.network)} community."/>
|
<meta name="description" content="The Mempool Open Source Project™ - Explore the full Bitcoin ecosystem with mempool.space™"/>
|
||||||
<meta property="og:image" content="${ogImageUrl}"/>
|
<meta property="og:image" content="${ogImageUrl}"/>
|
||||||
<meta property="og:image:type" content="image/png"/>
|
<meta property="og:image:type" content="image/png"/>
|
||||||
<meta property="og:image:width" content="${previewSupported ? 1200 : 1000}"/>
|
<meta property="og:image:width" content="${matchedRoute.render ? 1200 : 1000}"/>
|
||||||
<meta property="og:image:height" content="${previewSupported ? 600 : 500}"/>
|
<meta property="og:image:height" content="${matchedRoute.render ? 600 : 500}"/>
|
||||||
<meta property="og:title" content="${ogTitle}">
|
<meta property="og:title" content="${ogTitle}">
|
||||||
<meta property="twitter:card" content="summary_large_image">
|
<meta property="twitter:card" content="summary_large_image">
|
||||||
<meta property="twitter:site" content="@mempool">
|
<meta property="twitter:site" content="@mempool">
|
||||||
<meta property="twitter:creator" content="@mempool">
|
<meta property="twitter:creator" content="@mempool">
|
||||||
<meta property="twitter:title" content="${ogTitle}">
|
<meta property="twitter:title" content="${ogTitle}">
|
||||||
<meta property="twitter:description" content="Our self-hosted mempool explorer for the ${capitalize(this.network)} community."/>
|
<meta property="twitter:description" content="Explore the full Bitcoin ecosystem with mempool.space"/>
|
||||||
<meta property="twitter:image:src" content="${ogImageUrl}"/>
|
<meta property="twitter:image:src" content="${ogImageUrl}"/>
|
||||||
<meta property="twitter:domain" content="mempool.space">
|
<meta property="twitter:domain" content="mempool.space">
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultImageUrl() {
|
|
||||||
switch (this.network) {
|
|
||||||
case 'liquid':
|
|
||||||
return this.mempoolHost + '/resources/liquid/liquid-network-preview.png';
|
|
||||||
case 'bisq':
|
|
||||||
return this.mempoolHost + '/resources/bisq/bisq-markets-preview.png';
|
|
||||||
default:
|
|
||||||
return this.mempoolHost + '/resources/mempool-space-preview.png';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const server = new Server();
|
const server = new Server();
|
||||||
|
124
unfurler/src/routes.ts
Normal file
124
unfurler/src/routes.ts
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
interface Match {
|
||||||
|
render: boolean;
|
||||||
|
title: string;
|
||||||
|
fallbackImg: string;
|
||||||
|
staticImg?: string;
|
||||||
|
networkMode: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = {
|
||||||
|
block: {
|
||||||
|
render: true,
|
||||||
|
params: 1,
|
||||||
|
getTitle(path) {
|
||||||
|
return `Block: ${path[0]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
render: true,
|
||||||
|
params: 1,
|
||||||
|
getTitle(path) {
|
||||||
|
return `Address: ${path[0]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tx: {
|
||||||
|
render: true,
|
||||||
|
params: 1,
|
||||||
|
getTitle(path) {
|
||||||
|
return `Transaction: ${path[0]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lightning: {
|
||||||
|
title: "Lightning",
|
||||||
|
fallbackImg: '/resources/previews/lightning.png',
|
||||||
|
routes: {
|
||||||
|
node: {
|
||||||
|
render: true,
|
||||||
|
params: 1,
|
||||||
|
getTitle(path) {
|
||||||
|
return `Lightning Node: ${path[0]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
channel: {
|
||||||
|
render: true,
|
||||||
|
params: 1,
|
||||||
|
getTitle(path) {
|
||||||
|
return `Lightning Channel: ${path[0]}`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mining: {
|
||||||
|
title: "Mining",
|
||||||
|
fallbackImg: '/resources/previews/mining.png'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const networks = {
|
||||||
|
bitcoin: {
|
||||||
|
fallbackImg: '/resources/mempool-space-preview.png',
|
||||||
|
staticImg: '/resources/previews/dashboard.png',
|
||||||
|
routes: {
|
||||||
|
...routes // all routes supported
|
||||||
|
}
|
||||||
|
},
|
||||||
|
liquid: {
|
||||||
|
fallbackImg: '/resources/liquid/liquid-network-preview.png',
|
||||||
|
routes: { // only block, address & tx routes supported
|
||||||
|
block: routes.block,
|
||||||
|
address: routes.address,
|
||||||
|
tx: routes.tx
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bisq: {
|
||||||
|
fallbackImg: '/resources/bisq/bisq-markets-preview.png',
|
||||||
|
routes: {} // no routes supported
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function matchRoute(network: string, path: string): Match {
|
||||||
|
const match: Match = {
|
||||||
|
render: false,
|
||||||
|
title: '',
|
||||||
|
fallbackImg: '',
|
||||||
|
networkMode: 'mainnet'
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = path.slice(1).split('/').filter(p => p.length);
|
||||||
|
|
||||||
|
if (parts[0] === 'preview') {
|
||||||
|
parts.shift();
|
||||||
|
}
|
||||||
|
if (['testnet', 'signet'].includes(parts[0])) {
|
||||||
|
match.networkMode = parts.shift() || 'mainnet';
|
||||||
|
}
|
||||||
|
|
||||||
|
let route = networks[network] || networks.bitcoin;
|
||||||
|
match.fallbackImg = route.fallbackImg;
|
||||||
|
|
||||||
|
// traverse the route tree until we run out of route or tree, or hit a renderable match
|
||||||
|
while (!route.render && route.routes && parts.length && route.routes[parts[0]]) {
|
||||||
|
route = route.routes[parts[0]];
|
||||||
|
parts.shift();
|
||||||
|
if (route.fallbackImg) {
|
||||||
|
match.fallbackImg = route.fallbackImg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// enough route parts left for title & rendering
|
||||||
|
if (route.render && parts.length >= route.params) {
|
||||||
|
match.render = true;
|
||||||
|
}
|
||||||
|
// only use set a static image for exact matches
|
||||||
|
if (!parts.length && route.staticImg) {
|
||||||
|
match.staticImg = route.staticImg;
|
||||||
|
}
|
||||||
|
// apply the title function if present
|
||||||
|
if (route.getTitle && typeof route.getTitle === 'function') {
|
||||||
|
match.title = route.getTitle(parts);
|
||||||
|
} else {
|
||||||
|
match.title = route.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user