Compare commits

..

2 Commits

Author SHA1 Message Date
wiz
ef78b252f3 Merge branch 'master' into mononaut/fee-widget-gradient 2023-05-09 12:51:09 -05:00
Mononaut
db4886fb41 fix recommended fee gradient 2023-05-08 13:06:56 -06:00
10 changed files with 119 additions and 310 deletions

View File

@@ -130,9 +130,8 @@ class BitcoinRoutes {
private getInitData(req: Request, res: Response) {
try {
const result = websocketHandler.getSerializedInitData();
res.set('Content-Type', 'application/json');
res.send(result);
const result = websocketHandler.getInitData();
res.json(result);
} catch (e) {
res.status(500).send(e instanceof Error ? e.message : e);
}

View File

@@ -34,12 +34,11 @@ export function calcDifficultyAdjustment(
const remainingBlocks = EPOCH_BLOCK_LENGTH - blocksInEpoch;
const nextRetargetHeight = (blockHeight >= 0) ? blockHeight + remainingBlocks : 0;
const expectedBlocks = diffSeconds / BLOCK_SECONDS_TARGET;
const actualTimespan = (blocksInEpoch === 2015 ? latestBlockTimestamp : nowSeconds) - DATime;
let difficultyChange = 0;
let timeAvgSecs = blocksInEpoch ? diffSeconds / blocksInEpoch : BLOCK_SECONDS_TARGET;
difficultyChange = (BLOCK_SECONDS_TARGET / (actualTimespan / (blocksInEpoch + 1)) - 1) * 100;
difficultyChange = (BLOCK_SECONDS_TARGET / timeAvgSecs - 1) * 100;
// Max increase is x4 (+300%)
if (difficultyChange > 300) {
difficultyChange = 300;

View File

@@ -30,9 +30,6 @@ class WebsocketHandler {
private numConnected = 0;
private numDisconnected = 0;
private initData: { [key: string]: string } = {};
private serializedInitData: string = '{}';
constructor() { }
setWebsocketServer(wss: WebSocket.Server) {
@@ -41,41 +38,6 @@ class WebsocketHandler {
setExtraInitProperties(property: string, value: any) {
this.extraInitProperties[property] = value;
this.setInitDataFields(this.extraInitProperties);
}
private setInitDataFields(data: { [property: string]: any }): void {
for (const property of Object.keys(data)) {
if (data[property] != null) {
this.initData[property] = JSON.stringify(data[property]);
} else {
delete this.initData[property];
}
}
this.serializedInitData = '{'
+ Object.keys(this.initData).map(key => `"${key}": ${this.initData[key]}`).join(', ')
+ '}';
}
private updateInitData(): void {
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
const da = difficultyAdjustment.getDifficultyAdjustment();
this.setInitDataFields({
'mempoolInfo': memPool.getMempoolInfo(),
'vBytesPerSecond': memPool.getVBytesPerSecond(),
'blocks': _blocks,
'conversions': priceUpdater.getLatestPrices(),
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
'transactions': memPool.getLatestTransactions(),
'backendInfo': backendInfo.getBackendInfo(),
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
'da': da?.previousTime ? da : undefined,
'fees': feeApi.getRecommendedFee(),
});
}
public getSerializedInitData(): string {
return this.serializedInitData;
}
setupConnectionHandling() {
@@ -195,13 +157,11 @@ class WebsocketHandler {
}
if (parsedMessage.action === 'init') {
if (!this.initData['blocks']?.length || !this.initData['da']) {
this.updateInitData();
}
if (!this.initData['blocks']?.length) {
const _blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
if (!_blocks) {
return;
}
client.send(this.serializedInitData);
client.send(JSON.stringify(this.getInitData(_blocks)));
}
if (parsedMessage.action === 'ping') {
@@ -250,14 +210,11 @@ class WebsocketHandler {
throw new Error('WebSocket.Server is not set');
}
this.setInitDataFields({ 'loadingIndicators': indicators });
const response = JSON.stringify({ loadingIndicators: indicators });
this.wss.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
client.send(response);
client.send(JSON.stringify({ loadingIndicators: indicators }));
});
}
@@ -266,17 +223,34 @@ class WebsocketHandler {
throw new Error('WebSocket.Server is not set');
}
this.setInitDataFields({ 'conversions': conversionRates });
const response = JSON.stringify({ conversions: conversionRates });
this.wss.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
}
client.send(response);
client.send(JSON.stringify({ conversions: conversionRates }));
});
}
getInitData(_blocks?: BlockExtended[]) {
if (!_blocks) {
_blocks = blocks.getBlocks().slice(-config.MEMPOOL.INITIAL_BLOCKS_AMOUNT);
}
const da = difficultyAdjustment.getDifficultyAdjustment();
return {
'mempoolInfo': memPool.getMempoolInfo(),
'vBytesPerSecond': memPool.getVBytesPerSecond(),
'blocks': _blocks,
'conversions': priceUpdater.getLatestPrices(),
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
'transactions': memPool.getLatestTransactions(),
'backendInfo': backendInfo.getBackendInfo(),
'loadingIndicators': loadingIndicators.getLoadingIndicators(),
'da': da?.previousTime ? da : undefined,
'fees': feeApi.getRecommendedFee(),
...this.extraInitProperties
};
}
handleNewStatistic(stats: OptimizedStatistic) {
if (!this.wss) {
throw new Error('WebSocket.Server is not set');
@@ -284,10 +258,6 @@ class WebsocketHandler {
this.printLogs();
const response = JSON.stringify({
'live-2h-chart': stats
});
this.wss.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
@@ -297,7 +267,9 @@ class WebsocketHandler {
return;
}
client.send(response);
client.send(JSON.stringify({
'live-2h-chart': stats
}));
});
}
@@ -334,43 +306,6 @@ class WebsocketHandler {
}
const recommendedFees = feeApi.getRecommendedFee();
// update init data
this.updateInitData();
// cache serialized objects to avoid stringify-ing the same thing for every client
const responseCache = { ...this.initData };
function getCachedResponse(key: string, data): string {
if (!responseCache[key]) {
responseCache[key] = JSON.stringify(data);
}
return responseCache[key];
}
// pre-compute new tracked outspends
const outspendCache: { [txid: string]: { [vout: number]: { vin: number, txid: string } } } = {};
const trackedTxs = new Set<string>();
this.wss.clients.forEach((client) => {
if (client['track-tx']) {
trackedTxs.add(client['track-tx']);
}
});
if (trackedTxs.size > 0) {
for (const tx of newTransactions) {
for (let i = 0; i < tx.vin.length; i++) {
const vin = tx.vin[i];
if (trackedTxs.has(vin.txid)) {
if (!outspendCache[vin.txid]) {
outspendCache[vin.txid] = { [vin.vout]: { vin: i, txid: tx.txid }};
} else {
outspendCache[vin.txid][vin.vout] = { vin: i, txid: tx.txid };
}
}
}
}
}
const latestTransactions = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
this.wss.clients.forEach(async (client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
@@ -379,17 +314,17 @@ class WebsocketHandler {
const response = {};
if (client['want-stats']) {
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
response['vBytesPerSecond'] = getCachedResponse('vBytesPerSecond', vBytesPerSecond);
response['transactions'] = getCachedResponse('transactions', latestTransactions);
response['mempoolInfo'] = mempoolInfo;
response['vBytesPerSecond'] = vBytesPerSecond;
response['transactions'] = newTransactions.slice(0, 6).map((tx) => Common.stripTransaction(tx));
if (da?.previousTime) {
response['da'] = getCachedResponse('da', da);
response['da'] = da;
}
response['fees'] = getCachedResponse('fees', recommendedFees);
response['fees'] = recommendedFees;
}
if (client['want-mempool-blocks']) {
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
response['mempool-blocks'] = mBlocks;
}
if (client['track-mempool-tx']) {
@@ -398,12 +333,12 @@ class WebsocketHandler {
if (config.MEMPOOL.BACKEND !== 'esplora') {
try {
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
response['tx'] = JSON.stringify(fullTx);
response['tx'] = fullTx;
} catch (e) {
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
}
} else {
response['tx'] = JSON.stringify(tx);
response['tx'] = tx;
}
client['track-mempool-tx'] = null;
}
@@ -443,7 +378,7 @@ class WebsocketHandler {
}
if (foundTransactions.length) {
response['address-transactions'] = JSON.stringify(foundTransactions);
response['address-transactions'] = foundTransactions;
}
}
@@ -472,60 +407,65 @@ class WebsocketHandler {
});
if (foundTransactions.length) {
response['address-transactions'] = JSON.stringify(foundTransactions);
response['address-transactions'] = foundTransactions;
}
}
if (client['track-tx']) {
const trackTxid = client['track-tx'];
const outspends = outspendCache[trackTxid];
const outspends: object = {};
newTransactions.forEach((tx) => tx.vin.forEach((vin, i) => {
if (vin.txid === trackTxid) {
outspends[vin.vout] = {
vin: i,
txid: tx.txid,
};
}
}));
if (outspends && Object.keys(outspends).length) {
response['utxoSpent'] = JSON.stringify(outspends);
if (Object.keys(outspends).length) {
response['utxoSpent'] = outspends;
}
const rbfReplacedBy = rbfCache.getReplacedBy(client['track-tx']);
if (rbfReplacedBy) {
response['rbfTransaction'] = JSON.stringify({
response['rbfTransaction'] = {
txid: rbfReplacedBy,
})
}
}
const rbfChange = rbfChanges.map[client['track-tx']];
if (rbfChange) {
response['rbfInfo'] = JSON.stringify(rbfChanges.trees[rbfChange]);
response['rbfInfo'] = rbfChanges.trees[rbfChange];
}
const mempoolTx = newMempool[trackTxid];
if (mempoolTx && mempoolTx.position) {
response['txPosition'] = JSON.stringify({
response['txPosition'] = {
txid: trackTxid,
position: mempoolTx.position,
});
};
}
}
if (client['track-mempool-block'] >= 0) {
const index = client['track-mempool-block'];
if (mBlockDeltas[index]) {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
response['projected-block-transactions'] = {
index: index,
delta: mBlockDeltas[index],
});
};
}
}
if (client['track-rbf'] === 'all' && rbfReplacements) {
response['rbfLatest'] = getCachedResponse('rbfLatest', rbfReplacements);
response['rbfLatest'] = rbfReplacements;
} else if (client['track-rbf'] === 'fullRbf' && fullRbfReplacements) {
response['rbfLatest'] = getCachedResponse('fullrbfLatest', fullRbfReplacements);
response['rbfLatest'] = fullRbfReplacements;
}
if (Object.keys(response).length) {
const serializedResponse = '{'
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
+ '}';
client.send(serializedResponse);
client.send(JSON.stringify(response));
}
});
}
@@ -616,19 +556,6 @@ class WebsocketHandler {
const da = difficultyAdjustment.getDifficultyAdjustment();
const fees = feeApi.getRecommendedFee();
// update init data
this.updateInitData();
const responseCache = { ...this.initData };
function getCachedResponse(key, data): string {
if (!responseCache[key]) {
responseCache[key] = JSON.stringify(data);
}
return responseCache[key];
}
const mempoolInfo = memPool.getMempoolInfo();
this.wss.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) {
return;
@@ -638,27 +565,28 @@ class WebsocketHandler {
return;
}
const response = {};
response['block'] = getCachedResponse('block', block);
response['mempoolInfo'] = getCachedResponse('mempoolInfo', mempoolInfo);
response['da'] = getCachedResponse('da', da?.previousTime ? da : undefined);
response['fees'] = getCachedResponse('fees', fees);
const response = {
'block': block,
'mempoolInfo': memPool.getMempoolInfo(),
'da': da?.previousTime ? da : undefined,
'fees': fees,
};
if (mBlocks && client['want-mempool-blocks']) {
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
response['mempool-blocks'] = mBlocks;
}
if (client['track-tx']) {
const trackTxid = client['track-tx'];
if (txIds.indexOf(trackTxid) > -1) {
response['txConfirmed'] = 'true';
response['txConfirmed'] = true;
} else {
const mempoolTx = _memPool[trackTxid];
if (mempoolTx && mempoolTx.position) {
response['txPosition'] = JSON.stringify({
response['txPosition'] = {
txid: trackTxid,
position: mempoolTx.position,
});
};
}
}
}
@@ -686,7 +614,7 @@ class WebsocketHandler {
};
});
response['block-transactions'] = JSON.stringify(foundTransactions);
response['block-transactions'] = foundTransactions;
}
}
@@ -723,24 +651,21 @@ class WebsocketHandler {
};
});
response['block-transactions'] = JSON.stringify(foundTransactions);
response['block-transactions'] = foundTransactions;
}
}
if (client['track-mempool-block'] >= 0) {
const index = client['track-mempool-block'];
if (mBlockDeltas && mBlockDeltas[index]) {
response['projected-block-transactions'] = getCachedResponse(`projected-block-transactions-${index}`, {
response['projected-block-transactions'] = {
index: index,
delta: mBlockDeltas[index],
});
};
}
}
const serializedResponse = '{'
+ Object.keys(response).map(key => `"${key}": ${response[key]}`).join(', ')
+ '}';
client.send(serializedResponse);
client.send(JSON.stringify(response));
});
}

