mirror of
https://github.com/mempool/mempool.git
synced 2024-11-20 02:11:49 +01:00
Merge branch 'master' into simon/custom-lazy-loading-strategy
This commit is contained in:
commit
7e1ab55c01
@ -74,7 +74,7 @@ class Logger {
|
||||
|
||||
private getNetwork(): string {
|
||||
if (config.LIGHTNING.ENABLED) {
|
||||
return 'lightning';
|
||||
return config.MEMPOOL.NETWORK === 'mainnet' ? 'lightning' : `${config.MEMPOOL.NETWORK}-lightning`;
|
||||
}
|
||||
if (config.BISQ.ENABLED) {
|
||||
return 'bisq';
|
||||
|
@ -20,6 +20,10 @@ class LightningStatsImporter {
|
||||
logger.info('Caching funding txs for currently existing channels');
|
||||
await fundingTxFetcher.$fetchChannelsFundingTxs(channels.map(channel => channel.short_id));
|
||||
|
||||
if (config.MEMPOOL.NETWORK !== 'mainnet' || config.DATABASE.ENABLED === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$importHistoricalLightningStats();
|
||||
await this.$cleanupIncorrectSnapshot();
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
<div class="page-title">
|
||||
<h1 i18n="shared.transaction">Transaction</h1>
|
||||
<a class="tx-link" [routerLink]="['/tx/' | relativeUrl, txId]">
|
||||
<span class="truncated">{{txId.slice(0,-4)}}</span><span class="last-four">{{txId.slice(-4)}}</span>
|
||||
</a>
|
||||
<div *ngIf="network !== 'liquid' && network !== 'liquidtestnet'" class="features">
|
||||
<app-tx-features [tx]="tx"></app-tx-features>
|
||||
<span *ngIf="cpfpInfo && cpfpInfo.bestDescendant" class="badge badge-primary mr-1">
|
||||
@ -13,104 +16,50 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a [routerLink]="['/tx/' | relativeUrl, txId]" class="tx-link">
|
||||
{{ txId }}
|
||||
</a>
|
||||
<div class="top-data row">
|
||||
<span class="field col-sm-4 text-left">
|
||||
<ng-template [ngIf]="isLiquid && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
||||
<ng-template #defaultAmount>
|
||||
<app-amount [satoshis]="totalValue"></app-amount>
|
||||
</ng-template>
|
||||
</span>
|
||||
<span class="field col-sm-4 text-center">‎{{ (tx.status.confirmed ? tx.status.block_time : transactionTime) * 1000 | date:'yyyy-MM-dd HH:mm' }}</span>
|
||||
<span class="field col-sm-4 text-right"><span class="label" i18n="transaction.fee|Transaction fee">Fee </span>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr *ngIf="tx.status.confirmed; else firstSeen">
|
||||
<td i18n="block.timestamp">Timestamp</td>
|
||||
<td>
|
||||
‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
</td>
|
||||
</tr>
|
||||
<ng-template #firstSeen>
|
||||
<tr>
|
||||
<td i18n="transaction.first-seen|Transaction first seen">First seen</td>
|
||||
<td *ngIf="transactionTime > 0; else notSeen">
|
||||
‎{{ transactionTime * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
</td>
|
||||
<ng-template #notSeen>
|
||||
<td>?</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr>
|
||||
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
|
||||
<td>
|
||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
||||
<ng-template #defaultAmount>
|
||||
<app-amount [satoshis]="getTotalTxOutput(tx)"></app-amount>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="block.size">Size</td>
|
||||
<td [innerHTML]="'‎' + (tx.size | bytes: 2)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="block.weight">Weight</td>
|
||||
<td [innerHTML]="'‎' + (tx.weight | wuBytes: 2)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="transaction.inputs">Inputs</td>
|
||||
<td *ngIf="!isCoinbase(tx); else coinbaseInputs">{{ tx.vin.length }}</td>
|
||||
<ng-template #coinbaseInputs>
|
||||
<td i18n="transactions-list.coinbase">Coinbase</td>
|
||||
</ng-template>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="row graph-wrapper">
|
||||
<tx-bowtie-graph [tx]="tx" [width]="1112" [height]="346" [network]="network"></tx-bowtie-graph>
|
||||
<div class="above-bow">
|
||||
<p class="field pair">
|
||||
<span [innerHTML]="'‎' + (tx.size | bytes: 2)"></span>
|
||||
<span [innerHTML]="'‎' + (tx.weight | wuBytes: 2)"></span>
|
||||
</p>
|
||||
<p class="field" *ngIf="!isCoinbase(tx)">
|
||||
{{ tx.feePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>
|
||||
<td>{{ tx.fee | number }} <span class="symbol" i18n="shared.sat|sat">sat</span></td>
|
||||
</tr>
|
||||
<tr *ngIf="!cpfpInfo || (!cpfpInfo.bestDescendant && !cpfpInfo.ancestors.length); else cpfpFee">
|
||||
<td i18n="transaction.fee-rate|Transaction fee rate">Fee rate</td>
|
||||
<td>
|
||||
{{ tx.feePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
<ng-template [ngIf]="tx.status.confirmed">
|
||||
|
||||
<app-tx-fee-rating *ngIf="tx.fee && ((cpfpInfo && !cpfpInfo.bestDescendant && !cpfpInfo.ancestors.length) || !cpfpInfo)" [tx]="tx"></app-tx-fee-rating>
|
||||
</ng-template>
|
||||
</td>
|
||||
</tr>
|
||||
<ng-template #cpfpFee>
|
||||
<div class="overlaid">
|
||||
<ng-container [ngSwitch]="extraData">
|
||||
<table class="opreturns" *ngSwitchCase="'coinbase'">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
<td>
|
||||
<div class="effective-fee-container">
|
||||
{{ tx.effectiveFeePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||
<ng-template [ngIf]="tx.status.confirmed">
|
||||
<app-tx-fee-rating class="d-none d-lg-inline ml-2" *ngIf="tx.fee" [tx]="tx"></app-tx-fee-rating>
|
||||
</ng-template>
|
||||
</div>
|
||||
</td>
|
||||
<td class="label">Coinbase</td>
|
||||
<td class="message">{{ tx.vin[0].scriptsig | hex2ascii }}</td>
|
||||
</tr>
|
||||
</ng-template>
|
||||
<tr>
|
||||
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||
<td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="transaction.locktime">Locktime</td>
|
||||
<td [innerHTML]="'‎' + (tx.locktime | number)"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="transaction.outputs">Outputs</td>
|
||||
<td>{{ tx.vout.length }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</tbody>
|
||||
</table>
|
||||
<table class="opreturns" *ngSwitchCase="'opreturn'">
|
||||
<tbody>
|
||||
<ng-container *ngFor="let vout of opReturns.slice(0,3)">
|
||||
<tr>
|
||||
<td class="label">OP_RETURN</td>
|
||||
<td *ngIf="vout.scriptpubkey_asm !== 'OP_RETURN'" class="message">{{ vout.scriptpubkey_asm | hex2ascii }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -10,26 +10,10 @@
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.btn-small-height {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.arrow-green {
|
||||
color: #1a9436;
|
||||
}
|
||||
|
||||
.arrow-red {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.effective-fee-container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title {
|
||||
h2 {
|
||||
line-height: 1;
|
||||
@ -46,8 +30,9 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
align-items: baseline;
|
||||
margin-bottom: 2px;
|
||||
max-width: 100%;
|
||||
|
||||
h1 {
|
||||
font-size: 52px;
|
||||
@ -58,6 +43,43 @@
|
||||
.features {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
& > * {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tx-link {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
margin: 0 1em;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: baseline;
|
||||
|
||||
.truncated {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: -2px;
|
||||
}
|
||||
|
||||
.last-four {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.features {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.top-data {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.table {
|
||||
@ -68,8 +90,76 @@
|
||||
}
|
||||
}
|
||||
|
||||
.field {
|
||||
font-size: 32px;
|
||||
margin: 0;
|
||||
|
||||
::ng-deep .symbol {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #ffffff66;
|
||||
}
|
||||
|
||||
&.pair > *:first-child {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
.tx-link {
|
||||
display: inline-block;
|
||||
display: inline;
|
||||
font-size: 28px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.graph-wrapper {
|
||||
position: relative;
|
||||
background: #181b2d;
|
||||
padding: 10px;
|
||||
padding-bottom: 0;
|
||||
|
||||
.above-bow {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.overlaid {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 28px;
|
||||
max-width: 90%;
|
||||
margin: auto;
|
||||
overflow: hidden;
|
||||
|
||||
.opreturns {
|
||||
width: auto;
|
||||
margin: auto;
|
||||
table-layout: auto;
|
||||
background: #2d3348af;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
|
||||
td {
|
||||
padding: 10px 10px;
|
||||
|
||||
&.message {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,10 +7,9 @@ import {
|
||||
catchError,
|
||||
retryWhen,
|
||||
delay,
|
||||
map
|
||||
} from 'rxjs/operators';
|
||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
|
||||
import { of, merge, Subscription, Observable, Subject, from } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
@ -37,6 +36,10 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
||||
showCpfpDetails = false;
|
||||
fetchCpfp$ = new Subject<string>();
|
||||
liquidUnblinding = new LiquidUnblinding();
|
||||
isLiquid = false;
|
||||
totalValue: number;
|
||||
opReturns: Vout[];
|
||||
extraData: 'none' | 'coinbase' | 'opreturn';
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
@ -49,7 +52,12 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
||||
|
||||
ngOnInit() {
|
||||
this.stateService.networkChanged$.subscribe(
|
||||
(network) => (this.network = network)
|
||||
(network) => {
|
||||
this.network = network;
|
||||
if (this.network === 'liquid' || this.network == 'liquidtestnet') {
|
||||
this.isLiquid = true;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
this.fetchCpfpSubscription = this.fetchCpfp$
|
||||
@ -152,6 +160,9 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
||||
this.tx.feePerVsize = tx.fee / (tx.weight / 4);
|
||||
this.isLoadingTx = false;
|
||||
this.error = undefined;
|
||||
this.totalValue = this.tx.vout.reduce((acc, v) => v.value + acc, 0);
|
||||
this.opReturns = this.getOpReturns(this.tx);
|
||||
this.extraData = this.chooseExtraData();
|
||||
|
||||
if (!tx.status.confirmed && tx.firstSeen) {
|
||||
this.transactionTime = tx.firstSeen;
|
||||
@ -217,6 +228,20 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
|
||||
return tx.vout.map((v: Vout) => v.value || 0).reduce((a: number, b: number) => a + b);
|
||||
}
|
||||
|
||||
getOpReturns(tx: Transaction): Vout[] {
|
||||
return tx.vout.filter((v) => v.scriptpubkey_type === 'op_return' && v.scriptpubkey_asm !== 'OP_RETURN');
|
||||
}
|
||||
|
||||
chooseExtraData(): 'none' | 'opreturn' | 'coinbase' {
|
||||
if (this.isCoinbase(this.tx)) {
|
||||
return 'coinbase';
|
||||
} else if (this.opReturns?.length) {
|
||||
return 'opreturn';
|
||||
} else {
|
||||
return 'none';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.subscription.unsubscribe();
|
||||
this.fetchCpfpSubscription.unsubscribe();
|
||||
|
@ -0,0 +1,44 @@
|
||||
<svg *ngIf="inputs && outputs" class="bowtie" [attr.height]="(height + 10) + 'px'" [attr.width]="width + 'px'">
|
||||
<defs>
|
||||
<marker id="input-arrow" viewBox="-5 -5 10 10"
|
||||
refX="0" refY="0"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="1.5" markerHeight="1"
|
||||
orient="auto">
|
||||
<path d="M -5 -5 L 0 0 L -5 5 L 1 5 L 1 -5 Z" stroke-width="0" [attr.fill]="gradient[0]"/>
|
||||
</marker>
|
||||
<marker id="output-arrow" viewBox="-5 -5 10 10"
|
||||
refX="0" refY="0"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="1.5" markerHeight="1"
|
||||
orient="auto">
|
||||
<path d="M 1 -5 L 0 -5 L -5 0 L 0 5 L 1 5 Z" stroke-width="0" [attr.fill]="gradient[0]"/>
|
||||
</marker>
|
||||
<marker id="fee-arrow" viewBox="-5 -5 10 10"
|
||||
refX="0" refY="0"
|
||||
markerUnits="strokeWidth"
|
||||
markerWidth="1.5" markerHeight="1"
|
||||
orient="auto">
|
||||
</marker>
|
||||
<linearGradient id="input-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[0]" />
|
||||
<stop offset="100%" [attr.stop-color]="gradient[1]" />
|
||||
</linearGradient>
|
||||
<linearGradient id="output-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
||||
<stop offset="100%" [attr.stop-color]="gradient[0]" />
|
||||
</linearGradient>
|
||||
<linearGradient id="fee-gradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" [attr.stop-color]="gradient[1]" />
|
||||
<stop offset="50%" [attr.stop-color]="gradient[1]" />
|
||||
<stop offset="100%" stop-color="transparent" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path [attr.d]="middle.path" class="line middle" [style]="middle.style"/>
|
||||
<ng-container *ngFor="let input of inputs">
|
||||
<path [attr.d]="input.path" class="line {{input.class}}" [style]="input.style" attr.marker-start="url(#{{input.class}}-arrow)"/>
|
||||
</ng-container>
|
||||
<ng-container *ngFor="let output of outputs">
|
||||
<path [attr.d]="output.path" class="line {{output.class}}" [style]="output.style" attr.marker-start="url(#{{output.class}}-arrow)" />
|
||||
</ng-container>
|
||||
</svg>
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,15 @@
|
||||
.bowtie {
|
||||
.line {
|
||||
fill: none;
|
||||
|
||||
&.input {
|
||||
stroke: url(#input-gradient);
|
||||
}
|
||||
&.output {
|
||||
stroke: url(#output-gradient);
|
||||
}
|
||||
&.fee {
|
||||
stroke: url(#fee-gradient);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
import { Component, OnInit, Input, OnChanges } from '@angular/core';
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
|
||||
interface SvgLine {
|
||||
path: string;
|
||||
style: string;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tx-bowtie-graph',
|
||||
templateUrl: './tx-bowtie-graph.component.html',
|
||||
styleUrls: ['./tx-bowtie-graph.component.scss'],
|
||||
})
|
||||
export class TxBowtieGraphComponent implements OnInit, OnChanges {
|
||||
@Input() tx: Transaction;
|
||||
@Input() network: string;
|
||||
@Input() width = 1200;
|
||||
@Input() height = 600;
|
||||
@Input() combinedWeight = 100;
|
||||
@Input() minWeight = 2; //
|
||||
@Input() maxStrands = 24; // number of inputs/outputs to keep fully on-screen.
|
||||
|
||||
inputs: SvgLine[];
|
||||
outputs: SvgLine[];
|
||||
middle: SvgLine;
|
||||
isLiquid: boolean = false;
|
||||
|
||||
gradientColors = {
|
||||
'': ['#9339f4', '#105fb0'],
|
||||
bisq: ['#9339f4', '#105fb0'],
|
||||
// liquid: ['#116761', '#183550'],
|
||||
liquid: ['#09a197', '#0f62af'],
|
||||
// 'liquidtestnet': ['#494a4a', '#272e46'],
|
||||
'liquidtestnet': ['#d2d2d2', '#979797'],
|
||||
// testnet: ['#1d486f', '#183550'],
|
||||
testnet: ['#4edf77', '#10a0af'],
|
||||
// signet: ['#6f1d5d', '#471850'],
|
||||
signet: ['#d24fc8', '#a84fd2'],
|
||||
};
|
||||
|
||||
gradient: string[] = ['#105fb0', '#105fb0'];
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
|
||||
this.gradient = this.gradientColors[this.network];
|
||||
this.initGraph();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.isLiquid = (this.network === 'liquid' || this.network === 'liquidtestnet');
|
||||
this.gradient = this.gradientColors[this.network];
|
||||
this.initGraph();
|
||||
}
|
||||
|
||||
initGraph(): void {
|
||||
const totalValue = this.calcTotalValue(this.tx);
|
||||
const voutWithFee = this.tx.vout.map(v => { return { type: v.scriptpubkey_type === 'fee' ? 'fee' : 'output', value: v?.value }; });
|
||||
|
||||
if (this.tx.fee && !this.isLiquid) {
|
||||
voutWithFee.unshift({ type: 'fee', value: this.tx.fee });
|
||||
}
|
||||
|
||||
this.inputs = this.initLines('in', this.tx.vin.map(v => { return {type: 'input', value: v?.prevout?.value }; }), totalValue, this.maxStrands);
|
||||
this.outputs = this.initLines('out', voutWithFee, totalValue, this.maxStrands);
|
||||
|
||||
this.middle = {
|
||||
path: `M ${(this.width / 2) - 50} ${(this.height / 2) + 0.5} L ${(this.width / 2) + 50} ${(this.height / 2) + 0.5}`,
|
||||
style: `stroke-width: ${this.combinedWeight + 0.5}; stroke: ${this.gradient[1]}`
|
||||
};
|
||||
}
|
||||
|
||||
calcTotalValue(tx: Transaction): number {
|
||||
const totalOutput = this.tx.vout.reduce((acc, v) => (v.value == null ? 0 : v.value) + acc, 0);
|
||||
// simple sum of outputs + fee for bitcoin
|
||||
if (!this.isLiquid) {
|
||||
return this.tx.fee ? totalOutput + this.tx.fee : totalOutput;
|
||||
} else {
|
||||
const totalInput = this.tx.vin.reduce((acc, v) => (v?.prevout?.value == null ? 0 : v.prevout.value) + acc, 0);
|
||||
const confidentialInputCount = this.tx.vin.reduce((acc, v) => acc + (v?.prevout?.value == null ? 1 : 0), 0);
|
||||
const confidentialOutputCount = this.tx.vout.reduce((acc, v) => acc + (v.value == null ? 1 : 0), 0);
|
||||
|
||||
// if there are unknowns on both sides, the total is indeterminate, so we'll just fudge it
|
||||
if (confidentialInputCount && confidentialOutputCount) {
|
||||
const knownInputCount = (tx.vin.length - confidentialInputCount) || 1;
|
||||
const knownOutputCount = (tx.vout.length - confidentialOutputCount) || 1;
|
||||
// assume confidential inputs/outputs have the same average value as the known ones
|
||||
const adjustedTotalInput = totalInput + ((totalInput / knownInputCount) * confidentialInputCount);
|
||||
const adjustedTotalOutput = totalOutput + ((totalOutput / knownOutputCount) * confidentialOutputCount);
|
||||
return Math.max(adjustedTotalInput, adjustedTotalOutput) || 1;
|
||||
} else {
|
||||
// otherwise knowing the actual total of one side suffices
|
||||
return Math.max(totalInput, totalOutput) || 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initLines(side: 'in' | 'out', xputs: { type: string, value: number | void }[], total: number, maxVisibleStrands: number): SvgLine[] {
|
||||
const lines = [];
|
||||
let unknownCount = 0;
|
||||
let unknownTotal = total == null ? this.combinedWeight : total;
|
||||
xputs.forEach(put => {
|
||||
if (put.value == null) {
|
||||
unknownCount++;
|
||||
} else {
|
||||
unknownTotal -= put.value as number;
|
||||
}
|
||||
});
|
||||
const unknownShare = unknownTotal / unknownCount;
|
||||
|
||||
// conceptual weights
|
||||
const weights = xputs.map((put): number => this.combinedWeight * (put.value == null ? unknownShare : put.value as number) / total);
|
||||
// actual displayed line thicknesses
|
||||
const minWeights = weights.map((w) => Math.max(this.minWeight - 1, w) + 1);
|
||||
const visibleStrands = Math.min(maxVisibleStrands, xputs.length);
|
||||
const visibleWeight = minWeights.slice(0, visibleStrands).reduce((acc, v) => v + acc, 0);
|
||||
const gaps = visibleStrands - 1;
|
||||
|
||||
const innerTop = (this.height / 2) - (this.combinedWeight / 2);
|
||||
const innerBottom = innerTop + this.combinedWeight;
|
||||
// tracks the visual bottom of the endpoints of the previous line
|
||||
let lastOuter = 0;
|
||||
let lastInner = innerTop;
|
||||
// gap between strands
|
||||
const spacing = (this.height - visibleWeight) / gaps;
|
||||
|
||||
for (let i = 0; i < xputs.length; i++) {
|
||||
const weight = weights[i];
|
||||
const minWeight = minWeights[i];
|
||||
// set the vertical position of the (center of the) outer side of the line
|
||||
let outer = lastOuter + (minWeight / 2);
|
||||
const inner = Math.min(innerBottom + (minWeight / 2), Math.max(innerTop + (minWeight / 2), lastInner + (weight / 2)));
|
||||
|
||||
// special case to center single input/outputs
|
||||
if (xputs.length === 1) {
|
||||
outer = (this.height / 2);
|
||||
}
|
||||
|
||||
lastOuter += minWeight + spacing;
|
||||
lastInner += weight;
|
||||
lines.push({
|
||||
path: this.makePath(side, outer, inner, minWeight),
|
||||
style: this.makeStyle(minWeight, xputs[i].type),
|
||||
class: xputs[i].type
|
||||
});
|
||||
}
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
makePath(side: 'in' | 'out', outer: number, inner: number, weight: number): string {
|
||||
const start = side === 'in' ? (weight * 0.5) : this.width - (weight * 0.5);
|
||||
const center = this.width / 2 + (side === 'in' ? -45 : 45 );
|
||||
const midpoint = (start + center) / 2;
|
||||
// correct for svg horizontal gradient bug
|
||||
if (Math.round(outer) === Math.round(inner)) {
|
||||
outer -= 1;
|
||||
}
|
||||
return `M ${start} ${outer} C ${midpoint} ${outer}, ${midpoint} ${inner}, ${center} ${inner}`;
|
||||
}
|
||||
|
||||
makeStyle(minWeight, type): string {
|
||||
if (type === 'fee') {
|
||||
return `stroke-width: ${minWeight}; stroke: url(#fee-gradient)`;
|
||||
} else {
|
||||
return `stroke-width: ${minWeight}`;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@
|
||||
<a [routerLink]="['/lightning/node' | relativeUrl, channel.public_key]" >
|
||||
{{ channel.public_key | shortenString : 12 }}
|
||||
</a>
|
||||
<app-clipboard [text]="channel.node1_public_key"></app-clipboard>
|
||||
<app-clipboard [text]="channel.public_key"></app-clipboard>
|
||||
</div>
|
||||
<div class="box-right">
|
||||
<div class="second-line">{{ channel.channels }} channels</div>
|
||||
@ -51,4 +51,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
||||
|
@ -58,7 +58,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md map-col">
|
||||
<app-nodes-channels-map *ngIf="!error" [style]="'channelpage'" [channel]="channelGeo" [fitContainer]="true" (readyEvent)="onMapReady()"></app-nodes-channels-map>
|
||||
<app-nodes-channels-map *ngIf="!error" [style]="'channelpage'" [channel]="channelGeo" [fitContainer]="true" [placeholder]="true" (readyEvent)="onMapReady()"></app-nodes-channels-map>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -11,6 +11,7 @@
|
||||
<span class="badge rounded-pill badge-secondary" *ngIf="channel.status === 0">Inactive</span>
|
||||
<span class="badge rounded-pill badge-success" *ngIf="channel.status === 1">Active</span>
|
||||
<span class="badge rounded-pill badge-danger" *ngIf="channel.status === 2">Closed</span>
|
||||
<app-closing-type *ngIf="channel.closing_reason" [type]="channel.closing_reason"></app-closing-type>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
@ -30,6 +30,10 @@
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
app-fiat {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
|
@ -87,8 +87,6 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template #skeleton>
|
||||
<h2 class="float-left" i18n="lightning.channels">Channels</h2>
|
||||
|
||||
<table class="table table-borderless">
|
||||
<ng-container *ngTemplateOutlet="tableHeader"></ng-container>
|
||||
<tbody>
|
||||
|
@ -52,7 +52,7 @@
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md map-col">
|
||||
<app-nodes-channels-map *ngIf="!error" [style]="'nodepage'" [publicKey]="node.public_key" [fitContainer]="true" (readyEvent)="onMapReady()"></app-nodes-channels-map>
|
||||
<app-nodes-channels-map *ngIf="!error" [style]="'nodepage'" [publicKey]="node.public_key" [fitContainer]="true" [placeholder]="true" [hasLocation]="!!node.as_number" (readyEvent)="onMapReady()"></app-nodes-channels-map>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div class="container-xl" *ngIf="(node$ | async) as node">
|
||||
<h5 class="mb-0" style="color: #ffffff66" i18n="lightning.node">Lightning node</h5>
|
||||
<div class="title-container mb-2" *ngIf="!error">
|
||||
<h1 class="mb-0">{{ node.alias }}</h1>
|
||||
<h1 class="mb-0 text-truncate">{{ node.alias }}</h1>
|
||||
<span class="tx-link">
|
||||
<a [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">
|
||||
{{ node.public_key | shortenString : publicKeySize }}
|
||||
@ -131,7 +131,6 @@
|
||||
<app-node-statistics-chart [publicKey]="node.public_key"></app-node-statistics-chart>
|
||||
</div>
|
||||
|
||||
<h2 i18n="lightning.active-channels-map">Active channels map</h2>
|
||||
<app-node-channels style="display:block;margin-bottom: 40px" [publicKey]="node.public_key"></app-node-channels>
|
||||
|
||||
<div class="d-flex justify-content-between">
|
||||
|
@ -22,6 +22,7 @@ export class NodesChannelsMap implements OnInit {
|
||||
@Input() channel: any[] = [];
|
||||
@Input() fitContainer = false;
|
||||
@Input() hasLocation = true;
|
||||
@Input() placeholder = false;
|
||||
@Output() readyEvent = new EventEmitter();
|
||||
|
||||
channelsObservable: Observable<any>;
|
||||
@ -201,11 +202,26 @@ export class NodesChannelsMap implements OnInit {
|
||||
|
||||
prepareChartOptions(nodes, channels) {
|
||||
let title: object;
|
||||
if (channels.length === 0) {
|
||||
if (channels.length === 0 && !this.placeholder) {
|
||||
this.chartOptions = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// empty map fallback
|
||||
if (channels.length === 0 && this.placeholder) {
|
||||
title = {
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
fontSize: 18
|
||||
},
|
||||
text: $localize`No geolocation data available`,
|
||||
left: 'center',
|
||||
top: 'center'
|
||||
};
|
||||
this.zoom = 1.5;
|
||||
this.center = [0, 20];
|
||||
}
|
||||
|
||||
this.chartOptions = {
|
||||
silent: this.style === 'widget',
|
||||
title: title ?? undefined,
|
||||
|
@ -1,2 +1,9 @@
|
||||
<div *ngIf="channelsObservable$ | async" echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)">
|
||||
<div *ngIf="channelsObservable$ | async" style="min-height: 455px">
|
||||
<h2 i18n="lightning.active-channels-map">Active channels map</h2>
|
||||
<div echarts [initOpts]="chartInitOptions" [options]="chartOptions" (chartInit)="onChartInit($event)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isLoading" class="text-center loading-spinner">
|
||||
<div class="spinner-border text-light"></div>
|
||||
</div>
|
||||
|
@ -0,0 +1,9 @@
|
||||
.loading-spinner {
|
||||
min-height: 455px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
position: relative;
|
||||
top: 225px;
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
import { formatNumber } from '@angular/common';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnChanges, OnInit } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnChanges } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { ECharts, EChartsOption, TreemapSeriesOption } from 'echarts';
|
||||
import { Observable, tap } from 'rxjs';
|
||||
import { Observable, share, switchMap, tap } from 'rxjs';
|
||||
import { lerpColor } from 'src/app/shared/graphs.utils';
|
||||
import { AmountShortenerPipe } from 'src/app/shared/pipes/amount-shortener.pipe';
|
||||
import { LightningApiService } from '../lightning-api.service';
|
||||
@ -25,7 +25,7 @@ export class NodeChannels implements OnChanges {
|
||||
};
|
||||
|
||||
channelsObservable$: Observable<any>;
|
||||
isLoading: true;
|
||||
isLoading = true;
|
||||
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) public locale: string,
|
||||
@ -41,9 +41,20 @@ export class NodeChannels implements OnChanges {
|
||||
|
||||
this.channelsObservable$ = this.lightningApiService.getChannelsByNodeId$(this.publicKey, -1, 'active')
|
||||
.pipe(
|
||||
tap((response) => {
|
||||
const biggestCapacity = response.body[0].capacity;
|
||||
this.prepareChartOptions(response.body.map(channel => {
|
||||
switchMap((response) => {
|
||||
this.isLoading = true;
|
||||
if ((response.body?.length ?? 0) <= 0) {
|
||||
return [];
|
||||
}
|
||||
return [response.body];
|
||||
}),
|
||||
tap((body: any[]) => {
|
||||
if (body.length === 0) {
|
||||
this.isLoading = false;
|
||||
return;
|
||||
}
|
||||
const biggestCapacity = body[0].capacity;
|
||||
this.prepareChartOptions(body.map(channel => {
|
||||
return {
|
||||
name: channel.node.alias,
|
||||
value: channel.capacity,
|
||||
@ -54,7 +65,9 @@ export class NodeChannels implements OnChanges {
|
||||
}
|
||||
};
|
||||
}));
|
||||
})
|
||||
this.isLoading = false;
|
||||
}),
|
||||
share(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,12 @@ export class StateService {
|
||||
if (this.env.BASE_MODULE !== 'mempool' && this.env.BASE_MODULE !== 'liquid') {
|
||||
return;
|
||||
}
|
||||
const networkMatches = url.match(/^\/(bisq|testnet|liquidtestnet|liquid|signet)/);
|
||||
// horrible network regex breakdown:
|
||||
// /^\/ starts with a forward slash...
|
||||
// (?:[a-z]{2}(?:-[A-Z]{2})?\/)? optional locale prefix (non-capturing)
|
||||
// (?:preview\/)? optional "preview" prefix (non-capturing)
|
||||
// (bisq|testnet|liquidtestnet|liquid|signet)/ network string (captured as networkMatches[1])
|
||||
const networkMatches = url.match(/^\/(?:[a-z]{2}(?:-[A-Z]{2})?\/)?(?:preview\/)?(bisq|testnet|liquidtestnet|liquid|signet)/);
|
||||
switch (networkMatches && networkMatches[1]) {
|
||||
case 'liquid':
|
||||
if (this.network !== 'liquid') {
|
||||
|
@ -63,6 +63,7 @@ import { StatusViewComponent } from '../components/status-view/status-view.compo
|
||||
import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
|
||||
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
|
||||
import { TermsOfServiceComponent } from '../components/terms-of-service/terms-of-service.component';
|
||||
import { TxBowtieGraphComponent } from '../components/tx-bowtie-graph/tx-bowtie-graph.component';
|
||||
import { PrivacyPolicyComponent } from '../components/privacy-policy/privacy-policy.component';
|
||||
import { TrademarkPolicyComponent } from '../components/trademark-policy/trademark-policy.component';
|
||||
import { PushTransactionComponent } from '../components/push-transaction/push-transaction.component';
|
||||
@ -138,6 +139,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
|
||||
StatusViewComponent,
|
||||
FeesBoxComponent,
|
||||
DifficultyComponent,
|
||||
TxBowtieGraphComponent,
|
||||
TermsOfServiceComponent,
|
||||
PrivacyPolicyComponent,
|
||||
TrademarkPolicyComponent,
|
||||
@ -242,6 +244,7 @@ import { GeolocationComponent } from '../shared/components/geolocation/geolocati
|
||||
StatusViewComponent,
|
||||
FeesBoxComponent,
|
||||
DifficultyComponent,
|
||||
TxBowtieGraphComponent,
|
||||
TermsOfServiceComponent,
|
||||
PrivacyPolicyComponent,
|
||||
TrademarkPolicyComponent,
|
||||
|
@ -1287,9 +1287,9 @@ case $OS in
|
||||
osPackageInstall ${CLN_PKG}
|
||||
|
||||
echo "[*] Installing Core Lightning mainnet Cronjob"
|
||||
crontab_cln+='@reboot sleep 30 ; screen -dmS main lightningd --alias `hostname` --bitcoin-datadir /bitcoin\n'
|
||||
crontab_cln+='@reboot sleep 60 ; screen -dmS sig lightningd --alias `hostname` --bitcoin-datadir /bitcoin --network signet\n'
|
||||
crontab_cln+='@reboot sleep 60 ; screen -dmS main lightningd --alias `hostname` --bitcoin-datadir /bitcoin\n'
|
||||
crontab_cln+='@reboot sleep 90 ; screen -dmS tes lightningd --alias `hostname` --bitcoin-datadir /bitcoin --network testnet\n'
|
||||
crontab_cln+='@reboot sleep 120 ; screen -dmS sig lightningd --alias `hostname` --bitcoin-datadir /bitcoin --network signet\n'
|
||||
echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
||||
;;
|
||||
Debian)
|
||||
|
@ -12,7 +12,10 @@ ELEMENTS_RPC_USER=$(grep '^rpcuser' /elements/elements.conf | cut -d '=' -f2)
|
||||
ELEMENTS_RPC_PASS=$(grep '^rpcpassword' /elements/elements.conf | cut -d '=' -f2)
|
||||
|
||||
# get mysql credentials
|
||||
. /mempool/mysql_credentials
|
||||
MYSQL_CRED_FILE=${HOME}/mempool/mysql_credentials
|
||||
if [ -f "${MYSQL_CRED_FILE}" ];then
|
||||
. ${MYSQL_CRED_FILE}
|
||||
fi
|
||||
|
||||
if [ -f "${LOCKFILE}" ];then
|
||||
echo "upgrade already running? check lockfile ${LOCKFILE}"
|
||||
@ -63,6 +66,19 @@ build_frontend()
|
||||
npm run build || exit 1
|
||||
}
|
||||
|
||||
build_unfurler()
|
||||
{
|
||||
local site="$1"
|
||||
echo "[*] Building unfurler for ${site}"
|
||||
[ -z "${HASH}" ] && exit 1
|
||||
cd "$HOME/${site}/unfurler" || exit 1
|
||||
if [ ! -e "config.json" ];then
|
||||
cp "${HOME}/mempool/production/unfurler-config.${site}.json" "config.json"
|
||||
fi
|
||||
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install || exit 1
|
||||
npm run build || exit 1
|
||||
}
|
||||
|
||||
build_backend()
|
||||
{
|
||||
local site="$1"
|
||||
@ -128,6 +144,11 @@ for repo in $backend_repos;do
|
||||
update_repo "${repo}"
|
||||
done
|
||||
|
||||
# build unfurlers
|
||||
for repo in mainnet liquid;do
|
||||
build_unfurler "${repo}"
|
||||
done
|
||||
|
||||
# build backends
|
||||
for repo in $backend_repos;do
|
||||
build_backend "${repo}"
|
||||
|
@ -1,2 +1,8 @@
|
||||
#!/usr/bin/env zsh
|
||||
killall sh node
|
||||
killall sh
|
||||
killall node
|
||||
killall chrome
|
||||
killall xinit
|
||||
for pid in `ps uaxww|grep warmer|grep zsh|awk '{print $2}'`;do
|
||||
kill $pid
|
||||
done
|
||||
|
@ -2,7 +2,29 @@
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
source "$NVM_DIR/nvm.sh"
|
||||
|
||||
# start all mempool backends that exist
|
||||
for site in mainnet mainnet-lightning testnet testnet-lightning signet signet-lightning bisq liquid liquidtestnet;do
|
||||
cd "${HOME}/${site}/backend/" && \
|
||||
echo "starting mempool backend: ${site}" && \
|
||||
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
|
||||
done
|
||||
|
||||
# only start unfurler if GPU present
|
||||
if pciconf -lv|grep -i nvidia >/dev/null 2>&1;then
|
||||
export DISPLAY=:0
|
||||
screen -dmS x startx
|
||||
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
|
||||
|
||||
# start nginx warm cacher
|
||||
for site in mainnet;do
|
||||
echo "starting mempool cache warmer: ${site}"
|
||||
screen -dmS "warmer-${site}" $HOME/mempool/production/nginx-cache-warmer
|
||||
done
|
||||
|
||||
exit 0
|
||||
|
@ -2,6 +2,12 @@
|
||||
hostname=$(hostname)
|
||||
slugs=(`curl -sSL https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json | jq -r '.slugs[]'`)
|
||||
|
||||
warm()
|
||||
{
|
||||
echo "$1"
|
||||
curl -i -s "$1" | head -1
|
||||
}
|
||||
|
||||
while true
|
||||
do for url in / \
|
||||
'/api/v1/blocks' \
|
||||
@ -81,14 +87,14 @@ do for url in / \
|
||||
'/api/v1/lightning/channels-geo?style=graph' \
|
||||
|
||||
do
|
||||
curl -s "https://${hostname}${url}" >/dev/null
|
||||
warm "https://${hostname}${url}"
|
||||
done
|
||||
|
||||
for slug in $slugs
|
||||
do
|
||||
curl -s "https://${hostname}/api/v1/mining/pool/${slug}" >/dev/null
|
||||
curl -s "https://${hostname}/api/v1/mining/pool/${slug}/hashrate" >/dev/null
|
||||
curl -s "https://${hostname}/api/v1/mining/pool/${slug}/blocks" >/dev/null
|
||||
warm "https://${hostname}/api/v1/mining/pool/${slug}"
|
||||
warm "https://${hostname}/api/v1/mining/pool/${slug}/hashrate"
|
||||
warm "https://${hostname}/api/v1/mining/pool/${slug}/blocks"
|
||||
done
|
||||
|
||||
sleep 10
|
||||
|
@ -1,62 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:$HOME/bin
|
||||
HOSTNAME=$(hostname)
|
||||
LOCATION=$(hostname|cut -d . -f2)
|
||||
LOCKFILE="${HOME}/lock"
|
||||
REF=$(echo "${1:=origin/master}"|sed -e 's!:!/!')
|
||||
|
||||
if [ -f "${LOCKFILE}" ];then
|
||||
echo "upgrade already running? check lockfile ${LOCKFILE}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# on exit, remove lockfile but preserve exit code
|
||||
trap "rv=\$?; rm -f "${LOCKFILE}"; exit \$rv" INT TERM EXIT
|
||||
|
||||
# create lockfile
|
||||
touch "${LOCKFILE}"
|
||||
|
||||
# notify logged in users
|
||||
echo "Upgrading unfurler to ${REF}" | wall
|
||||
|
||||
update_repo()
|
||||
{
|
||||
echo "[*] Upgrading unfurler to ${REF}"
|
||||
cd "$HOME/unfurl/unfurler" || exit 1
|
||||
|
||||
git fetch origin || exit 1
|
||||
for remote in origin;do
|
||||
git remote add "${remote}" "https://github.com/${remote}/mempool" >/dev/null 2>&1
|
||||
git fetch "${remote}" || exit 1
|
||||
done
|
||||
|
||||
if [ $(git tag -l "${REF}") ];then
|
||||
git reset --hard "tags/${REF}" || exit 1
|
||||
elif [ $(git branch -r -l "origin/${REF}") ];then
|
||||
git reset --hard "origin/${REF}" || exit 1
|
||||
else
|
||||
git reset --hard "${REF}" || exit 1
|
||||
fi
|
||||
export HASH=$(git rev-parse HEAD)
|
||||
}
|
||||
|
||||
build_backend()
|
||||
{
|
||||
echo "[*] Building backend for unfurler"
|
||||
[ -z "${HASH}" ] && exit 1
|
||||
cd "$HOME/unfurl/unfurler" || exit 1
|
||||
if [ ! -e "config.json" ];then
|
||||
cp "${HOME}/unfurl/production/mempool-config.unfurl.json" "config.json"
|
||||
fi
|
||||
npm install || exit 1
|
||||
npm run build || exit 1
|
||||
}
|
||||
|
||||
update_repo
|
||||
build_backend
|
||||
|
||||
# notify everyone
|
||||
echo "${HOSTNAME} unfurl updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general mempool.dev
|
||||
echo "${HOSTNAME} unfurl updated to \`${REF}\` @ \`${HASH}\`" | /usr/local/bin/keybase chat send --nonblock --channel general "mempool.ops.${LOCATION}"
|
||||
|
||||
exit 0
|
@ -1,2 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
killall sh node
|
@ -1,6 +0,0 @@
|
||||
#!/usr/bin/env zsh
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
source "$NVM_DIR/nvm.sh"
|
||||
|
||||
cd "${HOME}/unfurl/unfurler/" && \
|
||||
screen -dmS "unfurl" sh -c 'while true;do npm run start-production;sleep 1;done'
|
17
production/unfurler-config.liquid.json
Normal file
17
production/unfurler-config.liquid.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"SERVER": {
|
||||
"HOST": "https://liquid.network",
|
||||
"HTTP_PORT": 8002
|
||||
},
|
||||
"MEMPOOL": {
|
||||
"HTTP_HOST": "https://liquid.network",
|
||||
"HTTP_PORT": 443,
|
||||
"NETWORK": "liquid"
|
||||
},
|
||||
"PUPPETEER": {
|
||||
"CLUSTER_SIZE": 8,
|
||||
"EXEC_PATH": "/usr/local/bin/chrome",
|
||||
"MAX_PAGE_AGE": 86400,
|
||||
"RENDER_TIMEOUT": 3000
|
||||
}
|
||||
}
|
17
production/unfurler-config.mainnet.json
Normal file
17
production/unfurler-config.mainnet.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"SERVER": {
|
||||
"HOST": "https://mempool.space",
|
||||
"HTTP_PORT": 8001
|
||||
},
|
||||
"MEMPOOL": {
|
||||
"HTTP_HOST": "https://mempool.space",
|
||||
"HTTP_PORT": 443,
|
||||
"NETWORK": "bitcoin"
|
||||
},
|
||||
"PUPPETEER": {
|
||||
"CLUSTER_SIZE": 8,
|
||||
"EXEC_PATH": "/usr/local/bin/chrome",
|
||||
"MAX_PAGE_AGE": 86400,
|
||||
"RENDER_TIMEOUT": 3000
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@
|
||||
"tsc": "./node_modules/typescript/bin/tsc",
|
||||
"build": "npm run tsc",
|
||||
"start": "node --max-old-space-size=2048 dist/index.js",
|
||||
"start-production": "node --max-old-space-size=4096 dist/index.js",
|
||||
"unfurler": "node --max-old-space-size=4096 dist/index.js",
|
||||
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
||||
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
||||
"prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
|
||||
|
@ -41,6 +41,6 @@
|
||||
"--use-mock-keychain",
|
||||
"--ignore-gpu-blacklist",
|
||||
"--ignore-gpu-blocklist",
|
||||
"--use-gl=swiftshader"
|
||||
"--use-gl=egl"
|
||||
]
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user