View File

@@ -33,7 +33,6 @@
repeatCount="indefinite"/>
</rect>
</svg>
<span *ngIf="lockedIn" class="lock-in-msg" i18n="difficulty-box.adjustment-locked-in">Difficulty adjustment locked in</span>
</div>
<div class="difficulty-stats">
<div class="item">

View File

@@ -172,18 +172,6 @@
width: 100%;
height: 22px;
margin-bottom: 12px;
position: relative;
.lock-in-msg {
position: absolute;
left: 0;
right: 0;
top: 0;
font-size: 12px;
line-height: 22px;
width: 100%;
text-align: center;
}
}
.epoch-blocks {

View File

@@ -54,7 +54,6 @@ export class DifficultyComponent implements OnInit {
expectedHeight: number;
expectedIndex: number;
difference: number;
lockedIn: boolean = false;
shapes: DiffShape[];
tooltipPosition = { x: 0, y: 0 };
@@ -105,7 +104,6 @@ export class DifficultyComponent implements OnInit {
this.currentIndex = this.currentHeight - this.epochStart;
this.expectedIndex = Math.min(this.expectedHeight - this.epochStart, 2016) - 1;
this.difference = this.currentIndex - this.expectedIndex;
this.lockedIn = this.currentIndex === 2015;
this.shapes = [];
this.shapes = this.shapes.concat(this.blocksToShapes(

View File

@@ -26,15 +26,23 @@ export class FeesBoxComponent implements OnInit {
this.recommendedFees$ = this.stateService.recommendedFees$
.pipe(
tap((fees) => {
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl);
let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.economyFee >= feeLvl);
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
const startColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.hourFee >= feeLvl);
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
const lowColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.halfHourFee >= feeLvl);
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
const medColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.fastestFee >= feeLvl);
feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex;
const endColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
const highColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]);
this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`;
this.gradient = `linear-gradient(to right, ${lowColor} 0%, ${medColor} 50%, ${highColor} 100%)`;
this.noPriority = startColor;
}
)

View File

@@ -1,76 +1,22 @@
<footer *ngIf="networkPaths$ | async as networkPaths">
<div class="container-fluid">
<div class="row main">
<div class="col-sm-4 branding">
<h5>The Mempool Open Source Project™</h5>
<p>Explore the full Bitcoin ecosystem.</p>
<div class="selector">
<app-language-selector></app-language-selector>
</div>
<div class="selector">
<app-fiat-selector></app-fiat-selector>
</div>
<a class="cta btn btn-purple sponsor" [routerLink]="['/signup' | relativeUrl]">Sign In / Sign Up</a>
<p class="cta-secondary"><a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a></p>
<p class="cta-secondary"><a [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]">Connect to Mempool Nodes</a></p>
</div>
<div class="col-sm-8 links">
<div class="row">
<div class="col-sm-4">
<p class="category">Explore</p>
<p><a [routerLink]="['/mining' | relativeUrl]">Mining Dashboard</a></p>
<p><a [routerLink]="['/lightning' | relativeUrl]">Lightning Dashboard</a></p>
<p><a [routerLink]="['/blocks' | relativeUrl]">Recent Blocks</a></p>
<p><a [routerLink]="['/rbf' | relativeUrl]">Recent RBF Transactions</a></p>
</div>
<div class="col-sm-4 links">
<p class="category">Learn</p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool">What is a mempool?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-block-explorer">What is a block explorer?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="what-is-a-mempool-explorer">What is a mempool explorer?</a></p>
<p><a [routerLink]="['/docs/faq']" fragment="why-is-transaction-stuck-in-mempool">Why isn't my transaction confirming?</a></p>
<p><a [routerLink]="['/docs/faq' | relativeUrl]">More FAQs </a></p>
</div>
<div class="col-sm-4 links">
<p class="category">Connect</p>
<p><a href="https://github.com/mempool" target="_blank">GitHub</a></p>
<p><a href="https://twitter.com/mempool" target="_blank">Twitter</a></p>
<p><a href="nostr:npub18d4r6wanxkyrdfjdrjqzj2ukua5cas669ew2g5w7lf4a8te7awzqey6lt3" target="_blank">Nostr</a></p>
<p><a href="https://youtube.com/@mempool" target="_blank">YouTube</a></p>
<p><a href="https://bitcointv.com/c/mempool/videos" target="_blank">BitcoinTV</a></p>
<p><a href="https://mempool.chat" target="_blank">Matrix</a></p>
</div>
</div>
<div class="row">
<div class="col-sm-4 links">
<p class="category">Resources</p>
<p><a [routerLink]="['/docs/faq' | relativeUrl]">FAQs</a></p>
<p><a [routerLink]="['/docs/api' | relativeUrl]">API Documentation</a></p>
<p><a [routerLink]="['/about' | relativeUrl]">About the Project</a></p>
</div>
<div class="col-sm-4 links">
<p class="category">More Networks</p>
<p><a [routerLink]="['/testnet' | relativeUrl]">Testnet Block Explorer</a></p>
<p><a [routerLink]="['/signet' | relativeUrl]">Signet Block Explorer</a></p>
<p><a href="https://liquid.network">Liquid Block Explorer</a></p>
<p><a href="https://bisq.network">Bisq Block Explorer</a></p>
</div>
<div class="col-sm-4 links">
<p class="category">Legal</p>
<p><a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a></p>
<p><a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a></p>
<p><a [routerLink]="['/trademark-policy']">Trademark Policy</a></p>
</div>
</div>
</div>
<div class="pref-selectors">
<div class="selector">
<app-language-selector></app-language-selector>
</div>
<div class="row version">
<div class="col-sm-12">
<p *ngIf="officialMempoolSpace">{{ (backendInfo$ | async)?.hostname }} (v{{ (backendInfo$ | async )?.version }}) [<a target="_blank" href="https://github.com/mempool/mempool/commit/{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}">{{ (backendInfo$ | async )?.gitCommit | slice:0:8 }}</a>]</p>
<p *ngIf="!officialMempoolSpace">v{{ packetJsonVersion }} [<a target="_blank" href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]</p>
</div>
<div class="selector">
<app-fiat-selector></app-fiat-selector>
</div>
</div>
</footer>
<div class="terms-of-service">
<a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a>
|
<a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a>
|
<a *ngIf="officialMempoolSpace && networkPaths['mainnet'] === '/lightning' else broadcastTransaction" [routerLink]="['/lightning/group/the-mempool-open-source-project' | relativeUrl]">Connect to our nodes</a>
<ng-template #broadcastTransaction>
<a [routerLink]="['/tx/push' | relativeUrl]" i18n="shared.broadcast-transaction|Broadcast Transaction">Broadcast Transaction</a>
</ng-template>
</div>
<br>
</footer>

View File

@@ -1,70 +1,22 @@
footer {
background-color: #1d1f31;
margin-top: 30px;
}
footer a {
color: rgba(255, 255, 255, 0.4);
}
footer p {
margin-bottom: 0.25rem;
}
footer .row.main {
padding: 40px 0;
}
footer .row.main .branding {
text-align: center;
}
footer .row.main .branding .btn {
display: inline-block;
color: #fff !important;
}
footer .row.main .branding button.account {
background-color: #2d3348;
}
footer .row.main .branding .cta {
margin: 50px auto 45px auto;
}
footer .row.main .branding .cta-secondary {
}
footer .row.main .links > div:first-child {
margin-bottom: 20px;
}
footer .row.main .links .category {
color: #4a68b9;
font-weight: 700;
}
footer .row.main .links .category:not(:first-child) {
.terms-of-service {
margin-top: 1rem;
}
footer .selector {
margin: 20px 0;
}
.pref-selectors {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
footer .row.version .col-sm-12 {
padding: 20px !important;
background-color: #11131f;
}
footer .row.version .col-sm-12 p {
margin-bottom: 0;
text-align: center;
font-size: 12px;
color: rgba(255, 255, 255, 0.4);
}
footer .row.version .col-sm-12 p a {
color: #09a3ba;
.selector {
margin-left: .5em;
margin-bottom: .5em;
&:first {
margin-left: 0;
}
}
}

View File

@@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { NavigationService } from '../../../services/navigation.service';
import { Env, StateService } from '../../../services/state.service';
import { IBackendInfo } from '../../../interfaces/websocket.interface';
@Component({
selector: 'app-global-footer',
@@ -15,9 +14,6 @@ export class GlobalFooterComponent implements OnInit {
networkPaths: { [network: string]: string };
officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE;
networkPaths$: Observable<Record<string, string>>;
backendInfo$: Observable<IBackendInfo>;
frontendGitCommitHash = this.stateService.env.GIT_COMMIT_HASH;
packetJsonVersion = this.stateService.env.PACKAGE_JSON_VERSION;
constructor(
public stateService: StateService,
@@ -27,7 +23,6 @@ export class GlobalFooterComponent implements OnInit {
ngOnInit(): void {
this.env = this.stateService.env;
this.networkPaths$ = this.navigationService.subnetPaths;
this.backendInfo$ = this.stateService.backendInfo$;
}
}